@nijaru/tk 0.0.4 → 0.1.1

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/lib/help.ts DELETED
@@ -1,249 +0,0 @@
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
- };
@@ -1,105 +0,0 @@
1
- import { test, expect, describe } from "bun:test";
2
- import { parsePriority, formatPriority, formatPriorityName } from "./priority";
3
-
4
- describe("parsePriority", () => {
5
- describe("valid inputs", () => {
6
- test("returns 3 (medium) for undefined", () => {
7
- expect(parsePriority(undefined)).toBe(3);
8
- });
9
-
10
- test("parses numeric strings 0-4", () => {
11
- expect(parsePriority("0")).toBe(0);
12
- expect(parsePriority("1")).toBe(1);
13
- expect(parsePriority("2")).toBe(2);
14
- expect(parsePriority("3")).toBe(3);
15
- expect(parsePriority("4")).toBe(4);
16
- });
17
-
18
- test("parses numbers 0-4", () => {
19
- expect(parsePriority(0)).toBe(0);
20
- expect(parsePriority(1)).toBe(1);
21
- expect(parsePriority(2)).toBe(2);
22
- expect(parsePriority(3)).toBe(3);
23
- expect(parsePriority(4)).toBe(4);
24
- });
25
-
26
- test("parses p0-p4 format (lowercase)", () => {
27
- expect(parsePriority("p0")).toBe(0);
28
- expect(parsePriority("p1")).toBe(1);
29
- expect(parsePriority("p2")).toBe(2);
30
- expect(parsePriority("p3")).toBe(3);
31
- expect(parsePriority("p4")).toBe(4);
32
- });
33
-
34
- test("parses P0-P4 format (uppercase)", () => {
35
- expect(parsePriority("P0")).toBe(0);
36
- expect(parsePriority("P1")).toBe(1);
37
- expect(parsePriority("P2")).toBe(2);
38
- expect(parsePriority("P3")).toBe(3);
39
- expect(parsePriority("P4")).toBe(4);
40
- });
41
-
42
- test("parses named priorities", () => {
43
- expect(parsePriority("none")).toBe(0);
44
- expect(parsePriority("urgent")).toBe(1);
45
- expect(parsePriority("high")).toBe(2);
46
- expect(parsePriority("medium")).toBe(3);
47
- expect(parsePriority("low")).toBe(4);
48
- });
49
-
50
- test("parses named priorities case-insensitively", () => {
51
- expect(parsePriority("URGENT")).toBe(1);
52
- expect(parsePriority("High")).toBe(2);
53
- expect(parsePriority("MEDIUM")).toBe(3);
54
- });
55
- });
56
-
57
- describe("invalid inputs", () => {
58
- test("throws for out of range numbers", () => {
59
- expect(() => parsePriority("5")).toThrow("Invalid priority");
60
- expect(() => parsePriority("-1")).toThrow("Invalid priority");
61
- expect(() => parsePriority("99")).toThrow("Invalid priority");
62
- });
63
-
64
- test("throws for out of range pX format", () => {
65
- expect(() => parsePriority("p5")).toThrow("Invalid priority");
66
- expect(() => parsePriority("p6")).toThrow("Invalid priority");
67
- expect(() => parsePriority("p-1")).toThrow("Invalid priority");
68
- });
69
-
70
- test("throws for invalid named priorities", () => {
71
- expect(() => parsePriority("critical")).toThrow("Invalid priority");
72
- expect(() => parsePriority("abc")).toThrow("Invalid priority");
73
- });
74
-
75
- test("throws for empty string", () => {
76
- expect(() => parsePriority("")).toThrow("Invalid priority");
77
- });
78
-
79
- test("error message includes valid formats", () => {
80
- expect(() => parsePriority("invalid")).toThrow(
81
- "Use 0-4, p0-p4, or none/urgent/high/medium/low",
82
- );
83
- });
84
- });
85
- });
86
-
87
- describe("formatPriority", () => {
88
- test("formats priorities as pX", () => {
89
- expect(formatPriority(0)).toBe("p0");
90
- expect(formatPriority(1)).toBe("p1");
91
- expect(formatPriority(2)).toBe("p2");
92
- expect(formatPriority(3)).toBe("p3");
93
- expect(formatPriority(4)).toBe("p4");
94
- });
95
- });
96
-
97
- describe("formatPriorityName", () => {
98
- test("formats priorities as names", () => {
99
- expect(formatPriorityName(0)).toBe("none");
100
- expect(formatPriorityName(1)).toBe("urgent");
101
- expect(formatPriorityName(2)).toBe("high");
102
- expect(formatPriorityName(3)).toBe("medium");
103
- expect(formatPriorityName(4)).toBe("low");
104
- });
105
- });
@@ -1,40 +0,0 @@
1
- import type { Priority } from "../types";
2
- import { PRIORITY_FROM_NAME, PRIORITY_LABELS } from "../types";
3
-
4
- /**
5
- * Parse priority from various formats:
6
- * - Number: 0-4
7
- * - Prefixed: p0-p4
8
- * - Named: none, urgent, high, medium, low
9
- */
10
- export function parsePriority(input: string | number | undefined): Priority {
11
- if (input === undefined) return 3; // default: medium
12
-
13
- const str = String(input).toLowerCase();
14
-
15
- // Handle named format
16
- const named = PRIORITY_FROM_NAME[str];
17
- if (named !== undefined) {
18
- return named;
19
- }
20
-
21
- // Handle pX format
22
- if (str.startsWith("p")) {
23
- const num = parseInt(str.slice(1), 10);
24
- if (num >= 0 && num <= 4) return num as Priority;
25
- }
26
-
27
- // Handle numeric format
28
- const num = parseInt(str, 10);
29
- if (num >= 0 && num <= 4) return num as Priority;
30
-
31
- throw new Error(`Invalid priority: ${input}. Use 0-4, p0-p4, or none/urgent/high/medium/low.`);
32
- }
33
-
34
- export function formatPriority(p: Priority): string {
35
- return `p${p}`;
36
- }
37
-
38
- export function formatPriorityName(p: Priority): string {
39
- return PRIORITY_LABELS[p];
40
- }
package/src/lib/root.ts DELETED
@@ -1,79 +0,0 @@
1
- import { existsSync } from "fs";
2
- import { join, dirname, resolve } from "path";
3
-
4
- const TASKS_DIR = ".tasks";
5
- const GIT_DIR = ".git";
6
-
7
- // Global override for working directory (set via -C flag)
8
- let workingDir: string | null = null;
9
-
10
- export function setWorkingDir(dir: string | null): void {
11
- workingDir = dir ? resolve(dir) : null;
12
- }
13
-
14
- export function getWorkingDir(): string {
15
- return workingDir ?? process.cwd();
16
- }
17
-
18
- export interface RootInfo {
19
- root: string;
20
- tasksDir: string;
21
- exists: boolean;
22
- }
23
-
24
- /**
25
- * Find the project root by walking up directories.
26
- * Looks for existing .tasks/ or .git/ directory.
27
- * Returns cwd if neither found.
28
- */
29
- export function findRoot(): RootInfo {
30
- let current = getWorkingDir();
31
-
32
- while (true) {
33
- const tasksPath = join(current, TASKS_DIR);
34
- if (existsSync(tasksPath)) {
35
- return { root: current, tasksDir: tasksPath, exists: true };
36
- }
37
-
38
- const gitPath = join(current, GIT_DIR);
39
- if (existsSync(gitPath)) {
40
- return {
41
- root: current,
42
- tasksDir: join(current, TASKS_DIR),
43
- exists: false,
44
- };
45
- }
46
-
47
- const parent = dirname(current);
48
- if (parent === current) break; // Reached filesystem root
49
- current = parent;
50
- }
51
-
52
- // No .git or .tasks found, use working dir
53
- const wd = getWorkingDir();
54
- return {
55
- root: wd,
56
- tasksDir: join(wd, TASKS_DIR),
57
- exists: false,
58
- };
59
- }
60
-
61
- /**
62
- * Get the tasks directory path, optionally requiring it to exist.
63
- */
64
- export function getTasksDir(requireExists = false): string {
65
- const info = findRoot();
66
- if (requireExists && !info.exists) {
67
- throw new TasksNotFoundError(info.root);
68
- }
69
- return info.tasksDir;
70
- }
71
-
72
- export class TasksNotFoundError extends Error {
73
- constructor(searchedFrom: string) {
74
- super(
75
- `No .tasks/ directory found. Run 'tk add' to create one, or initialize with 'tk init'.\nSearched from: ${searchedFrom}`,
76
- );
77
- this.name = "TasksNotFoundError";
78
- }
79
- }
package/src/lib/time.ts DELETED
@@ -1,115 +0,0 @@
1
- import type { Status } from "../types";
2
-
3
- /**
4
- * Determines if a task is overdue.
5
- * Done tasks are never overdue.
6
- */
7
- export function isTaskOverdue(dueDate: string | null, status: Status): boolean {
8
- if (!dueDate || status === "done") return false;
9
- const today = new Date();
10
- today.setHours(0, 0, 0, 0);
11
- const parts = dueDate.split("-").map(Number);
12
- const year = parts[0];
13
- const month = parts[1];
14
- const day = parts[2];
15
- if (!year || !month || !day) return false;
16
- const due = new Date(year, month - 1, day);
17
- return due < today;
18
- }
19
-
20
- /**
21
- * Format a timestamp into a human-readable relative time (e.g., "2d", "5h").
22
- */
23
- export function formatRelativeTime(timestamp: string): string {
24
- const now = new Date();
25
- const date = new Date(timestamp);
26
- const diffMs = now.getTime() - date.getTime();
27
- const diffSec = Math.floor(diffMs / 1000);
28
- const diffMin = Math.floor(diffSec / 60);
29
- const diffHour = Math.floor(diffMin / 60);
30
- const diffDay = Math.floor(diffHour / 24);
31
-
32
- if (diffDay > 0) return `${diffDay}d`;
33
- if (diffHour > 0) return `${diffHour}h`;
34
- if (diffMin > 0) return `${diffMin}m`;
35
- return "now";
36
- }
37
-
38
- /**
39
- * Format a timestamp into a localized date string.
40
- */
41
- export function formatDate(timestamp: string): string {
42
- return new Date(timestamp).toLocaleString();
43
- }
44
-
45
- /**
46
- * Format a Date object into YYYY-MM-DD.
47
- */
48
- export function formatLocalDate(date: Date): string {
49
- const year = date.getFullYear();
50
- const month = String(date.getMonth() + 1).padStart(2, "0");
51
- const day = String(date.getDate()).padStart(2, "0");
52
- return `${year}-${month}-${day}`;
53
- }
54
-
55
- /**
56
- * Parse a due date input string. Supports YYYY-MM-DD and relative formats like +7d.
57
- */
58
- export function parseDueDate(input: string | undefined): string | undefined {
59
- if (!input) return undefined;
60
- if (input === "-") return undefined;
61
-
62
- // Handle relative dates like +7d
63
- if (input.startsWith("+")) {
64
- const match = input.match(/^\+(\d+)([dwmh])$/);
65
- if (match && match[1] && match[2]) {
66
- const num = match[1];
67
- const unit = match[2] as "h" | "d" | "w" | "m";
68
- const n = Number(num);
69
- const now = new Date();
70
- now.setSeconds(0, 0); // Normalize to start of minute for predictability
71
- switch (unit) {
72
- case "h":
73
- now.setHours(now.getHours() + n);
74
- break;
75
- case "d":
76
- now.setDate(now.getDate() + n);
77
- break;
78
- case "w":
79
- now.setDate(now.getDate() + n * 7);
80
- break;
81
- case "m":
82
- now.setMonth(now.getMonth() + n);
83
- break;
84
- }
85
- return formatLocalDate(now);
86
- }
87
- throw new Error(`Invalid relative date: ${input}. Use format like +7d, +2w, +1m`);
88
- }
89
-
90
- // Validate YYYY-MM-DD format
91
- const dateMatch = input.match(/^(\d{4})-(\d{2})-(\d{2})$/);
92
- if (dateMatch) {
93
- const [, year, month, day] = dateMatch;
94
- const y = parseInt(year!, 10);
95
- const m = parseInt(month!, 10);
96
- const d = parseInt(day!, 10);
97
- const date = new Date(y, m - 1, d);
98
- if (date.getFullYear() === y && date.getMonth() === m - 1 && date.getDate() === d) {
99
- return input;
100
- }
101
- }
102
-
103
- throw new Error(`Invalid date format: ${input}. Use YYYY-MM-DD or relative like +7d`);
104
- }
105
-
106
- /**
107
- * Parse an estimate string (number of minutes/hours/etc. depending on convention).
108
- */
109
- export function parseEstimate(input: string | undefined): number | undefined {
110
- if (!input) return undefined;
111
- if (!/^\d+$/.test(input)) {
112
- throw new Error(`Invalid estimate: ${input}. Must be a non-negative number.`);
113
- }
114
- return Number(input);
115
- }