-
Notifications
You must be signed in to change notification settings - Fork 0
feature: adds timer to CLI #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
2a149af
feature: adds timer to CLI
marlonmarcello 0fc1e39
chore: changeset
marlonmarcello 09a11c9
fix: address PR review feedback in timer and upgrade commands
Copilot 2544c57
refactor: directory structure with apis
marlonmarcello 82d85c4
refactor: moves shared api interface from tui and cli
marlonmarcello 31c67e5
fix: missing mocks for tests
marlonmarcello 3519c58
fix: yargs handlers
marlonmarcello File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "wtc": patch | ||
| --- | ||
|
|
||
| Adds timers feature to CLI | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # `src/api/` — Shared business logic layer | ||
|
|
||
| All business logic lives here, organized by domain. Both `src/cli/` and `src/tui/` import from `src/api/` — never the reverse. | ||
|
|
||
| ## Domains | ||
|
|
||
| | Directory | Responsibility | | ||
| | ----------- | -------------------------------------------------------- | | ||
| | `teamwork/` | Teamwork API wrappers, local timers, task/data types | | ||
| | `config/` | Config file loading, saving, validation, schemas | | ||
| | `state/` | Per-directory TUI state persistence (route memory) | | ||
| | `cache/` | Cache directory management (`getCacheDir`, `clearCache`) | | ||
|
|
||
| ## Conventions | ||
|
|
||
| ### No presentation concerns | ||
|
|
||
| Functions in `src/api/` do not write to stdout, render JSX, or call `setMessage`. They take input and return data. Formatting output strings for display is the caller's responsibility (CLI handlers or TUI components). | ||
|
|
||
| ### Dependency injection for side effects | ||
|
|
||
| Functions that touch I/O (filesystem, network, secrets) accept an optional `actions` parameter with default wiring to real implementations. This allows tests to inject mocks without module-level mocking. | ||
|
|
||
| ```typescript | ||
| interface SubmitTimerActions { | ||
| stopLocalTimer: () => Promise<LocalTimerEntry | null>; | ||
| createTaskTimeEntry: (input: TeamworkTaskTimeEntryInput) => Promise<number>; | ||
| removeLocalTimer: (id: string) => Promise<void>; | ||
| } | ||
|
|
||
| export async function submitTimer( | ||
| timer: LocalTimerEntry, | ||
| actions: SubmitTimerActions, | ||
| ): Promise<SubmitResult> { | ||
| // ... | ||
| } | ||
| ``` | ||
|
|
||
| Tests pass custom `actions` objects; production callers rely on the defaults. | ||
|
|
||
| ### Pure functions stay pure | ||
|
|
||
| Utilities like `formatTimerDuration`, `getLocalTimerElapsedMs`, and Zod schemas are pure exports — no DI needed. | ||
|
|
||
| ### Format comments | ||
|
|
||
| Functions that produce a formatted string for CLI/TUI display include a JSDoc `@example` or `Example output:` block showing the expected format. See `formatTimerListOutput` or `formatTeamworkTaskListPinnedOutput` for the pattern. |
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { mkdir, rm } from "node:fs/promises"; | ||
|
|
||
| import { getCacheDir } from "./consts.ts"; | ||
|
|
||
| /** Deletes the entire cache directory. */ | ||
| export async function clearCache(): Promise<void> { | ||
| const cacheDir = getCacheDir(); | ||
| await rm(cacheDir, { recursive: true, force: true }); | ||
| await mkdir(cacheDir, { recursive: true }); | ||
| } |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion
2
src/teamwork/project-metadata.ts → src/api/teamwork/project-metadata.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { z } from "zod"; | ||
|
|
||
| import { fetchTeamworkApiJson } from "./client.ts"; | ||
|
|
||
| const TeamworkTaskByIdResponseSchema = z.object({ | ||
| task: z.object({ | ||
| id: z.union([z.number().int().positive(), z.string().regex(/^\d+$/).transform(Number)]), | ||
| name: z.string().optional(), | ||
| title: z.string().optional(), | ||
| content: z.string().optional(), | ||
| }), | ||
| }); | ||
|
|
||
| /** Fetches a single Teamwork task by ID and returns its id and name. */ | ||
| export async function getTeamworkTaskById(taskId: number): Promise<{ id: number; name: string }> { | ||
| const parsed = TeamworkTaskByIdResponseSchema.parse( | ||
| await fetchTeamworkApiJson(`/tasks/${taskId}.json`), | ||
| ); | ||
| const name = parsed.task.name ?? parsed.task.title ?? parsed.task.content; | ||
| if (!name) throw new Error("Teamwork task response did not include a task name."); | ||
|
|
||
| return { id: parsed.task.id, name }; | ||
| } |
File renamed without changes.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
src/teamwork/workflow-stages.ts → src/api/teamwork/workflow-stages.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # `src/cli/` — CLI presentation layer | ||
|
|
||
| Parses CLI arguments via yargs and prints output to stdout. Never contains business logic — calls `src/api/` functions for that. | ||
|
|
||
| ## Structure | ||
|
|
||
| | Path | Purpose | | ||
| | ----------------------- | ------------------------------------------------------------------ | | ||
| | `parser.ts` | yargs setup: registers all top-level commands | | ||
| | `commands/*-command.ts` | yargs command wiring (builder, handler) for each top-level command | | ||
| | `commands/*.ts` | CLI handler functions: call api/, format output, print | | ||
|
|
||
| ## Conventions | ||
|
|
||
| ### Handler pattern | ||
|
|
||
| Each handler function accepts a plain args object and an optional `actions` parameter for dependency injection: | ||
|
|
||
| ```typescript | ||
| export async function teamworkTimerList( | ||
| args: { json: boolean }, | ||
| actions = timerListActions, | ||
| ): Promise<void> { | ||
| const timers = await actions.loadLocalTimers(); | ||
| console.log(formatTimerListOutput(timers, { json: args.json })); | ||
| } | ||
| ``` | ||
|
|
||
| The default `actions` object wires real implementations. Tests pass custom stubs. | ||
|
|
||
| ### Output | ||
|
|
||
| All user-facing output goes through `console.log`. Pure formatting functions live alongside the handler and include a format example in their doc comment. | ||
|
|
||
| ### Yargs wiring | ||
|
|
||
| Command modules in `*-command.ts` files define the yargs tree. Each module: | ||
|
|
||
| - Declares positional args and options in `builder` | ||
| - Calls the handler function in `handler`, passing destructured args | ||
| - Parent commands with subcommands get `handler: () => {}` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import type { CommandModule } from "yargs"; | ||
|
|
||
| import { cacheClean } from "./cache.ts"; | ||
|
|
||
| const cacheCleanCommand: CommandModule = { | ||
| command: "clean", | ||
| describe: "Delete all cached data", | ||
| handler: () => cacheClean(), | ||
| }; | ||
|
|
||
| export const cacheCommand: CommandModule = { | ||
| command: "cache", | ||
| describe: "Manage local cache", | ||
| builder: (yargs) => | ||
| yargs.command(cacheCleanCommand).demandCommand(1, "Specify a cache subcommand: clean"), | ||
| handler: () => {}, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,15 @@ | ||
| import { clearCache } from "../../state/manager.ts"; | ||
| import { clearCache as clearCacheFn } from "../../api/cache/manager.ts"; | ||
|
|
||
| interface CacheCleanActions { | ||
| clearCache: () => Promise<void>; | ||
| } | ||
|
|
||
| const cacheCleanActions: CacheCleanActions = { | ||
| clearCache: clearCacheFn, | ||
| }; | ||
|
|
||
| /** Deletes the entire WTC cache directory. */ | ||
| export async function cacheClean(): Promise<void> { | ||
| await clearCache(); | ||
| export async function cacheClean(actions = cacheCleanActions): Promise<void> { | ||
| await actions.clearCache(); | ||
| console.log("Cache cleaned."); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.