@task-mcp/cli 1.0.20 → 1.0.22
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 +3 -1
- package/dist/index.js +818 -0
- package/package.json +7 -5
- package/src/__tests__/ansi.test.ts +0 -221
- package/src/__tests__/dashboard.test.ts +0 -226
- package/src/__tests__/inbox.test.ts +0 -307
- package/src/__tests__/index.test.ts +0 -140
- package/src/__tests__/list.test.ts +0 -347
- package/src/__tests__/storage.test.ts +0 -271
- package/src/ansi.ts +0 -50
- package/src/commands/dashboard.ts +0 -92
- package/src/commands/inbox.ts +0 -229
- package/src/commands/list.ts +0 -106
- package/src/constants.ts +0 -59
- package/src/index.ts +0 -277
- package/src/interactive.ts +0 -254
- package/src/storage.ts +0 -221
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
describe("inbox commands - unit tests", () => {
|
|
4
|
-
describe("inbox item status handling", () => {
|
|
5
|
-
type InboxStatus = "pending" | "promoted" | "discarded";
|
|
6
|
-
|
|
7
|
-
test("pending is the default status", () => {
|
|
8
|
-
const defaultStatus: InboxStatus = "pending";
|
|
9
|
-
expect(defaultStatus).toBe("pending");
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test("all valid statuses are supported", () => {
|
|
13
|
-
const validStatuses: InboxStatus[] = ["pending", "promoted", "discarded"];
|
|
14
|
-
|
|
15
|
-
expect(validStatuses).toContain("pending");
|
|
16
|
-
expect(validStatuses).toContain("promoted");
|
|
17
|
-
expect(validStatuses).toContain("discarded");
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
describe("content parsing", () => {
|
|
22
|
-
// Test the tag extraction logic from parseInboxInput
|
|
23
|
-
test("extracts hashtags from content", () => {
|
|
24
|
-
const content = "Test idea #tag1 with #tag2 multiple tags";
|
|
25
|
-
const tagRegex = /#(\w+)/g;
|
|
26
|
-
const tags: string[] = [];
|
|
27
|
-
let match;
|
|
28
|
-
while ((match = tagRegex.exec(content)) !== null) {
|
|
29
|
-
tags.push(match[1]!);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
expect(tags).toContain("tag1");
|
|
33
|
-
expect(tags).toContain("tag2");
|
|
34
|
-
expect(tags.length).toBe(2);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("handles content without tags", () => {
|
|
38
|
-
const content = "Simple idea without any tags";
|
|
39
|
-
const tagRegex = /#(\w+)/g;
|
|
40
|
-
const tags: string[] = [];
|
|
41
|
-
let match;
|
|
42
|
-
while ((match = tagRegex.exec(content)) !== null) {
|
|
43
|
-
tags.push(match[1]!);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
expect(tags.length).toBe(0);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("handles empty content", () => {
|
|
50
|
-
const content = "";
|
|
51
|
-
const tagRegex = /#(\w+)/g;
|
|
52
|
-
const tags: string[] = [];
|
|
53
|
-
let match;
|
|
54
|
-
while ((match = tagRegex.exec(content)) !== null) {
|
|
55
|
-
tags.push(match[1]!);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
expect(tags.length).toBe(0);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("extracts tags with alphanumeric characters", () => {
|
|
62
|
-
const content = "Idea #feature123 and #bug456";
|
|
63
|
-
const tagRegex = /#(\w+)/g;
|
|
64
|
-
const tags: string[] = [];
|
|
65
|
-
let match;
|
|
66
|
-
while ((match = tagRegex.exec(content)) !== null) {
|
|
67
|
-
tags.push(match[1]!);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
expect(tags).toContain("feature123");
|
|
71
|
-
expect(tags).toContain("bug456");
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe("title truncation for promote", () => {
|
|
76
|
-
test("truncates long content to 100 characters for title", () => {
|
|
77
|
-
const longContent = "A".repeat(150);
|
|
78
|
-
const title = longContent.slice(0, 100);
|
|
79
|
-
|
|
80
|
-
expect(title.length).toBe(100);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("keeps short content as-is for title", () => {
|
|
84
|
-
const shortContent = "Short inbox idea";
|
|
85
|
-
const title = shortContent.slice(0, 100);
|
|
86
|
-
|
|
87
|
-
expect(title).toBe(shortContent);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
describe("priority validation", () => {
|
|
92
|
-
type Priority = "critical" | "high" | "medium" | "low";
|
|
93
|
-
|
|
94
|
-
test("all valid priorities are recognized", () => {
|
|
95
|
-
const priorities: Priority[] = ["critical", "high", "medium", "low"];
|
|
96
|
-
|
|
97
|
-
expect(priorities.length).toBe(4);
|
|
98
|
-
expect(priorities).toContain("critical");
|
|
99
|
-
expect(priorities).toContain("high");
|
|
100
|
-
expect(priorities).toContain("medium");
|
|
101
|
-
expect(priorities).toContain("low");
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("default priority is medium", () => {
|
|
105
|
-
const defaultPriority: Priority = "medium";
|
|
106
|
-
expect(defaultPriority).toBe("medium");
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
describe("date formatting", () => {
|
|
111
|
-
test("formats ISO date string for display", () => {
|
|
112
|
-
const isoDate = "2024-12-25T10:30:00Z";
|
|
113
|
-
const date = new Date(isoDate);
|
|
114
|
-
const formatted = date.toLocaleDateString();
|
|
115
|
-
|
|
116
|
-
expect(formatted.length).toBeGreaterThan(0);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("formats full timestamp for detailed view", () => {
|
|
120
|
-
const isoDate = "2024-12-25T10:30:00Z";
|
|
121
|
-
const date = new Date(isoDate);
|
|
122
|
-
const formatted = date.toLocaleString();
|
|
123
|
-
|
|
124
|
-
expect(formatted.length).toBeGreaterThan(0);
|
|
125
|
-
// Should include both date and time components
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe("filtering logic", () => {
|
|
130
|
-
interface InboxItem {
|
|
131
|
-
id: string;
|
|
132
|
-
status: "pending" | "promoted" | "discarded";
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
test("filters by pending status", () => {
|
|
136
|
-
const items: InboxItem[] = [
|
|
137
|
-
{ id: "1", status: "pending" },
|
|
138
|
-
{ id: "2", status: "promoted" },
|
|
139
|
-
{ id: "3", status: "pending" },
|
|
140
|
-
];
|
|
141
|
-
|
|
142
|
-
const filtered = items.filter((i) => i.status === "pending");
|
|
143
|
-
|
|
144
|
-
expect(filtered.length).toBe(2);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("filters by promoted status", () => {
|
|
148
|
-
const items: InboxItem[] = [
|
|
149
|
-
{ id: "1", status: "pending" },
|
|
150
|
-
{ id: "2", status: "promoted" },
|
|
151
|
-
{ id: "3", status: "promoted" },
|
|
152
|
-
];
|
|
153
|
-
|
|
154
|
-
const filtered = items.filter((i) => i.status === "promoted");
|
|
155
|
-
|
|
156
|
-
expect(filtered.length).toBe(2);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test("returns all items when no filter applied", () => {
|
|
160
|
-
const items: InboxItem[] = [
|
|
161
|
-
{ id: "1", status: "pending" },
|
|
162
|
-
{ id: "2", status: "promoted" },
|
|
163
|
-
{ id: "3", status: "discarded" },
|
|
164
|
-
];
|
|
165
|
-
|
|
166
|
-
expect(items.length).toBe(3);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
describe("promoted item handling", () => {
|
|
171
|
-
test("tracks promotedToTaskId when promoted", () => {
|
|
172
|
-
const item = {
|
|
173
|
-
id: "inbox_123",
|
|
174
|
-
status: "promoted" as const,
|
|
175
|
-
promotedToTaskId: "task_456",
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
expect(item.promotedToTaskId).toBe("task_456");
|
|
179
|
-
expect(item.status).toBe("promoted");
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test("prevents double promotion", () => {
|
|
183
|
-
const item = {
|
|
184
|
-
id: "inbox_123",
|
|
185
|
-
status: "promoted" as const,
|
|
186
|
-
promotedToTaskId: "task_456",
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const canPromote = item.status !== "promoted";
|
|
190
|
-
|
|
191
|
-
expect(canPromote).toBe(false);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test("allows promotion of pending items", () => {
|
|
195
|
-
const item: {
|
|
196
|
-
id: string;
|
|
197
|
-
status: "pending" | "promoted" | "discarded";
|
|
198
|
-
promotedToTaskId?: string;
|
|
199
|
-
} = {
|
|
200
|
-
id: "inbox_123",
|
|
201
|
-
status: "pending",
|
|
202
|
-
promotedToTaskId: undefined,
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const canPromote = item.status !== "promoted";
|
|
206
|
-
|
|
207
|
-
expect(canPromote).toBe(true);
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
describe("inbox commands - exports", () => {
|
|
213
|
-
test("inboxAddCmd is exported", async () => {
|
|
214
|
-
const { inboxAddCmd } = await import("../commands/inbox.js");
|
|
215
|
-
expect(typeof inboxAddCmd).toBe("function");
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test("inboxListCmd is exported", async () => {
|
|
219
|
-
const { inboxListCmd } = await import("../commands/inbox.js");
|
|
220
|
-
expect(typeof inboxListCmd).toBe("function");
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
test("inboxGetCmd is exported", async () => {
|
|
224
|
-
const { inboxGetCmd } = await import("../commands/inbox.js");
|
|
225
|
-
expect(typeof inboxGetCmd).toBe("function");
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
test("inboxPromoteCmd is exported", async () => {
|
|
229
|
-
const { inboxPromoteCmd } = await import("../commands/inbox.js");
|
|
230
|
-
expect(typeof inboxPromoteCmd).toBe("function");
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
test("inboxDiscardCmd is exported", async () => {
|
|
234
|
-
const { inboxDiscardCmd } = await import("../commands/inbox.js");
|
|
235
|
-
expect(typeof inboxDiscardCmd).toBe("function");
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
test("inboxDeleteCmd is exported", async () => {
|
|
239
|
-
const { inboxDeleteCmd } = await import("../commands/inbox.js");
|
|
240
|
-
expect(typeof inboxDeleteCmd).toBe("function");
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test("inboxCountCmd is exported", async () => {
|
|
244
|
-
const { inboxCountCmd } = await import("../commands/inbox.js");
|
|
245
|
-
expect(typeof inboxCountCmd).toBe("function");
|
|
246
|
-
});
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
describe("inbox options interfaces", () => {
|
|
250
|
-
test("InboxAddOptions structure", () => {
|
|
251
|
-
const options: { content: string; source?: string } = {
|
|
252
|
-
content: "Test content #tag",
|
|
253
|
-
source: "cli",
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
expect(options.content).toBeDefined();
|
|
257
|
-
expect(options.source).toBe("cli");
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test("InboxListOptions structure", () => {
|
|
261
|
-
const options: {
|
|
262
|
-
all?: boolean;
|
|
263
|
-
status?: "pending" | "promoted" | "discarded";
|
|
264
|
-
} = {
|
|
265
|
-
all: false,
|
|
266
|
-
status: "pending",
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
expect(options.all).toBe(false);
|
|
270
|
-
expect(options.status).toBe("pending");
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test("InboxPromoteOptions structure", () => {
|
|
274
|
-
const options: {
|
|
275
|
-
itemId: string;
|
|
276
|
-
projectId?: string;
|
|
277
|
-
title?: string;
|
|
278
|
-
priority?: "critical" | "high" | "medium" | "low";
|
|
279
|
-
} = {
|
|
280
|
-
itemId: "inbox_123",
|
|
281
|
-
projectId: "proj_abc",
|
|
282
|
-
title: "Promoted Task",
|
|
283
|
-
priority: "high",
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
expect(options.itemId).toBe("inbox_123");
|
|
287
|
-
expect(options.projectId).toBe("proj_abc");
|
|
288
|
-
expect(options.title).toBe("Promoted Task");
|
|
289
|
-
expect(options.priority).toBe("high");
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
test("InboxPromoteOptions with minimal fields", () => {
|
|
293
|
-
const options: {
|
|
294
|
-
itemId: string;
|
|
295
|
-
projectId?: string;
|
|
296
|
-
title?: string;
|
|
297
|
-
priority?: "critical" | "high" | "medium" | "low";
|
|
298
|
-
} = {
|
|
299
|
-
itemId: "inbox_123",
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
expect(options.itemId).toBe("inbox_123");
|
|
303
|
-
expect(options.projectId).toBeUndefined();
|
|
304
|
-
expect(options.title).toBeUndefined();
|
|
305
|
-
expect(options.priority).toBeUndefined();
|
|
306
|
-
});
|
|
307
|
-
});
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { parseArgs } from "../index.js";
|
|
3
|
-
|
|
4
|
-
describe("parseArgs", () => {
|
|
5
|
-
// Helper: simulate argv with bun and script path
|
|
6
|
-
const argv = (...args: string[]) => ["bun", "script.ts", ...args];
|
|
7
|
-
|
|
8
|
-
describe("command parsing", () => {
|
|
9
|
-
test("defaults to dashboard when no command", () => {
|
|
10
|
-
const result = parseArgs(argv());
|
|
11
|
-
expect(result.command).toBe("dashboard");
|
|
12
|
-
expect(result.args).toEqual([]);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test("parses command from first positional arg", () => {
|
|
16
|
-
const result = parseArgs(argv("list"));
|
|
17
|
-
expect(result.command).toBe("list");
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("parses command and additional args", () => {
|
|
21
|
-
const result = parseArgs(argv("dashboard", "proj_123"));
|
|
22
|
-
expect(result.command).toBe("dashboard");
|
|
23
|
-
expect(result.args).toEqual(["proj_123"]);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("parses multiple positional args", () => {
|
|
27
|
-
const result = parseArgs(argv("cmd", "arg1", "arg2"));
|
|
28
|
-
expect(result.command).toBe("cmd");
|
|
29
|
-
expect(result.args).toEqual(["arg1", "arg2"]);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe("long flags (--flag)", () => {
|
|
34
|
-
test("parses --flag as boolean true", () => {
|
|
35
|
-
const result = parseArgs(argv("list", "--all"));
|
|
36
|
-
expect(result.flags.all).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("parses --flag=value", () => {
|
|
40
|
-
const result = parseArgs(argv("list", "--status=pending"));
|
|
41
|
-
expect(result.flags.status).toBe("pending");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("parses --flag value (space separated)", () => {
|
|
45
|
-
const result = parseArgs(argv("list", "--status", "pending"));
|
|
46
|
-
expect(result.flags.status).toBe("pending");
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("parses multiple flags", () => {
|
|
50
|
-
const result = parseArgs(argv("list", "--status", "pending", "--priority", "high", "--all"));
|
|
51
|
-
expect(result.flags.status).toBe("pending");
|
|
52
|
-
expect(result.flags.priority).toBe("high");
|
|
53
|
-
expect(result.flags.all).toBe(true);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("flag value starting with dash is treated as boolean", () => {
|
|
57
|
-
const result = parseArgs(argv("list", "--flag", "--other"));
|
|
58
|
-
expect(result.flags.flag).toBe(true);
|
|
59
|
-
expect(result.flags.other).toBe(true);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe("short flags (-f)", () => {
|
|
64
|
-
test("parses -f as boolean true", () => {
|
|
65
|
-
const result = parseArgs(argv("list", "-a"));
|
|
66
|
-
expect(result.flags.a).toBe(true);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("parses -f value (space separated)", () => {
|
|
70
|
-
const result = parseArgs(argv("list", "-s", "pending"));
|
|
71
|
-
expect(result.flags.s).toBe("pending");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test("parses multiple short flags", () => {
|
|
75
|
-
const result = parseArgs(argv("list", "-s", "pending", "-p", "high"));
|
|
76
|
-
expect(result.flags.s).toBe("pending");
|
|
77
|
-
expect(result.flags.p).toBe("high");
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe("mixed flags and args", () => {
|
|
82
|
-
test("boolean flag before command consumes next arg as value", () => {
|
|
83
|
-
// Note: --all list is parsed as --all=list (list becomes value of all)
|
|
84
|
-
const result = parseArgs(argv("--all", "list"));
|
|
85
|
-
expect(result.command).toBe("dashboard"); // default, since 'list' was consumed
|
|
86
|
-
expect(result.flags.all).toBe("list");
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("explicit boolean flag with command works", () => {
|
|
90
|
-
// Use --all without value by putting flag after command
|
|
91
|
-
const result = parseArgs(argv("list", "--all"));
|
|
92
|
-
expect(result.command).toBe("list");
|
|
93
|
-
expect(result.flags.all).toBe(true);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("flags after args are parsed correctly", () => {
|
|
97
|
-
const result = parseArgs(argv("list", "proj_123", "--all"));
|
|
98
|
-
expect(result.command).toBe("list");
|
|
99
|
-
expect(result.args).toEqual(["proj_123"]);
|
|
100
|
-
expect(result.flags.all).toBe(true);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("complex mixed parsing", () => {
|
|
104
|
-
const result = parseArgs(argv(
|
|
105
|
-
"dashboard",
|
|
106
|
-
"proj_abc",
|
|
107
|
-
"--status=pending",
|
|
108
|
-
"-v",
|
|
109
|
-
"--priority", "high"
|
|
110
|
-
));
|
|
111
|
-
expect(result.command).toBe("dashboard");
|
|
112
|
-
expect(result.args).toEqual(["proj_abc"]);
|
|
113
|
-
expect(result.flags.status).toBe("pending");
|
|
114
|
-
expect(result.flags.v).toBe(true);
|
|
115
|
-
expect(result.flags.priority).toBe("high");
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
describe("edge cases", () => {
|
|
120
|
-
test("handles empty argv (just bun and script)", () => {
|
|
121
|
-
const result = parseArgs(["bun", "script.ts"]);
|
|
122
|
-
expect(result.command).toBe("dashboard");
|
|
123
|
-
expect(result.args).toEqual([]);
|
|
124
|
-
expect(result.flags).toEqual({});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("handles flag with empty value via =", () => {
|
|
128
|
-
const result = parseArgs(argv("list", "--status="));
|
|
129
|
-
expect(result.flags.status).toBe("");
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("handles help and version flags", () => {
|
|
133
|
-
expect(parseArgs(argv("--help")).command).toBe("dashboard");
|
|
134
|
-
expect(parseArgs(argv("--help")).flags.help).toBe(true);
|
|
135
|
-
expect(parseArgs(argv("-h")).flags.h).toBe(true);
|
|
136
|
-
expect(parseArgs(argv("--version")).flags.version).toBe(true);
|
|
137
|
-
expect(parseArgs(argv("-v")).flags.v).toBe(true);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
});
|