@nijaru/tk 0.0.1 → 0.0.2
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/README.md +30 -17
- package/package.json +4 -4
- package/src/cli.test.ts +265 -5
- package/src/cli.ts +227 -106
- package/src/db/storage.ts +305 -90
- package/src/lib/completions.ts +15 -8
- package/src/lib/format.ts +26 -0
- package/src/types.ts +2 -2
package/src/cli.ts
CHANGED
|
@@ -4,7 +4,15 @@ import { version } from "../package.json";
|
|
|
4
4
|
|
|
5
5
|
import * as storage from "./db/storage";
|
|
6
6
|
import { parsePriority } from "./lib/priority";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
formatTaskList,
|
|
9
|
+
formatTaskDetail,
|
|
10
|
+
formatJson,
|
|
11
|
+
formatConfig,
|
|
12
|
+
green,
|
|
13
|
+
red,
|
|
14
|
+
yellow,
|
|
15
|
+
} from "./lib/format";
|
|
8
16
|
import { findRoot, setWorkingDir } from "./lib/root";
|
|
9
17
|
import { parseId } from "./types";
|
|
10
18
|
import type { Status } from "./types";
|
|
@@ -47,6 +55,13 @@ function parseEstimate(input: string | undefined): number | undefined {
|
|
|
47
55
|
return n;
|
|
48
56
|
}
|
|
49
57
|
|
|
58
|
+
function formatLocalDate(date: Date): string {
|
|
59
|
+
const year = date.getFullYear();
|
|
60
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
61
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
62
|
+
return `${year}-${month}-${day}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
function parseDueDate(input: string | undefined): string | undefined {
|
|
51
66
|
if (!input) return undefined;
|
|
52
67
|
if (input === "-") return undefined; // clear
|
|
@@ -73,17 +88,23 @@ function parseDueDate(input: string | undefined): string | undefined {
|
|
|
73
88
|
now.setMonth(now.getMonth() + n);
|
|
74
89
|
break;
|
|
75
90
|
}
|
|
76
|
-
return now
|
|
91
|
+
return formatLocalDate(now);
|
|
77
92
|
}
|
|
78
93
|
throw new Error(`Invalid relative date: ${input}. Use format like +7d, +2w, +1m`);
|
|
79
94
|
}
|
|
80
95
|
|
|
81
|
-
// Validate
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
|
|
96
|
+
// Validate YYYY-MM-DD format - return as-is to avoid timezone issues
|
|
97
|
+
const dateMatch = input.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
98
|
+
if (dateMatch) {
|
|
99
|
+
const [, , month, day] = dateMatch;
|
|
100
|
+
const m = parseInt(month!, 10);
|
|
101
|
+
const d = parseInt(day!, 10);
|
|
102
|
+
// Basic validation: month 1-12, day 1-31
|
|
103
|
+
if (m >= 1 && m <= 12 && d >= 1 && d <= 31) {
|
|
104
|
+
return input; // Return as-is, already in correct format
|
|
105
|
+
}
|
|
85
106
|
}
|
|
86
|
-
|
|
107
|
+
throw new Error(`Invalid date: ${input}. Use YYYY-MM-DD or +Nd format.`);
|
|
87
108
|
}
|
|
88
109
|
|
|
89
110
|
function parseLabels(input: string | undefined): string[] | undefined {
|
|
@@ -121,23 +142,26 @@ function resolveId(input: string | undefined, context: string): string {
|
|
|
121
142
|
throw new Error(`ID required: tk ${context} <id>`);
|
|
122
143
|
}
|
|
123
144
|
|
|
124
|
-
// Try to resolve
|
|
145
|
+
// Try to resolve partial ID
|
|
125
146
|
const resolved = storage.resolveId(input);
|
|
126
147
|
if (resolved) return resolved;
|
|
127
148
|
|
|
128
|
-
// Check if it's a valid full ID
|
|
149
|
+
// Check if it's a valid full ID format
|
|
129
150
|
if (parseId(input)) return input;
|
|
130
151
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
)
|
|
152
|
+
// Check for ambiguous matches
|
|
153
|
+
const matches = storage.findMatchingIds(input);
|
|
154
|
+
if (matches.length > 1) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`Ambiguous ID '${input}' matches ${matches.length} tasks: ${matches.join(", ")}`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
throw new Error(`Task not found: ${input}`);
|
|
134
161
|
}
|
|
135
162
|
|
|
136
163
|
const rawArgs = process.argv.slice(2);
|
|
137
164
|
|
|
138
|
-
// Global flags that can appear anywhere in the command
|
|
139
|
-
const GLOBAL_FLAGS = new Set(["--json", "--help", "-h", "--version", "-V"]);
|
|
140
|
-
|
|
141
165
|
function isFlag(arg: string): boolean {
|
|
142
166
|
return arg.startsWith("-");
|
|
143
167
|
}
|
|
@@ -158,23 +182,44 @@ if (dirFlag) {
|
|
|
158
182
|
setWorkingDir(dirFlag);
|
|
159
183
|
}
|
|
160
184
|
|
|
161
|
-
// Extract global flags from anywhere in args
|
|
162
|
-
const jsonFlag = argsWithoutDir.includes("--json");
|
|
163
|
-
const helpFlag = argsWithoutDir.includes("--help") || argsWithoutDir.includes("-h");
|
|
164
|
-
const versionFlag = argsWithoutDir.includes("--version") || argsWithoutDir.includes("-V");
|
|
165
|
-
|
|
166
185
|
// Find command: first non-flag argument
|
|
167
|
-
const
|
|
186
|
+
const commandIndex = argsWithoutDir.findIndex((arg) => !isFlag(arg));
|
|
187
|
+
const command = commandIndex >= 0 ? argsWithoutDir[commandIndex] : undefined;
|
|
168
188
|
|
|
169
|
-
// Get args for command
|
|
170
|
-
const
|
|
189
|
+
// Get args for command (before stripping flags)
|
|
190
|
+
const postCommandArgs = commandIndex >= 0 ? argsWithoutDir.slice(commandIndex + 1) : [];
|
|
191
|
+
const preCommandArgs = commandIndex >= 0 ? argsWithoutDir.slice(0, commandIndex) : argsWithoutDir;
|
|
192
|
+
|
|
193
|
+
// Global flag detection:
|
|
194
|
+
// --json can appear anywhere and should be stripped from args
|
|
195
|
+
// --help/-h detected before command OR as first arg after command (not in message content)
|
|
196
|
+
// --version/-V only detected before command
|
|
197
|
+
const jsonFlag = argsWithoutDir.includes("--json");
|
|
198
|
+
const helpFlag =
|
|
199
|
+
preCommandArgs.includes("--help") ||
|
|
200
|
+
preCommandArgs.includes("-h") ||
|
|
201
|
+
postCommandArgs[0] === "--help" ||
|
|
202
|
+
postCommandArgs[0] === "-h";
|
|
203
|
+
const versionFlag = preCommandArgs.includes("--version") || preCommandArgs.includes("-V");
|
|
204
|
+
|
|
205
|
+
// Strip --json from args (works from any position)
|
|
206
|
+
// Strip --help/-h only if it's the first arg (to allow "tk ls --help")
|
|
207
|
+
const args = postCommandArgs
|
|
208
|
+
.filter((arg) => arg !== "--json")
|
|
209
|
+
.filter((arg, i) => i !== 0 || (arg !== "--help" && arg !== "-h"));
|
|
171
210
|
|
|
172
211
|
function output(data: unknown, formatted: string) {
|
|
173
212
|
console.log(jsonFlag ? formatJson(data) : formatted);
|
|
174
213
|
}
|
|
175
214
|
|
|
215
|
+
function outputCleanup(taskId: string, cleanup: storage.CleanupInfo | null) {
|
|
216
|
+
if (cleanup && !jsonFlag) {
|
|
217
|
+
console.error(storage.formatCleanupMessage(taskId, cleanup));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
176
221
|
function error(message: string): never {
|
|
177
|
-
console.error(`Error: ${message}`);
|
|
222
|
+
console.error(red(`Error: ${message}`));
|
|
178
223
|
process.exit(1);
|
|
179
224
|
}
|
|
180
225
|
|
|
@@ -199,6 +244,7 @@ COMMANDS:
|
|
|
199
244
|
unblock Remove blocker
|
|
200
245
|
rm, remove Delete task
|
|
201
246
|
clean Remove old done tasks
|
|
247
|
+
check Check for data issues
|
|
202
248
|
config Show/set configuration
|
|
203
249
|
completions Output shell completions
|
|
204
250
|
|
|
@@ -328,7 +374,9 @@ EXAMPLES:
|
|
|
328
374
|
log: `tk log - Add a log entry to a task
|
|
329
375
|
|
|
330
376
|
USAGE:
|
|
331
|
-
tk log <id> <message>
|
|
377
|
+
tk log <id> "<message>"
|
|
378
|
+
|
|
379
|
+
Message must be quoted.
|
|
332
380
|
|
|
333
381
|
EXAMPLES:
|
|
334
382
|
tk log tk-1 "Started implementation"
|
|
@@ -371,16 +419,28 @@ USAGE:
|
|
|
371
419
|
tk clean [options]
|
|
372
420
|
|
|
373
421
|
OPTIONS:
|
|
374
|
-
--older-than <
|
|
375
|
-
-
|
|
422
|
+
--older-than <days> Age threshold in days (default: from config, 14)
|
|
423
|
+
-f, --force Remove all done tasks (ignores age and disabled state)
|
|
424
|
+
|
|
425
|
+
EXAMPLES:
|
|
426
|
+
tk clean # Remove done tasks older than config.clean_after days
|
|
427
|
+
tk clean --older-than 30 # Remove done tasks older than 30 days
|
|
428
|
+
tk clean --force # Remove all done tasks regardless of age
|
|
429
|
+
`,
|
|
430
|
+
check: `tk check - Check for data issues
|
|
431
|
+
|
|
432
|
+
USAGE:
|
|
433
|
+
tk check
|
|
376
434
|
|
|
377
|
-
|
|
378
|
-
|
|
435
|
+
Scans all tasks for issues. Auto-fixable issues (orphaned references, ID
|
|
436
|
+
mismatches) are fixed automatically. Unfixable issues (corrupted JSON) are
|
|
437
|
+
reported for manual intervention.
|
|
438
|
+
|
|
439
|
+
Note: Auto-fixing also happens during normal task operations (show, done,
|
|
440
|
+
edit, etc.) - this command is for bulk cleanup or diagnostics.
|
|
379
441
|
|
|
380
442
|
EXAMPLES:
|
|
381
|
-
tk
|
|
382
|
-
tk clean --older-than 30d
|
|
383
|
-
tk clean -a # Remove all done tasks
|
|
443
|
+
tk check # Scan and fix all tasks
|
|
384
444
|
`,
|
|
385
445
|
config: `tk config - Show or set configuration
|
|
386
446
|
|
|
@@ -464,10 +524,13 @@ function main() {
|
|
|
464
524
|
}
|
|
465
525
|
const info = findRoot();
|
|
466
526
|
if (info.exists) {
|
|
467
|
-
output(
|
|
527
|
+
output(
|
|
528
|
+
{ path: info.tasksDir, created: false },
|
|
529
|
+
green(`Already initialized: ${info.tasksDir}`),
|
|
530
|
+
);
|
|
468
531
|
} else {
|
|
469
532
|
const path = storage.initTasks(values.project);
|
|
470
|
-
output({ path, created: true }, `Initialized: ${path}`);
|
|
533
|
+
output({ path, created: true }, green(`Initialized: ${path}`));
|
|
471
534
|
}
|
|
472
535
|
break;
|
|
473
536
|
}
|
|
@@ -573,37 +636,43 @@ function main() {
|
|
|
573
636
|
|
|
574
637
|
case "show": {
|
|
575
638
|
const id = resolveId(args[0], "show");
|
|
576
|
-
const
|
|
577
|
-
if (!
|
|
578
|
-
|
|
639
|
+
const result = storage.getTaskWithMeta(id);
|
|
640
|
+
if (!result) error(`Task not found: ${id}`);
|
|
641
|
+
outputCleanup(id, result.cleanup);
|
|
642
|
+
output(result.task, formatTaskDetail(result.task, result.task.logs));
|
|
579
643
|
break;
|
|
580
644
|
}
|
|
581
645
|
|
|
582
646
|
case "start": {
|
|
583
647
|
const id = resolveId(args[0], "start");
|
|
584
|
-
const
|
|
585
|
-
if (!
|
|
586
|
-
|
|
648
|
+
const result = storage.getTask(id);
|
|
649
|
+
if (!result) error(`Task not found: ${id}`);
|
|
650
|
+
outputCleanup(id, result.cleanup);
|
|
651
|
+
if (result.task.status === "active")
|
|
652
|
+
error(`Task already active. Use 'tk done ${id}' to complete it.`);
|
|
653
|
+
if (result.task.status === "done") error(`Task already done. Use 'tk reopen ${id}' first.`);
|
|
587
654
|
const updated = storage.updateTaskStatus(id, "active");
|
|
588
|
-
output(updated, `Started: ${id}`);
|
|
655
|
+
output(updated, green(`Started: ${id}`));
|
|
589
656
|
break;
|
|
590
657
|
}
|
|
591
658
|
|
|
592
659
|
case "done": {
|
|
593
660
|
const id = resolveId(args[0], "done");
|
|
594
|
-
const
|
|
595
|
-
if (!
|
|
661
|
+
const result = storage.getTask(id);
|
|
662
|
+
if (!result) error(`Task not found: ${id}`);
|
|
663
|
+
outputCleanup(id, result.cleanup);
|
|
596
664
|
const updated = storage.updateTaskStatus(id, "done");
|
|
597
|
-
output(updated, `Completed: ${id}`);
|
|
665
|
+
output(updated, green(`Completed: ${id}`));
|
|
598
666
|
break;
|
|
599
667
|
}
|
|
600
668
|
|
|
601
669
|
case "reopen": {
|
|
602
670
|
const id = resolveId(args[0], "reopen");
|
|
603
|
-
const
|
|
604
|
-
if (!
|
|
671
|
+
const result = storage.getTask(id);
|
|
672
|
+
if (!result) error(`Task not found: ${id}`);
|
|
673
|
+
outputCleanup(id, result.cleanup);
|
|
605
674
|
const updated = storage.updateTaskStatus(id, "open");
|
|
606
|
-
output(updated, `Reopened: ${id}`);
|
|
675
|
+
output(updated, green(`Reopened: ${id}`));
|
|
607
676
|
break;
|
|
608
677
|
}
|
|
609
678
|
|
|
@@ -623,8 +692,9 @@ function main() {
|
|
|
623
692
|
allowPositionals: true,
|
|
624
693
|
});
|
|
625
694
|
const id = resolveId(positionals[0], "edit");
|
|
626
|
-
const
|
|
627
|
-
if (!
|
|
695
|
+
const result = storage.getTask(id);
|
|
696
|
+
if (!result) error(`Task not found: ${id}`);
|
|
697
|
+
outputCleanup(id, result.cleanup);
|
|
628
698
|
|
|
629
699
|
// Handle label modifications (+tag, -tag)
|
|
630
700
|
let labels: string[] | undefined;
|
|
@@ -632,11 +702,13 @@ function main() {
|
|
|
632
702
|
if (values.labels.startsWith("+")) {
|
|
633
703
|
// Add label (avoid duplicates)
|
|
634
704
|
const newLabel = values.labels.slice(1);
|
|
635
|
-
labels = task.labels.includes(newLabel)
|
|
705
|
+
labels = result.task.labels.includes(newLabel)
|
|
706
|
+
? result.task.labels
|
|
707
|
+
: [...result.task.labels, newLabel];
|
|
636
708
|
} else if (values.labels.startsWith("-")) {
|
|
637
709
|
// Remove label
|
|
638
710
|
const removeLabel = values.labels.slice(1);
|
|
639
|
-
labels = task.labels.filter((l) => l !== removeLabel);
|
|
711
|
+
labels = result.task.labels.filter((l) => l !== removeLabel);
|
|
640
712
|
} else {
|
|
641
713
|
// Replace labels
|
|
642
714
|
labels = parseLabels(values.labels);
|
|
@@ -665,18 +737,25 @@ function main() {
|
|
|
665
737
|
estimate: values.estimate === "-" ? null : (parseEstimate(values.estimate) ?? undefined),
|
|
666
738
|
due_date: values.due === "-" ? null : (parseDueDate(values.due) ?? undefined),
|
|
667
739
|
});
|
|
668
|
-
output(updated, `Updated: ${id}`);
|
|
740
|
+
output(updated, green(`Updated: ${id}`));
|
|
669
741
|
break;
|
|
670
742
|
}
|
|
671
743
|
|
|
672
744
|
case "log": {
|
|
673
745
|
const id = resolveId(args[0], "log");
|
|
674
|
-
const message = args
|
|
675
|
-
if (!message) error(
|
|
676
|
-
|
|
677
|
-
|
|
746
|
+
const message = args[1]?.trim();
|
|
747
|
+
if (!message) error('Message required: tk log <id> "<message>"');
|
|
748
|
+
if (args.length > 2) {
|
|
749
|
+
error(
|
|
750
|
+
'Message must be quoted: tk log <id> "<message>"\n' +
|
|
751
|
+
` Got ${args.length - 1} arguments instead of 1`,
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
const result = storage.getTask(id);
|
|
755
|
+
if (!result) error(`Task not found: ${id}`);
|
|
756
|
+
outputCleanup(id, result.cleanup);
|
|
678
757
|
const entry = storage.addLogEntry(id, message);
|
|
679
|
-
output(entry, `Logged: ${id}`);
|
|
758
|
+
output(entry, green(`Logged: ${id}`));
|
|
680
759
|
break;
|
|
681
760
|
}
|
|
682
761
|
|
|
@@ -687,7 +766,10 @@ function main() {
|
|
|
687
766
|
if (taskId === blockerId) error("Task cannot block itself");
|
|
688
767
|
const result = storage.addBlock(taskId, blockerId);
|
|
689
768
|
if (!result.ok) error(result.error!);
|
|
690
|
-
output(
|
|
769
|
+
output(
|
|
770
|
+
{ task_id: taskId, blocked_by: blockerId },
|
|
771
|
+
green(`${taskId} blocked by ${blockerId}`),
|
|
772
|
+
);
|
|
691
773
|
break;
|
|
692
774
|
}
|
|
693
775
|
|
|
@@ -695,40 +777,101 @@ function main() {
|
|
|
695
777
|
if (!args[0] || !args[1]) error("Usage: tk unblock <task> <blocker>");
|
|
696
778
|
const taskId = resolveId(args[0], "unblock");
|
|
697
779
|
const blockerId = resolveId(args[1], "unblock");
|
|
698
|
-
const
|
|
699
|
-
if (!
|
|
700
|
-
output(
|
|
780
|
+
const removed = storage.removeBlock(taskId, blockerId);
|
|
781
|
+
if (!removed) error(`${taskId} is not blocked by ${blockerId}`);
|
|
782
|
+
output(
|
|
783
|
+
{ task_id: taskId, blocked_by: blockerId },
|
|
784
|
+
green(`${taskId} unblocked from ${blockerId}`),
|
|
785
|
+
);
|
|
701
786
|
break;
|
|
702
787
|
}
|
|
703
788
|
|
|
704
789
|
case "rm":
|
|
705
790
|
case "remove": {
|
|
706
791
|
const id = resolveId(args[0], "rm");
|
|
707
|
-
const
|
|
708
|
-
if (!
|
|
709
|
-
output({ id, deleted: true }, `Deleted: ${id}`);
|
|
792
|
+
const deleted = storage.deleteTask(id);
|
|
793
|
+
if (!deleted) error(`Task not found: ${id}`);
|
|
794
|
+
output({ id, deleted: true }, green(`Deleted: ${id}`));
|
|
710
795
|
break;
|
|
711
796
|
}
|
|
712
797
|
|
|
713
798
|
case "clean": {
|
|
799
|
+
const config = storage.getConfig();
|
|
714
800
|
const { values } = parseArgs({
|
|
715
801
|
args,
|
|
716
802
|
options: {
|
|
717
|
-
"older-than": { type: "string"
|
|
718
|
-
|
|
719
|
-
all: { type: "boolean", short: "a" },
|
|
803
|
+
"older-than": { type: "string" },
|
|
804
|
+
force: { type: "boolean", short: "f" },
|
|
720
805
|
},
|
|
721
806
|
allowPositionals: true,
|
|
722
807
|
});
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
808
|
+
|
|
809
|
+
// Get days from CLI or config
|
|
810
|
+
let days: number | false;
|
|
811
|
+
if (values["older-than"] !== undefined) {
|
|
812
|
+
const n = parseInt(values["older-than"], 10);
|
|
813
|
+
if (isNaN(n) || n < 0) {
|
|
814
|
+
error(`Invalid --older-than: ${values["older-than"]}. Use a number of days.`);
|
|
815
|
+
}
|
|
816
|
+
days = n;
|
|
817
|
+
} else {
|
|
818
|
+
days = config.clean_after;
|
|
819
|
+
// Validate config value at runtime
|
|
820
|
+
if (days !== false && (typeof days !== "number" || days < 0 || !Number.isFinite(days))) {
|
|
821
|
+
error(
|
|
822
|
+
`Invalid clean_after in config: ${JSON.stringify(days)}. Must be a number or false.`,
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (days === false && !values.force) {
|
|
828
|
+
error("Cleaning is disabled (clean_after: false). Use --force to override.");
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const ms = days === false ? 0 : days * 24 * 60 * 60 * 1000;
|
|
726
832
|
const count = storage.cleanTasks({
|
|
727
833
|
olderThanMs: ms,
|
|
728
|
-
|
|
729
|
-
all: values.all,
|
|
834
|
+
force: values.force,
|
|
730
835
|
});
|
|
731
|
-
output({ deleted: count }, `Cleaned ${count} tasks`);
|
|
836
|
+
output({ deleted: count }, green(`Cleaned ${count} tasks`));
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
case "check": {
|
|
841
|
+
const checkResult = storage.checkTasks();
|
|
842
|
+
|
|
843
|
+
if (jsonFlag) {
|
|
844
|
+
output(checkResult, "");
|
|
845
|
+
} else {
|
|
846
|
+
// Report cleaned issues
|
|
847
|
+
if (checkResult.cleaned.length > 0) {
|
|
848
|
+
for (const { task, info } of checkResult.cleaned) {
|
|
849
|
+
console.log(storage.formatCleanupMessage(task, info));
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Report unfixable issues
|
|
854
|
+
if (checkResult.unfixable.length > 0) {
|
|
855
|
+
console.log(red("\nUnfixable issues (require manual intervention):"));
|
|
856
|
+
for (const { file, error: err } of checkResult.unfixable) {
|
|
857
|
+
console.log(red(` ${file}: ${err}`));
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Summary
|
|
862
|
+
if (checkResult.cleaned.length === 0 && checkResult.unfixable.length === 0) {
|
|
863
|
+
console.log(green(`All ${checkResult.totalTasks} tasks OK`));
|
|
864
|
+
} else {
|
|
865
|
+
const parts: string[] = [];
|
|
866
|
+
if (checkResult.cleaned.length > 0) {
|
|
867
|
+
parts.push(yellow(`${checkResult.cleaned.length} fixed`));
|
|
868
|
+
}
|
|
869
|
+
if (checkResult.unfixable.length > 0) {
|
|
870
|
+
parts.push(red(`${checkResult.unfixable.length} unfixable`));
|
|
871
|
+
}
|
|
872
|
+
console.log(`\nChecked ${checkResult.totalTasks} tasks: ${parts.join(", ")}`);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
732
875
|
break;
|
|
733
876
|
}
|
|
734
877
|
|
|
@@ -759,15 +902,17 @@ function main() {
|
|
|
759
902
|
const result = storage.renameProject(values.rename, newProject);
|
|
760
903
|
output(
|
|
761
904
|
result,
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
905
|
+
green(
|
|
906
|
+
`Renamed ${result.renamed.length} tasks: ${values.rename}-* → ${newProject}-*` +
|
|
907
|
+
(result.referencesUpdated > 0
|
|
908
|
+
? `\nUpdated ${result.referencesUpdated} references`
|
|
909
|
+
: ""),
|
|
910
|
+
),
|
|
766
911
|
);
|
|
767
912
|
} else {
|
|
768
913
|
validateProject(newProject);
|
|
769
914
|
const updated = storage.setDefaultProject(newProject);
|
|
770
|
-
output(updated, `Default project: ${newProject}`);
|
|
915
|
+
output(updated, green(`Default project: ${newProject}`));
|
|
771
916
|
}
|
|
772
917
|
break;
|
|
773
918
|
}
|
|
@@ -783,7 +928,7 @@ function main() {
|
|
|
783
928
|
|
|
784
929
|
if (values.rm) {
|
|
785
930
|
const updated = storage.removeAlias(values.rm);
|
|
786
|
-
output(updated, `Removed alias: ${values.rm}`);
|
|
931
|
+
output(updated, green(`Removed alias: ${values.rm}`));
|
|
787
932
|
} else if (positionals.length >= 2) {
|
|
788
933
|
const alias = positionals[0];
|
|
789
934
|
const path = positionals[1];
|
|
@@ -791,7 +936,7 @@ function main() {
|
|
|
791
936
|
error("Alias name and path are required");
|
|
792
937
|
}
|
|
793
938
|
const updated = storage.setAlias(alias, path);
|
|
794
|
-
output(updated, `Added alias: ${alias} → ${path}`);
|
|
939
|
+
output(updated, green(`Added alias: ${alias} → ${path}`));
|
|
795
940
|
} else {
|
|
796
941
|
// List aliases
|
|
797
942
|
const aliases = config.aliases;
|
|
@@ -835,37 +980,13 @@ function main() {
|
|
|
835
980
|
}
|
|
836
981
|
}
|
|
837
982
|
|
|
838
|
-
function parseDuration(s: string): number {
|
|
839
|
-
const match = s.match(/^(\d+)(s|m|h|d|w)$/);
|
|
840
|
-
if (!match || !match[1] || !match[2]) {
|
|
841
|
-
throw new Error(`Invalid duration: ${s}. Use format like 7d, 24h, 30m, 90s, or 2w`);
|
|
842
|
-
}
|
|
843
|
-
const num = match[1];
|
|
844
|
-
const unit = match[2];
|
|
845
|
-
const n = parseInt(num);
|
|
846
|
-
switch (unit) {
|
|
847
|
-
case "s":
|
|
848
|
-
return n * 1000;
|
|
849
|
-
case "m":
|
|
850
|
-
return n * 60 * 1000;
|
|
851
|
-
case "h":
|
|
852
|
-
return n * 60 * 60 * 1000;
|
|
853
|
-
case "d":
|
|
854
|
-
return n * 24 * 60 * 60 * 1000;
|
|
855
|
-
case "w":
|
|
856
|
-
return n * 7 * 24 * 60 * 60 * 1000;
|
|
857
|
-
default:
|
|
858
|
-
throw new Error(`Invalid duration unit: ${unit}`);
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
|
|
862
983
|
try {
|
|
863
984
|
main();
|
|
864
985
|
} catch (e) {
|
|
865
986
|
if (e instanceof Error) {
|
|
866
|
-
console.error(`Error: ${e.message}`);
|
|
987
|
+
console.error(red(`Error: ${e.message}`));
|
|
867
988
|
} else {
|
|
868
|
-
console.error("An unexpected error occurred");
|
|
989
|
+
console.error(red("An unexpected error occurred"));
|
|
869
990
|
}
|
|
870
991
|
process.exit(1);
|
|
871
992
|
}
|