@nijaru/tk 0.0.3 → 0.0.4
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/package.json +1 -1
- package/src/cli.ts +35 -267
- package/src/db/storage.ts +2 -2
- package/src/lib/format.test.ts +8 -4
- package/src/lib/format.ts +21 -9
- package/src/lib/help.ts +249 -0
package/package.json
CHANGED
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(
|
|
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
|
|
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
|
-
|
|
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)
|
|
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!);
|
|
@@ -571,7 +328,8 @@ function main() {
|
|
|
571
328
|
let parentFilter: string | undefined;
|
|
572
329
|
if (values.parent) {
|
|
573
330
|
const resolved = storage.resolveId(values.parent);
|
|
574
|
-
if (!resolved)
|
|
331
|
+
if (!resolved)
|
|
332
|
+
error(`Parent task not found: ${values.parent}. Run 'tk ls' to see available tasks.`);
|
|
575
333
|
parentFilter = resolved;
|
|
576
334
|
}
|
|
577
335
|
|
|
@@ -592,7 +350,10 @@ function main() {
|
|
|
592
350
|
|
|
593
351
|
case "ready": {
|
|
594
352
|
const list = storage.listReadyTasks();
|
|
595
|
-
output(
|
|
353
|
+
output(
|
|
354
|
+
list,
|
|
355
|
+
formatTaskList(list, undefined, "No ready tasks. All tasks are either done or blocked."),
|
|
356
|
+
);
|
|
596
357
|
break;
|
|
597
358
|
}
|
|
598
359
|
|
|
@@ -660,7 +421,8 @@ function main() {
|
|
|
660
421
|
resolvedParent = null;
|
|
661
422
|
} else if (values.parent) {
|
|
662
423
|
const resolved = storage.resolveId(values.parent);
|
|
663
|
-
if (!resolved)
|
|
424
|
+
if (!resolved)
|
|
425
|
+
error(`Parent task not found: ${values.parent}. Run 'tk ls' to see available tasks.`);
|
|
664
426
|
resolvedParent = resolved;
|
|
665
427
|
const parentResult = storage.validateParent(resolvedParent, task.id);
|
|
666
428
|
if (!parentResult.ok) error(parentResult.error!);
|
|
@@ -696,10 +458,13 @@ function main() {
|
|
|
696
458
|
}
|
|
697
459
|
|
|
698
460
|
case "block": {
|
|
699
|
-
if (!args[0] || !args[1])
|
|
461
|
+
if (!args[0] || !args[1])
|
|
462
|
+
error(
|
|
463
|
+
"Two IDs required: tk block <task> <blocker>. The first task becomes blocked by the second.",
|
|
464
|
+
);
|
|
700
465
|
const taskId = resolveId(args[0], "block");
|
|
701
466
|
const blockerId = resolveId(args[1], "block");
|
|
702
|
-
if (taskId === blockerId) error("Task cannot block itself");
|
|
467
|
+
if (taskId === blockerId) error("Task cannot block itself.");
|
|
703
468
|
const result = storage.addBlock(taskId, blockerId);
|
|
704
469
|
if (!result.ok) error(result.error!);
|
|
705
470
|
output(
|
|
@@ -710,7 +475,7 @@ function main() {
|
|
|
710
475
|
}
|
|
711
476
|
|
|
712
477
|
case "unblock": {
|
|
713
|
-
if (!args[0] || !args[1]) error("
|
|
478
|
+
if (!args[0] || !args[1]) error("Two IDs required: tk unblock <task> <blocker>.");
|
|
714
479
|
const taskId = resolveId(args[0], "unblock");
|
|
715
480
|
const blockerId = resolveId(args[1], "unblock");
|
|
716
481
|
const removed = storage.removeBlock(taskId, blockerId);
|
|
@@ -726,7 +491,7 @@ function main() {
|
|
|
726
491
|
case "remove": {
|
|
727
492
|
const id = resolveId(args[0], "rm");
|
|
728
493
|
const deleted = storage.deleteTask(id);
|
|
729
|
-
if (!deleted) error(`Task not found: ${id}
|
|
494
|
+
if (!deleted) error(`Task not found: ${id}. Run 'tk ls' to see available tasks.`);
|
|
730
495
|
output({ id, deleted: true }, green(`Deleted: ${id}`));
|
|
731
496
|
break;
|
|
732
497
|
}
|
|
@@ -868,7 +633,7 @@ function main() {
|
|
|
868
633
|
const alias = positionals[0];
|
|
869
634
|
const path = positionals[1];
|
|
870
635
|
if (!alias || !path || !alias.trim()) {
|
|
871
|
-
error("Alias name and path
|
|
636
|
+
error("Alias name and path required: tk config alias <name> <path>");
|
|
872
637
|
}
|
|
873
638
|
const updated = storage.setAlias(alias, path);
|
|
874
639
|
output(updated, green(`Added alias: ${alias} → ${path}`));
|
|
@@ -876,7 +641,10 @@ function main() {
|
|
|
876
641
|
// List aliases
|
|
877
642
|
const aliases = config.aliases;
|
|
878
643
|
if (Object.keys(aliases).length === 0) {
|
|
879
|
-
output(
|
|
644
|
+
output(
|
|
645
|
+
{ aliases: {} },
|
|
646
|
+
"No aliases defined. Add one with: tk config alias <name> <path>",
|
|
647
|
+
);
|
|
880
648
|
} else {
|
|
881
649
|
const lines = Object.entries(aliases)
|
|
882
650
|
.map(([a, p]) => `${a} → ${p}`)
|
|
@@ -887,7 +655,7 @@ function main() {
|
|
|
887
655
|
break;
|
|
888
656
|
}
|
|
889
657
|
default:
|
|
890
|
-
error(`Unknown config command: ${subcommand}
|
|
658
|
+
error(`Unknown config command: ${subcommand}. Valid: project, alias.`);
|
|
891
659
|
}
|
|
892
660
|
break;
|
|
893
661
|
}
|
|
@@ -905,7 +673,7 @@ function main() {
|
|
|
905
673
|
console.log(FISH_COMPLETION);
|
|
906
674
|
break;
|
|
907
675
|
default:
|
|
908
|
-
error("Shell required: tk completions <bash|zsh|fish
|
|
676
|
+
error("Shell required: tk completions <bash|zsh|fish>. Add to your shell's rc file.");
|
|
909
677
|
}
|
|
910
678
|
break;
|
|
911
679
|
}
|
package/src/db/storage.ts
CHANGED
|
@@ -158,7 +158,7 @@ export function renameProject(oldProject: string, newProject: string): RenameRes
|
|
|
158
158
|
// Find tasks to rename
|
|
159
159
|
const toRename = allTasks.filter((t) => t.project === oldProject);
|
|
160
160
|
if (toRename.length === 0) {
|
|
161
|
-
throw new Error(`No tasks found with project "${oldProject}"
|
|
161
|
+
throw new Error(`No tasks found with project "${oldProject}". Run 'tk ls' to see projects.`);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
// Check for collisions
|
|
@@ -166,7 +166,7 @@ export function renameProject(oldProject: string, newProject: string): RenameRes
|
|
|
166
166
|
for (const task of toRename) {
|
|
167
167
|
const newId = `${newProject}-${task.ref}`;
|
|
168
168
|
if (existingIds.has(newId)) {
|
|
169
|
-
throw new Error(`Cannot rename: "${newId}" already exists
|
|
169
|
+
throw new Error(`Cannot rename: "${newId}" already exists. Choose a different project name.`);
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
package/src/lib/format.test.ts
CHANGED
|
@@ -92,13 +92,13 @@ describe("formatTaskRow", () => {
|
|
|
92
92
|
expect(parts.length).toBe(4); // ID | PRIO | STATUS | TITLE
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
test("truncates long project names
|
|
95
|
+
test("truncates long project names with ellipsis", () => {
|
|
96
96
|
const longProjectTask: TaskWithMeta = {
|
|
97
97
|
...task,
|
|
98
98
|
id: "mylongproject-a1b2",
|
|
99
99
|
};
|
|
100
100
|
const result = formatTaskRow(longProjectTask, false);
|
|
101
|
-
expect(result).toContain("
|
|
101
|
+
expect(result).toContain("mylon…-a1b2");
|
|
102
102
|
expect(result).not.toContain("mylongproject");
|
|
103
103
|
});
|
|
104
104
|
|
|
@@ -130,8 +130,12 @@ describe("formatTaskRow", () => {
|
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
describe("formatTaskList", () => {
|
|
133
|
-
test("returns message for empty list", () => {
|
|
134
|
-
expect(formatTaskList([])).toBe("No tasks found.");
|
|
133
|
+
test("returns helpful message for empty list", () => {
|
|
134
|
+
expect(formatTaskList([])).toBe("No tasks found. Run 'tk add \"title\"' to create one.");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("allows custom empty hint", () => {
|
|
138
|
+
expect(formatTaskList([], undefined, "Custom hint")).toBe("Custom hint");
|
|
135
139
|
});
|
|
136
140
|
|
|
137
141
|
test("includes header and divider", () => {
|
package/src/lib/format.ts
CHANGED
|
@@ -43,22 +43,28 @@ export function dim(msg: string): string {
|
|
|
43
43
|
return shouldUseColor() ? `${DIM}${msg}${RESET}` : msg;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
function truncate(text: string, maxLen: number): string {
|
|
47
|
+
return text.length <= maxLen ? text : text.slice(0, maxLen - 1) + "…";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatId(id: string, maxLen = 11): string {
|
|
51
|
+
// Truncate project prefix to fit maxLen, keep full 4-char ref
|
|
52
|
+
// "myproject-a1b2" -> "mypr…-a1b2"
|
|
49
53
|
const dash = id.lastIndexOf("-");
|
|
50
|
-
if (dash === -1) return id
|
|
54
|
+
if (dash === -1) return truncate(id, maxLen);
|
|
51
55
|
const project = id.slice(0, dash);
|
|
52
56
|
const ref = id.slice(dash + 1);
|
|
53
|
-
const
|
|
54
|
-
return
|
|
57
|
+
const maxProjectLen = maxLen - ref.length - 1; // -1 for dash
|
|
58
|
+
return project.length > maxProjectLen
|
|
59
|
+
? `${truncate(project, maxProjectLen)}-${ref}`
|
|
60
|
+
: `${project}-${ref}`;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
export function formatTaskRow(task: TaskWithMeta, useColor?: boolean): string {
|
|
58
64
|
const color = useColor ?? shouldUseColor();
|
|
59
65
|
const id = formatId(task.id).padEnd(11);
|
|
60
66
|
const priority = formatPriority(task.priority).padEnd(4);
|
|
61
|
-
const title = task.title
|
|
67
|
+
const title = truncate(task.title, 50);
|
|
62
68
|
const isOverdue = task.is_overdue;
|
|
63
69
|
|
|
64
70
|
let statusText: string = task.status;
|
|
@@ -78,8 +84,14 @@ export function formatTaskRow(task: TaskWithMeta, useColor?: boolean): string {
|
|
|
78
84
|
return `${id} | ${priority} | ${status} | ${title}${overdueMarker}`;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
|
-
export function formatTaskList(
|
|
82
|
-
|
|
87
|
+
export function formatTaskList(
|
|
88
|
+
tasks: TaskWithMeta[],
|
|
89
|
+
useColor?: boolean,
|
|
90
|
+
emptyHint?: string,
|
|
91
|
+
): string {
|
|
92
|
+
if (tasks.length === 0) {
|
|
93
|
+
return emptyHint ?? "No tasks found. Run 'tk add \"title\"' to create one.";
|
|
94
|
+
}
|
|
83
95
|
const color = useColor ?? shouldUseColor();
|
|
84
96
|
|
|
85
97
|
const header = "ID | PRIO | STATUS | TITLE";
|
package/src/lib/help.ts
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { version } from "../../package.json";
|
|
2
|
+
|
|
3
|
+
export const MAIN_HELP = `tk v${version} - Task tracker for AI agents
|
|
4
|
+
|
|
5
|
+
USAGE:
|
|
6
|
+
tk <command> [options]
|
|
7
|
+
|
|
8
|
+
COMMANDS:
|
|
9
|
+
init Initialize .tasks/ directory
|
|
10
|
+
add Create task
|
|
11
|
+
ls, list List tasks
|
|
12
|
+
ready List ready tasks (active/open + unblocked)
|
|
13
|
+
show Show task details
|
|
14
|
+
start Start working (open -> active)
|
|
15
|
+
done Complete task
|
|
16
|
+
reopen Reopen task
|
|
17
|
+
edit Edit task
|
|
18
|
+
log Add log entry
|
|
19
|
+
block Add blocker
|
|
20
|
+
unblock Remove blocker
|
|
21
|
+
rm, remove Delete task
|
|
22
|
+
clean Remove old done tasks
|
|
23
|
+
check Check for data issues
|
|
24
|
+
config Show/set configuration
|
|
25
|
+
completions Output shell completions
|
|
26
|
+
|
|
27
|
+
GLOBAL OPTIONS:
|
|
28
|
+
-C <dir> Run in different directory
|
|
29
|
+
--json Output as JSON
|
|
30
|
+
-h, --help Show help
|
|
31
|
+
-V Show version
|
|
32
|
+
|
|
33
|
+
Run 'tk <command> --help' for command-specific options.
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
export const COMMAND_HELP: Record<string, string> = {
|
|
37
|
+
init: `tk init - Initialize .tasks/ directory
|
|
38
|
+
|
|
39
|
+
USAGE:
|
|
40
|
+
tk init [options]
|
|
41
|
+
|
|
42
|
+
OPTIONS:
|
|
43
|
+
-P, --project <name> Set default project name
|
|
44
|
+
`,
|
|
45
|
+
add: `tk add - Create a new task
|
|
46
|
+
|
|
47
|
+
USAGE:
|
|
48
|
+
tk add <title> [options]
|
|
49
|
+
|
|
50
|
+
OPTIONS:
|
|
51
|
+
-p, --priority <0-4> Priority (0=none, 1=urgent, 2=high, 3=medium, 4=low)
|
|
52
|
+
-P, --project <name> Project prefix for ID
|
|
53
|
+
-d, --description <text> Description
|
|
54
|
+
-l, --labels <csv> Labels (comma-separated)
|
|
55
|
+
-A, --assignees <csv> Assignees (comma-separated, @me for git user)
|
|
56
|
+
--parent <id> Parent task ID
|
|
57
|
+
--estimate <n> Estimate (user-defined units)
|
|
58
|
+
--due <date> Due date (YYYY-MM-DD or +Nh/+Nd/+Nw/+Nm)
|
|
59
|
+
|
|
60
|
+
EXAMPLES:
|
|
61
|
+
tk add "Fix login bug" -p 1
|
|
62
|
+
tk add "New feature" -P api -l bug,urgent
|
|
63
|
+
tk add "Sprint task" --due +7d
|
|
64
|
+
`,
|
|
65
|
+
ls: `tk ls - List tasks
|
|
66
|
+
|
|
67
|
+
USAGE:
|
|
68
|
+
tk ls [options]
|
|
69
|
+
|
|
70
|
+
OPTIONS:
|
|
71
|
+
-s, --status <status> Filter by status (open, active, done)
|
|
72
|
+
-p, --priority <0-4> Filter by priority
|
|
73
|
+
-P, --project <name> Filter by project
|
|
74
|
+
-l, --label <label> Filter by label
|
|
75
|
+
--assignee <name> Filter by assignee
|
|
76
|
+
--parent <id> Filter by parent
|
|
77
|
+
--roots Show only root tasks (no parent)
|
|
78
|
+
--overdue Show only overdue tasks
|
|
79
|
+
-n, --limit <n> Limit results (default: 20)
|
|
80
|
+
-a, --all Show all (no limit)
|
|
81
|
+
|
|
82
|
+
EXAMPLES:
|
|
83
|
+
tk ls -s open -p 1 # Urgent open tasks
|
|
84
|
+
tk ls --overdue # Overdue tasks
|
|
85
|
+
tk ls -P api --roots # Root tasks in api project
|
|
86
|
+
`,
|
|
87
|
+
list: `tk list - List tasks (alias for 'ls')
|
|
88
|
+
|
|
89
|
+
Run 'tk ls --help' for options.
|
|
90
|
+
`,
|
|
91
|
+
ready: `tk ready - List ready tasks (active/open + unblocked)
|
|
92
|
+
|
|
93
|
+
USAGE:
|
|
94
|
+
tk ready
|
|
95
|
+
|
|
96
|
+
Shows active or open tasks that are not blocked by any incomplete task.
|
|
97
|
+
`,
|
|
98
|
+
show: `tk show - Show task details
|
|
99
|
+
|
|
100
|
+
USAGE:
|
|
101
|
+
tk show <id>
|
|
102
|
+
|
|
103
|
+
EXAMPLES:
|
|
104
|
+
tk show tk-1
|
|
105
|
+
tk show 1 # If unambiguous
|
|
106
|
+
`,
|
|
107
|
+
start: `tk start - Start working on a task
|
|
108
|
+
|
|
109
|
+
USAGE:
|
|
110
|
+
tk start <id>
|
|
111
|
+
|
|
112
|
+
Changes status from open to active.
|
|
113
|
+
`,
|
|
114
|
+
done: `tk done - Complete a task
|
|
115
|
+
|
|
116
|
+
USAGE:
|
|
117
|
+
tk done <id>
|
|
118
|
+
|
|
119
|
+
Changes status to done.
|
|
120
|
+
`,
|
|
121
|
+
reopen: `tk reopen - Reopen a task
|
|
122
|
+
|
|
123
|
+
USAGE:
|
|
124
|
+
tk reopen <id>
|
|
125
|
+
|
|
126
|
+
Changes status back to open.
|
|
127
|
+
`,
|
|
128
|
+
edit: `tk edit - Edit a task
|
|
129
|
+
|
|
130
|
+
USAGE:
|
|
131
|
+
tk edit <id> [options]
|
|
132
|
+
|
|
133
|
+
OPTIONS:
|
|
134
|
+
-t, --title <text> New title
|
|
135
|
+
-d, --description <text> New description
|
|
136
|
+
-p, --priority <0-4> New priority
|
|
137
|
+
-l, --labels <csv> Replace labels (use +tag/-tag to add/remove)
|
|
138
|
+
-A, --assignees <csv> Replace assignees
|
|
139
|
+
--parent <id> Set parent (use - to clear)
|
|
140
|
+
--estimate <n> Set estimate (use - to clear)
|
|
141
|
+
--due <date> Set due date (use - to clear)
|
|
142
|
+
|
|
143
|
+
EXAMPLES:
|
|
144
|
+
tk edit tk-1 -t "New title"
|
|
145
|
+
tk edit tk-1 -l +urgent # Add label
|
|
146
|
+
tk edit tk-1 --due - # Clear due date
|
|
147
|
+
`,
|
|
148
|
+
log: `tk log - Add a log entry to a task
|
|
149
|
+
|
|
150
|
+
USAGE:
|
|
151
|
+
tk log <id> "<message>"
|
|
152
|
+
|
|
153
|
+
Message must be quoted.
|
|
154
|
+
|
|
155
|
+
EXAMPLES:
|
|
156
|
+
tk log tk-1 "Started implementation"
|
|
157
|
+
tk log tk-1 "Blocked on API changes"
|
|
158
|
+
`,
|
|
159
|
+
block: `tk block - Add a blocker dependency
|
|
160
|
+
|
|
161
|
+
USAGE:
|
|
162
|
+
tk block <task> <blocker>
|
|
163
|
+
|
|
164
|
+
The first task becomes blocked by the second.
|
|
165
|
+
|
|
166
|
+
EXAMPLES:
|
|
167
|
+
tk block tk-2 tk-1 # tk-2 is blocked by tk-1
|
|
168
|
+
`,
|
|
169
|
+
unblock: `tk unblock - Remove a blocker dependency
|
|
170
|
+
|
|
171
|
+
USAGE:
|
|
172
|
+
tk unblock <task> <blocker>
|
|
173
|
+
|
|
174
|
+
EXAMPLES:
|
|
175
|
+
tk unblock tk-2 tk-1
|
|
176
|
+
`,
|
|
177
|
+
rm: `tk rm - Delete a task
|
|
178
|
+
|
|
179
|
+
USAGE:
|
|
180
|
+
tk rm <id>
|
|
181
|
+
|
|
182
|
+
EXAMPLES:
|
|
183
|
+
tk rm tk-1
|
|
184
|
+
`,
|
|
185
|
+
remove: `tk remove - Delete a task (alias for 'rm')
|
|
186
|
+
|
|
187
|
+
USAGE:
|
|
188
|
+
tk remove <id>
|
|
189
|
+
`,
|
|
190
|
+
clean: `tk clean - Remove old completed tasks
|
|
191
|
+
|
|
192
|
+
USAGE:
|
|
193
|
+
tk clean [options]
|
|
194
|
+
|
|
195
|
+
OPTIONS:
|
|
196
|
+
--older-than <days> Age threshold in days (default: from config, 14)
|
|
197
|
+
-f, --force Remove all done tasks (ignores age and disabled state)
|
|
198
|
+
|
|
199
|
+
EXAMPLES:
|
|
200
|
+
tk clean # Remove done tasks older than config.clean_after days
|
|
201
|
+
tk clean --older-than 30 # Remove done tasks older than 30 days
|
|
202
|
+
tk clean --force # Remove all done tasks regardless of age
|
|
203
|
+
`,
|
|
204
|
+
check: `tk check - Check for data issues
|
|
205
|
+
|
|
206
|
+
USAGE:
|
|
207
|
+
tk check
|
|
208
|
+
|
|
209
|
+
Scans all tasks for issues. Auto-fixable issues (orphaned references, ID
|
|
210
|
+
mismatches) are fixed automatically. Unfixable issues (corrupted JSON) are
|
|
211
|
+
reported for manual intervention.
|
|
212
|
+
|
|
213
|
+
Note: Auto-fixing also happens during normal task operations (show, done,
|
|
214
|
+
edit, etc.) - this command is for bulk cleanup or diagnostics.
|
|
215
|
+
|
|
216
|
+
EXAMPLES:
|
|
217
|
+
tk check # Scan and fix all tasks
|
|
218
|
+
`,
|
|
219
|
+
config: `tk config - Show or set configuration
|
|
220
|
+
|
|
221
|
+
USAGE:
|
|
222
|
+
tk config Show all config
|
|
223
|
+
tk config project Show default project
|
|
224
|
+
tk config project <name> Set default project
|
|
225
|
+
tk config project <new> --rename <old> Rename project (old-* → new-*)
|
|
226
|
+
tk config alias List aliases
|
|
227
|
+
tk config alias <name> <path> Add alias
|
|
228
|
+
tk config alias --rm <name> Remove alias
|
|
229
|
+
|
|
230
|
+
EXAMPLES:
|
|
231
|
+
tk config project api
|
|
232
|
+
tk config project lsmvec --rename cloudlsmvec
|
|
233
|
+
tk config alias web src/web
|
|
234
|
+
`,
|
|
235
|
+
completions: `tk completions - Output shell completions
|
|
236
|
+
|
|
237
|
+
USAGE:
|
|
238
|
+
tk completions <shell>
|
|
239
|
+
|
|
240
|
+
SHELLS:
|
|
241
|
+
bash Bash completion script
|
|
242
|
+
zsh Zsh completion script
|
|
243
|
+
fish Fish completion script
|
|
244
|
+
|
|
245
|
+
EXAMPLES:
|
|
246
|
+
eval "$(tk completions bash)" # Add to ~/.bashrc
|
|
247
|
+
eval "$(tk completions zsh)" # Add to ~/.zshrc
|
|
248
|
+
`,
|
|
249
|
+
};
|