@nijaru/tk 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cli.ts CHANGED
@@ -13,11 +13,13 @@ import {
13
13
  green,
14
14
  red,
15
15
  yellow,
16
+ dim,
16
17
  } from "./lib/format";
17
18
  import { findRoot, setWorkingDir } from "./lib/root";
18
19
  import { parseId } from "./types";
19
20
  import type { Status, TaskWithMeta } from "./types";
20
21
  import { BASH_COMPLETION, ZSH_COMPLETION, FISH_COMPLETION } from "./lib/completions";
22
+ import { MAIN_HELP, COMMAND_HELP } from "./lib/help";
21
23
 
22
24
  const VALID_STATUSES: Status[] = ["open", "active", "done"];
23
25
  const PROJECT_PATTERN = /^[a-z][a-z0-9]*$/;
@@ -40,7 +42,7 @@ const TASK_MUTATION_OPTIONS = {
40
42
  function validateProject(name: string): void {
41
43
  if (!PROJECT_PATTERN.test(name)) {
42
44
  throw new Error(
43
- `Invalid project name: ${name}. Use lowercase letters and numbers, starting with a letter.`,
45
+ `Invalid project name: ${name}. Use lowercase letters and numbers, starting with a letter (e.g., 'api', 'web2').`,
44
46
  );
45
47
  }
46
48
  }
@@ -108,7 +110,7 @@ function resolveId(input: string | undefined, context: string): string {
108
110
  const matches = storage.findMatchingIds(input);
109
111
  if (matches.length > 1) {
110
112
  throw new Error(
111
- `Ambiguous ID '${input}' matches ${matches.length} tasks: ${matches.join(", ")}`,
113
+ `Ambiguous ID '${input}' matches ${matches.length} tasks: ${matches.join(", ")}. Use more characters to narrow it down.`,
112
114
  );
113
115
  }
114
116
 
@@ -116,7 +118,7 @@ function resolveId(input: string | undefined, context: string): string {
116
118
  // and let getTask handle the "not found" error consistently.
117
119
  if (parseId(input)) return input;
118
120
 
119
- throw new Error(`Task not found: ${input}`);
121
+ throw new Error(`Task not found: ${input}. Run 'tk ls' to see available tasks.`);
120
122
  }
121
123
 
122
124
  /**
@@ -126,7 +128,7 @@ function resolveId(input: string | undefined, context: string): string {
126
128
  function resolveTask(input: string | undefined, context: string): TaskWithMeta {
127
129
  const id = resolveId(input, context);
128
130
  const result = storage.getTask(id);
129
- if (!result) error(`Task not found: ${id}`);
131
+ if (!result) error(`Task not found: ${id}. Run 'tk ls' to see available tasks.`);
130
132
 
131
133
  outputCleanup(id, result.cleanup);
132
134
  return result.task;
@@ -195,258 +197,12 @@ function error(message: string): never {
195
197
  process.exit(1);
196
198
  }
197
199
 
198
- function showHelp() {
199
- console.log(`tk v${version} - Task tracker for AI agents
200
-
201
- USAGE:
202
- tk <command> [options]
203
-
204
- COMMANDS:
205
- init Initialize .tasks/ directory
206
- add Create task
207
- ls, list List tasks
208
- ready List ready tasks (active/open + unblocked)
209
- show Show task details
210
- start Start working (open -> active)
211
- done Complete task
212
- reopen Reopen task
213
- edit Edit task
214
- log Add log entry
215
- block Add blocker
216
- unblock Remove blocker
217
- rm, remove Delete task
218
- clean Remove old done tasks
219
- check Check for data issues
220
- config Show/set configuration
221
- completions Output shell completions
222
-
223
- GLOBAL OPTIONS:
224
- -C <dir> Run in different directory
225
- --json Output as JSON
226
- -h, --help Show help
227
- -V Show version
228
-
229
- Run 'tk <command> --help' for command-specific options.
230
- `);
200
+ function showHelp(): void {
201
+ console.log(MAIN_HELP);
231
202
  }
232
203
 
233
- function showCommandHelp(cmd: string) {
234
- const helps: Record<string, string> = {
235
- init: `tk init - Initialize .tasks/ directory
236
-
237
- USAGE:
238
- tk init [options]
239
-
240
- OPTIONS:
241
- -P, --project <name> Set default project name
242
- `,
243
- add: `tk add - Create a new task
244
-
245
- USAGE:
246
- tk add <title> [options]
247
-
248
- OPTIONS:
249
- -p, --priority <0-4> Priority (0=none, 1=urgent, 2=high, 3=medium, 4=low)
250
- -P, --project <name> Project prefix for ID
251
- -d, --description <text> Description
252
- -l, --labels <csv> Labels (comma-separated)
253
- -A, --assignees <csv> Assignees (comma-separated, @me for git user)
254
- --parent <id> Parent task ID
255
- --estimate <n> Estimate (user-defined units)
256
- --due <date> Due date (YYYY-MM-DD or +Nh/+Nd/+Nw/+Nm)
257
-
258
- EXAMPLES:
259
- tk add "Fix login bug" -p 1
260
- tk add "New feature" -P api -l bug,urgent
261
- tk add "Sprint task" --due +7d
262
- `,
263
- ls: `tk ls - List tasks
264
-
265
- USAGE:
266
- tk ls [options]
267
-
268
- OPTIONS:
269
- -s, --status <status> Filter by status (open, active, done)
270
- -p, --priority <0-4> Filter by priority
271
- -P, --project <name> Filter by project
272
- -l, --label <label> Filter by label
273
- --assignee <name> Filter by assignee
274
- --parent <id> Filter by parent
275
- --roots Show only root tasks (no parent)
276
- --overdue Show only overdue tasks
277
- -n, --limit <n> Limit results (default: 20)
278
- -a, --all Show all (no limit)
279
-
280
- EXAMPLES:
281
- tk ls -s open -p 1 # Urgent open tasks
282
- tk ls --overdue # Overdue tasks
283
- tk ls -P api --roots # Root tasks in api project
284
- `,
285
- list: `tk list - List tasks (alias for 'ls')
286
-
287
- Run 'tk ls --help' for options.
288
- `,
289
- ready: `tk ready - List ready tasks (active/open + unblocked)
290
-
291
- USAGE:
292
- tk ready
293
-
294
- Shows active or open tasks that are not blocked by any incomplete task.
295
- `,
296
- show: `tk show - Show task details
297
-
298
- USAGE:
299
- tk show <id>
300
-
301
- EXAMPLES:
302
- tk show tk-1
303
- tk show 1 # If unambiguous
304
- `,
305
- start: `tk start - Start working on a task
306
-
307
- USAGE:
308
- tk start <id>
309
-
310
- Changes status from open to active.
311
- `,
312
- done: `tk done - Complete a task
313
-
314
- USAGE:
315
- tk done <id>
316
-
317
- Changes status to done.
318
- `,
319
- reopen: `tk reopen - Reopen a task
320
-
321
- USAGE:
322
- tk reopen <id>
323
-
324
- Changes status back to open.
325
- `,
326
- edit: `tk edit - Edit a task
327
-
328
- USAGE:
329
- tk edit <id> [options]
330
-
331
- OPTIONS:
332
- -t, --title <text> New title
333
- -d, --description <text> New description
334
- -p, --priority <0-4> New priority
335
- -l, --labels <csv> Replace labels (use +tag/-tag to add/remove)
336
- -A, --assignees <csv> Replace assignees
337
- --parent <id> Set parent (use - to clear)
338
- --estimate <n> Set estimate (use - to clear)
339
- --due <date> Set due date (use - to clear)
340
-
341
- EXAMPLES:
342
- tk edit tk-1 -t "New title"
343
- tk edit tk-1 -l +urgent # Add label
344
- tk edit tk-1 --due - # Clear due date
345
- `,
346
- log: `tk log - Add a log entry to a task
347
-
348
- USAGE:
349
- tk log <id> "<message>"
350
-
351
- Message must be quoted.
352
-
353
- EXAMPLES:
354
- tk log tk-1 "Started implementation"
355
- tk log tk-1 "Blocked on API changes"
356
- `,
357
- block: `tk block - Add a blocker dependency
358
-
359
- USAGE:
360
- tk block <task> <blocker>
361
-
362
- The first task becomes blocked by the second.
363
-
364
- EXAMPLES:
365
- tk block tk-2 tk-1 # tk-2 is blocked by tk-1
366
- `,
367
- unblock: `tk unblock - Remove a blocker dependency
368
-
369
- USAGE:
370
- tk unblock <task> <blocker>
371
-
372
- EXAMPLES:
373
- tk unblock tk-2 tk-1
374
- `,
375
- rm: `tk rm - Delete a task
376
-
377
- USAGE:
378
- tk rm <id>
379
-
380
- EXAMPLES:
381
- tk rm tk-1
382
- `,
383
- remove: `tk remove - Delete a task (alias for 'rm')
384
-
385
- USAGE:
386
- tk remove <id>
387
- `,
388
- clean: `tk clean - Remove old completed tasks
389
-
390
- USAGE:
391
- tk clean [options]
392
-
393
- OPTIONS:
394
- --older-than <days> Age threshold in days (default: from config, 14)
395
- -f, --force Remove all done tasks (ignores age and disabled state)
396
-
397
- EXAMPLES:
398
- tk clean # Remove done tasks older than config.clean_after days
399
- tk clean --older-than 30 # Remove done tasks older than 30 days
400
- tk clean --force # Remove all done tasks regardless of age
401
- `,
402
- check: `tk check - Check for data issues
403
-
404
- USAGE:
405
- tk check
406
-
407
- Scans all tasks for issues. Auto-fixable issues (orphaned references, ID
408
- mismatches) are fixed automatically. Unfixable issues (corrupted JSON) are
409
- reported for manual intervention.
410
-
411
- Note: Auto-fixing also happens during normal task operations (show, done,
412
- edit, etc.) - this command is for bulk cleanup or diagnostics.
413
-
414
- EXAMPLES:
415
- tk check # Scan and fix all tasks
416
- `,
417
- config: `tk config - Show or set configuration
418
-
419
- USAGE:
420
- tk config Show all config
421
- tk config project Show default project
422
- tk config project <name> Set default project
423
- tk config project <new> --rename <old> Rename project (old-* → new-*)
424
- tk config alias List aliases
425
- tk config alias <name> <path> Add alias
426
- tk config alias --rm <name> Remove alias
427
-
428
- EXAMPLES:
429
- tk config project api
430
- tk config project lsmvec --rename cloudlsmvec
431
- tk config alias web src/web
432
- `,
433
- completions: `tk completions - Output shell completions
434
-
435
- USAGE:
436
- tk completions <shell>
437
-
438
- SHELLS:
439
- bash Bash completion script
440
- zsh Zsh completion script
441
- fish Fish completion script
442
-
443
- EXAMPLES:
444
- eval "$(tk completions bash)" # Add to ~/.bashrc
445
- eval "$(tk completions zsh)" # Add to ~/.zshrc
446
- `,
447
- };
448
-
449
- const help = helps[cmd];
204
+ function showCommandHelp(cmd: string): void {
205
+ const help = COMMAND_HELP[cmd];
450
206
  if (help) {
451
207
  console.log(help);
452
208
  } else {
@@ -498,7 +254,7 @@ function main() {
498
254
  if (info.exists) {
499
255
  output(
500
256
  { path: info.tasksDir, created: false },
501
- green(`Already initialized: ${info.tasksDir}`),
257
+ dim(`Already initialized: ${info.tasksDir}`),
502
258
  );
503
259
  } else {
504
260
  const path = storage.initTasks(values.project);
@@ -525,7 +281,8 @@ function main() {
525
281
  let parentId: string | undefined;
526
282
  if (values.parent) {
527
283
  const resolved = storage.resolveId(values.parent);
528
- if (!resolved) error(`Parent task not found: ${values.parent}`);
284
+ if (!resolved)
285
+ error(`Parent task not found: ${values.parent}. Run 'tk ls' to see available tasks.`);
529
286
  parentId = resolved;
530
287
  const parentResult = storage.validateParent(parentId);
531
288
  if (!parentResult.ok) error(parentResult.error!);
@@ -551,8 +308,9 @@ function main() {
551
308
  const { values } = parseArgs({
552
309
  args,
553
310
  options: {
554
- ...COMMON_OPTIONS,
555
- label: COMMON_OPTIONS.labels, // alias for consistency
311
+ project: { type: "string", short: "P" },
312
+ priority: { type: "string", short: "p" },
313
+ label: { type: "string", short: "l" },
556
314
  status: { type: "string", short: "s" },
557
315
  assignee: { type: "string" },
558
316
  parent: { type: "string" },
@@ -571,7 +329,8 @@ function main() {
571
329
  let parentFilter: string | undefined;
572
330
  if (values.parent) {
573
331
  const resolved = storage.resolveId(values.parent);
574
- if (!resolved) error(`Parent task not found: ${values.parent}`);
332
+ if (!resolved)
333
+ error(`Parent task not found: ${values.parent}. Run 'tk ls' to see available tasks.`);
575
334
  parentFilter = resolved;
576
335
  }
577
336
 
@@ -579,7 +338,7 @@ function main() {
579
338
  status,
580
339
  priority,
581
340
  project: values.project,
582
- label: values.label ?? values.labels,
341
+ label: values.label,
583
342
  assignee: values.assignee,
584
343
  parent: parentFilter,
585
344
  roots: values.roots,
@@ -592,7 +351,10 @@ function main() {
592
351
 
593
352
  case "ready": {
594
353
  const list = storage.listReadyTasks();
595
- output(list, formatTaskList(list));
354
+ output(
355
+ list,
356
+ formatTaskList(list, undefined, "No ready tasks. All tasks are either done or blocked."),
357
+ );
596
358
  break;
597
359
  }
598
360
 
@@ -641,12 +403,12 @@ function main() {
641
403
  let labels: string[] | undefined;
642
404
  if (values.labels) {
643
405
  if (values.labels.startsWith("+")) {
644
- // Add label (avoid duplicates)
645
- const newLabel = values.labels.slice(1);
406
+ const newLabel = values.labels.slice(1).trim();
407
+ if (!newLabel) throw new Error("Label name required after '+'");
646
408
  labels = task.labels.includes(newLabel) ? task.labels : [...task.labels, newLabel];
647
409
  } else if (values.labels.startsWith("-")) {
648
- // Remove label
649
- const removeLabel = values.labels.slice(1);
410
+ const removeLabel = values.labels.slice(1).trim();
411
+ if (!removeLabel) throw new Error("Label name required after '-'");
650
412
  labels = task.labels.filter((l: string) => l !== removeLabel);
651
413
  } else {
652
414
  // Replace labels
@@ -660,7 +422,8 @@ function main() {
660
422
  resolvedParent = null;
661
423
  } else if (values.parent) {
662
424
  const resolved = storage.resolveId(values.parent);
663
- if (!resolved) error(`Parent task not found: ${values.parent}`);
425
+ if (!resolved)
426
+ error(`Parent task not found: ${values.parent}. Run 'tk ls' to see available tasks.`);
664
427
  resolvedParent = resolved;
665
428
  const parentResult = storage.validateParent(resolvedParent, task.id);
666
429
  if (!parentResult.ok) error(parentResult.error!);
@@ -668,7 +431,7 @@ function main() {
668
431
 
669
432
  const updated = storage.updateTask(task.id, {
670
433
  title: values.title?.trim() || undefined,
671
- description: values.description,
434
+ description: values.description === "-" ? null : values.description,
672
435
  priority: values.priority ? parsePriority(values.priority) : undefined,
673
436
  labels,
674
437
  assignees: parseAssignees(values.assignees),
@@ -696,10 +459,13 @@ function main() {
696
459
  }
697
460
 
698
461
  case "block": {
699
- if (!args[0] || !args[1]) error("Usage: tk block <task> <blocker>");
462
+ if (!args[0] || !args[1])
463
+ error(
464
+ "Two IDs required: tk block <task> <blocker>. The first task becomes blocked by the second.",
465
+ );
700
466
  const taskId = resolveId(args[0], "block");
701
467
  const blockerId = resolveId(args[1], "block");
702
- if (taskId === blockerId) error("Task cannot block itself");
468
+ if (taskId === blockerId) error("Task cannot block itself.");
703
469
  const result = storage.addBlock(taskId, blockerId);
704
470
  if (!result.ok) error(result.error!);
705
471
  output(
@@ -710,7 +476,7 @@ function main() {
710
476
  }
711
477
 
712
478
  case "unblock": {
713
- if (!args[0] || !args[1]) error("Usage: tk unblock <task> <blocker>");
479
+ if (!args[0] || !args[1]) error("Two IDs required: tk unblock <task> <blocker>.");
714
480
  const taskId = resolveId(args[0], "unblock");
715
481
  const blockerId = resolveId(args[1], "unblock");
716
482
  const removed = storage.removeBlock(taskId, blockerId);
@@ -726,11 +492,24 @@ function main() {
726
492
  case "remove": {
727
493
  const id = resolveId(args[0], "rm");
728
494
  const deleted = storage.deleteTask(id);
729
- if (!deleted) error(`Task not found: ${id}`);
495
+ if (!deleted) error(`Task not found: ${id}. Run 'tk ls' to see available tasks.`);
730
496
  output({ id, deleted: true }, green(`Deleted: ${id}`));
731
497
  break;
732
498
  }
733
499
 
500
+ case "mv":
501
+ case "move": {
502
+ const id = resolveId(args[0], "mv");
503
+ const newProject = args[1];
504
+ if (!newProject) error("Project required: tk mv <id> <project>");
505
+ validateProject(newProject);
506
+ const result = storage.moveTask(id, newProject);
507
+ const refMsg =
508
+ result.referencesUpdated > 0 ? `\nUpdated ${result.referencesUpdated} references` : "";
509
+ output(result, green(`Moved: ${result.old_id} → ${result.new_id}${refMsg}`));
510
+ break;
511
+ }
512
+
734
513
  case "clean": {
735
514
  const config = storage.getConfig();
736
515
  const { values } = parseArgs({
@@ -851,43 +630,8 @@ function main() {
851
630
  }
852
631
  break;
853
632
  }
854
- case "alias": {
855
- const { values, positionals } = parseArgs({
856
- args: args.slice(1),
857
- options: {
858
- rm: { type: "string" },
859
- list: { type: "boolean" },
860
- },
861
- allowPositionals: true,
862
- });
863
-
864
- if (values.rm) {
865
- const updated = storage.removeAlias(values.rm);
866
- output(updated, green(`Removed alias: ${values.rm}`));
867
- } else if (positionals.length >= 2) {
868
- const alias = positionals[0];
869
- const path = positionals[1];
870
- if (!alias || !path || !alias.trim()) {
871
- error("Alias name and path are required");
872
- }
873
- const updated = storage.setAlias(alias, path);
874
- output(updated, green(`Added alias: ${alias} → ${path}`));
875
- } else {
876
- // List aliases
877
- const aliases = config.aliases;
878
- if (Object.keys(aliases).length === 0) {
879
- output({ aliases: {} }, "No aliases defined.");
880
- } else {
881
- const lines = Object.entries(aliases)
882
- .map(([a, p]) => `${a} → ${p}`)
883
- .join("\n");
884
- output({ aliases }, lines);
885
- }
886
- }
887
- break;
888
- }
889
633
  default:
890
- error(`Unknown config command: ${subcommand}`);
634
+ error(`Unknown config command: ${subcommand}. Valid: project.`);
891
635
  }
892
636
  break;
893
637
  }
@@ -905,7 +649,7 @@ function main() {
905
649
  console.log(FISH_COMPLETION);
906
650
  break;
907
651
  default:
908
- error("Shell required: tk completions <bash|zsh|fish>");
652
+ error("Shell required: tk completions <bash|zsh|fish>. Add to your shell's rc file.");
909
653
  }
910
654
  break;
911
655
  }