@task-mcp/cli 1.0.4 → 1.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/README.md +127 -35
- package/package.json +1 -1
- package/src/__tests__/ansi.test.ts +221 -0
- package/src/__tests__/index.test.ts +140 -0
- package/src/__tests__/storage.test.ts +271 -0
- package/src/ansi.ts +1 -1
- package/src/commands/dashboard.ts +371 -40
- package/src/commands/inbox.ts +269 -0
- package/src/commands/list.ts +1 -1
- package/src/index.ts +124 -4
- package/src/interactive.ts +400 -0
- package/src/storage.ts +367 -14
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Inbox Commands
|
|
3
|
+
* Quick capture and management of ideas
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { c } from "../ansi.js";
|
|
7
|
+
import { InboxStore } from "../../../mcp-server/src/storage/inbox-store.js";
|
|
8
|
+
import { TaskStore } from "../../../mcp-server/src/storage/task-store.js";
|
|
9
|
+
import { ProjectStore } from "../../../mcp-server/src/storage/project-store.js";
|
|
10
|
+
import { parseInboxInput } from "../../../shared/src/utils/natural-language.js";
|
|
11
|
+
|
|
12
|
+
const inboxStore = new InboxStore();
|
|
13
|
+
const taskStore = new TaskStore();
|
|
14
|
+
const projectStore = new ProjectStore();
|
|
15
|
+
|
|
16
|
+
export interface InboxAddOptions {
|
|
17
|
+
content: string;
|
|
18
|
+
source?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface InboxListOptions {
|
|
22
|
+
all?: boolean;
|
|
23
|
+
status?: "pending" | "promoted" | "discarded";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface InboxPromoteOptions {
|
|
27
|
+
itemId: string;
|
|
28
|
+
projectId?: string;
|
|
29
|
+
title?: string;
|
|
30
|
+
priority?: "critical" | "high" | "medium" | "low";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Add item to inbox with natural language parsing
|
|
35
|
+
*/
|
|
36
|
+
export async function inboxAddCmd(options: InboxAddOptions): Promise<void> {
|
|
37
|
+
const parsed = parseInboxInput(options.content);
|
|
38
|
+
|
|
39
|
+
const item = await inboxStore.add({
|
|
40
|
+
content: parsed.content,
|
|
41
|
+
source: options.source ?? "cli",
|
|
42
|
+
tags: parsed.tags,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
console.log();
|
|
46
|
+
console.log(c.green("✓") + " Captured to inbox");
|
|
47
|
+
console.log();
|
|
48
|
+
console.log(" " + c.dim("ID:") + " " + c.cyan(item.id));
|
|
49
|
+
console.log(" " + c.dim("Content:") + " " + item.content);
|
|
50
|
+
if (parsed.tags?.length) {
|
|
51
|
+
console.log(" " + c.dim("Tags:") + " " + parsed.tags.map((t) => c.yellow("#" + t)).join(" "));
|
|
52
|
+
}
|
|
53
|
+
console.log();
|
|
54
|
+
console.log(c.dim("Use 'task inbox promote " + item.id + "' to convert to task"));
|
|
55
|
+
console.log();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* List inbox items
|
|
60
|
+
*/
|
|
61
|
+
export async function inboxListCmd(options: InboxListOptions): Promise<void> {
|
|
62
|
+
const status = options.all ? undefined : (options.status ?? "pending");
|
|
63
|
+
const items = await inboxStore.list({ status });
|
|
64
|
+
|
|
65
|
+
console.log();
|
|
66
|
+
|
|
67
|
+
if (items.length === 0) {
|
|
68
|
+
console.log(c.dim(status ? `No ${status} items in inbox.` : "Inbox is empty."));
|
|
69
|
+
console.log();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Header
|
|
74
|
+
const title = status ? `Inbox (${status})` : "Inbox (all)";
|
|
75
|
+
console.log(c.bold(c.cyan(title)) + c.dim(` - ${items.length} items`));
|
|
76
|
+
console.log(c.dim("─".repeat(50)));
|
|
77
|
+
console.log();
|
|
78
|
+
|
|
79
|
+
for (const item of items) {
|
|
80
|
+
const statusIcon =
|
|
81
|
+
item.status === "pending" ? c.yellow("○") :
|
|
82
|
+
item.status === "promoted" ? c.green("✓") :
|
|
83
|
+
c.dim("×");
|
|
84
|
+
|
|
85
|
+
const date = new Date(item.capturedAt).toLocaleDateString();
|
|
86
|
+
const tags = item.tags?.length
|
|
87
|
+
? " " + item.tags.map((t) => c.dim("#" + t)).join(" ")
|
|
88
|
+
: "";
|
|
89
|
+
|
|
90
|
+
console.log(`${statusIcon} ${c.cyan(item.id)} ${c.dim("(" + date + ")")}`);
|
|
91
|
+
console.log(` ${item.content}${tags}`);
|
|
92
|
+
|
|
93
|
+
if (item.promotedToTaskId) {
|
|
94
|
+
console.log(` ${c.dim("→ Task:")} ${c.green(item.promotedToTaskId)}`);
|
|
95
|
+
}
|
|
96
|
+
console.log();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Show inbox item details
|
|
102
|
+
*/
|
|
103
|
+
export async function inboxGetCmd(itemId: string): Promise<void> {
|
|
104
|
+
const item = await inboxStore.get(itemId);
|
|
105
|
+
|
|
106
|
+
if (!item) {
|
|
107
|
+
console.log();
|
|
108
|
+
console.log(c.error(`Inbox item not found: ${itemId}`));
|
|
109
|
+
console.log();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log();
|
|
114
|
+
console.log(c.bold(c.cyan("Inbox Item")));
|
|
115
|
+
console.log(c.dim("─".repeat(40)));
|
|
116
|
+
console.log();
|
|
117
|
+
console.log(" " + c.dim("ID:") + " " + item.id);
|
|
118
|
+
console.log(" " + c.dim("Status:") + " " + formatStatus(item.status));
|
|
119
|
+
console.log(" " + c.dim("Captured:") + " " + new Date(item.capturedAt).toLocaleString());
|
|
120
|
+
if (item.source) {
|
|
121
|
+
console.log(" " + c.dim("Source:") + " " + item.source);
|
|
122
|
+
}
|
|
123
|
+
if (item.tags?.length) {
|
|
124
|
+
console.log(" " + c.dim("Tags:") + " " + item.tags.map((t) => c.yellow("#" + t)).join(" "));
|
|
125
|
+
}
|
|
126
|
+
console.log();
|
|
127
|
+
console.log(c.bold("Content:"));
|
|
128
|
+
console.log(" " + item.content);
|
|
129
|
+
|
|
130
|
+
if (item.promotedToTaskId) {
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(c.green("→ Promoted to: " + item.promotedToTaskId));
|
|
133
|
+
}
|
|
134
|
+
console.log();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Promote inbox item to task
|
|
139
|
+
*/
|
|
140
|
+
export async function inboxPromoteCmd(options: InboxPromoteOptions): Promise<void> {
|
|
141
|
+
const item = await inboxStore.get(options.itemId);
|
|
142
|
+
|
|
143
|
+
if (!item) {
|
|
144
|
+
console.log();
|
|
145
|
+
console.log(c.error(`Inbox item not found: ${options.itemId}`));
|
|
146
|
+
console.log();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (item.status === "promoted") {
|
|
151
|
+
console.log();
|
|
152
|
+
console.log(c.warning(`Item already promoted to: ${item.promotedToTaskId}`));
|
|
153
|
+
console.log();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Get project
|
|
158
|
+
let projectId = options.projectId;
|
|
159
|
+
if (!projectId) {
|
|
160
|
+
const projects = await projectStore.list();
|
|
161
|
+
if (projects.length === 0) {
|
|
162
|
+
console.log();
|
|
163
|
+
console.log(c.error("No projects found. Create a project first."));
|
|
164
|
+
console.log();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// Use first project if not specified
|
|
168
|
+
projectId = projects[0]!.id;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const project = await projectStore.get(projectId);
|
|
172
|
+
if (!project) {
|
|
173
|
+
console.log();
|
|
174
|
+
console.log(c.error(`Project not found: ${projectId}`));
|
|
175
|
+
console.log();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Create task
|
|
180
|
+
const task = await taskStore.create(projectId, {
|
|
181
|
+
title: options.title ?? item.content.slice(0, 100),
|
|
182
|
+
description: item.content,
|
|
183
|
+
priority: options.priority ?? "medium",
|
|
184
|
+
tags: item.tags,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Mark as promoted
|
|
188
|
+
await inboxStore.promote(options.itemId, task.id);
|
|
189
|
+
|
|
190
|
+
console.log();
|
|
191
|
+
console.log(c.green("✓") + " Promoted to task");
|
|
192
|
+
console.log();
|
|
193
|
+
console.log(" " + c.dim("Task ID:") + " " + c.cyan(task.id));
|
|
194
|
+
console.log(" " + c.dim("Title:") + " " + task.title);
|
|
195
|
+
console.log(" " + c.dim("Project:") + " " + project.name);
|
|
196
|
+
console.log(" " + c.dim("Priority:") + " " + formatPriority(task.priority));
|
|
197
|
+
console.log();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Discard inbox item
|
|
202
|
+
*/
|
|
203
|
+
export async function inboxDiscardCmd(itemId: string): Promise<void> {
|
|
204
|
+
const item = await inboxStore.discard(itemId);
|
|
205
|
+
|
|
206
|
+
if (!item) {
|
|
207
|
+
console.log();
|
|
208
|
+
console.log(c.error(`Inbox item not found: ${itemId}`));
|
|
209
|
+
console.log();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log();
|
|
214
|
+
console.log(c.dim("×") + " Discarded: " + item.content.slice(0, 50) + "...");
|
|
215
|
+
console.log();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Delete inbox item permanently
|
|
220
|
+
*/
|
|
221
|
+
export async function inboxDeleteCmd(itemId: string): Promise<void> {
|
|
222
|
+
const deleted = await inboxStore.delete(itemId);
|
|
223
|
+
|
|
224
|
+
if (!deleted) {
|
|
225
|
+
console.log();
|
|
226
|
+
console.log(c.error(`Inbox item not found: ${itemId}`));
|
|
227
|
+
console.log();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
console.log();
|
|
232
|
+
console.log(c.green("✓") + " Deleted: " + itemId);
|
|
233
|
+
console.log();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Show inbox count
|
|
238
|
+
*/
|
|
239
|
+
export async function inboxCountCmd(): Promise<void> {
|
|
240
|
+
const count = await inboxStore.getPendingCount();
|
|
241
|
+
|
|
242
|
+
console.log();
|
|
243
|
+
if (count > 0) {
|
|
244
|
+
console.log(c.yellow("📥") + ` ${count} pending item${count === 1 ? "" : "s"} in inbox`);
|
|
245
|
+
} else {
|
|
246
|
+
console.log(c.dim("Inbox is empty"));
|
|
247
|
+
}
|
|
248
|
+
console.log();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Helper functions
|
|
252
|
+
function formatStatus(status: string): string {
|
|
253
|
+
switch (status) {
|
|
254
|
+
case "pending": return c.yellow("pending");
|
|
255
|
+
case "promoted": return c.green("promoted");
|
|
256
|
+
case "discarded": return c.dim("discarded");
|
|
257
|
+
default: return status;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function formatPriority(priority: string): string {
|
|
262
|
+
switch (priority) {
|
|
263
|
+
case "critical": return c.red("critical");
|
|
264
|
+
case "high": return c.yellow("high");
|
|
265
|
+
case "medium": return c.blue("medium");
|
|
266
|
+
case "low": return c.dim("low");
|
|
267
|
+
default: return priority;
|
|
268
|
+
}
|
|
269
|
+
}
|
package/src/commands/list.ts
CHANGED
|
@@ -85,7 +85,7 @@ export async function listTasksCmd(options: {
|
|
|
85
85
|
{ header: "Due", key: "dueDate", width: 12, format: (v) => formatDate(v as string) },
|
|
86
86
|
];
|
|
87
87
|
|
|
88
|
-
console.log(table(tasks, columns));
|
|
88
|
+
console.log(table(tasks as unknown as Record<string, unknown>[], columns));
|
|
89
89
|
console.log();
|
|
90
90
|
}
|
|
91
91
|
|
package/src/index.ts
CHANGED
|
@@ -16,16 +16,26 @@
|
|
|
16
16
|
import { c, banner } from "./ansi.js";
|
|
17
17
|
import { dashboard } from "./commands/dashboard.js";
|
|
18
18
|
import { listTasksCmd, listProjectsCmd } from "./commands/list.js";
|
|
19
|
+
import { startInteractive } from "./interactive.js";
|
|
20
|
+
import {
|
|
21
|
+
inboxAddCmd,
|
|
22
|
+
inboxListCmd,
|
|
23
|
+
inboxGetCmd,
|
|
24
|
+
inboxPromoteCmd,
|
|
25
|
+
inboxDiscardCmd,
|
|
26
|
+
inboxDeleteCmd,
|
|
27
|
+
inboxCountCmd,
|
|
28
|
+
} from "./commands/inbox.js";
|
|
19
29
|
|
|
20
|
-
const VERSION = "1.0.4";
|
|
30
|
+
export const VERSION = "1.0.4";
|
|
21
31
|
|
|
22
|
-
interface ParsedArgs {
|
|
32
|
+
export interface ParsedArgs {
|
|
23
33
|
command: string;
|
|
24
34
|
args: string[];
|
|
25
35
|
flags: Record<string, string | boolean>;
|
|
26
36
|
}
|
|
27
37
|
|
|
28
|
-
function parseArgs(argv: string[]): ParsedArgs {
|
|
38
|
+
export function parseArgs(argv: string[]): ParsedArgs {
|
|
29
39
|
const args = argv.slice(2); // Skip bun and script path
|
|
30
40
|
const flags: Record<string, string | boolean> = {};
|
|
31
41
|
const positional: string[] = [];
|
|
@@ -75,10 +85,23 @@ ${c.yellow("COMMANDS")}
|
|
|
75
85
|
${c.cyan("ls")} Alias for list
|
|
76
86
|
${c.cyan("projects")} List all projects
|
|
77
87
|
${c.cyan("p")} Alias for projects
|
|
88
|
+
${c.cyan("inbox")} <subcommand> Manage inbox (add/list/get/promote/discard)
|
|
89
|
+
${c.cyan("interactive")} Start interactive mode
|
|
90
|
+
${c.cyan("i")} Alias for interactive
|
|
78
91
|
${c.cyan("help")} Show this help message
|
|
79
92
|
${c.cyan("version")} Show version
|
|
80
93
|
|
|
94
|
+
${c.yellow("INBOX SUBCOMMANDS")}
|
|
95
|
+
${c.cyan("inbox")} "content" Quick capture to inbox
|
|
96
|
+
${c.cyan("inbox add")} "content" Add item with natural language
|
|
97
|
+
${c.cyan("inbox list")} List pending items
|
|
98
|
+
${c.cyan("inbox get")} <id> Show item details
|
|
99
|
+
${c.cyan("inbox promote")} <id> Promote to task
|
|
100
|
+
${c.cyan("inbox discard")} <id> Discard item
|
|
101
|
+
${c.cyan("inbox count")} Show pending count
|
|
102
|
+
|
|
81
103
|
${c.yellow("OPTIONS")}
|
|
104
|
+
${c.cyan("-i, --interactive")} Start interactive mode
|
|
82
105
|
${c.cyan("--status")} <status> Filter tasks by status (pending,in_progress,blocked,completed,cancelled)
|
|
83
106
|
${c.cyan("--priority")} <priority> Filter tasks by priority (critical,high,medium,low)
|
|
84
107
|
${c.cyan("--all")} Include completed/cancelled tasks
|
|
@@ -102,9 +125,94 @@ function showVersion(): void {
|
|
|
102
125
|
console.log(`task-mcp-cli v${VERSION}`);
|
|
103
126
|
}
|
|
104
127
|
|
|
128
|
+
async function handleInboxCommand(
|
|
129
|
+
args: string[],
|
|
130
|
+
flags: Record<string, string | boolean>
|
|
131
|
+
): Promise<void> {
|
|
132
|
+
const subcommand = args[0];
|
|
133
|
+
|
|
134
|
+
// If first arg is not a subcommand, treat it as content for quick capture
|
|
135
|
+
const subcommands = ["add", "list", "ls", "get", "promote", "discard", "delete", "count"];
|
|
136
|
+
if (subcommand && !subcommands.includes(subcommand)) {
|
|
137
|
+
// Quick capture: task inbox "content here"
|
|
138
|
+
await inboxAddCmd({ content: args.join(" ") });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
switch (subcommand) {
|
|
143
|
+
case "add":
|
|
144
|
+
if (!args[1]) {
|
|
145
|
+
console.log(c.error("Usage: task inbox add \"content\""));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
await inboxAddCmd({ content: args.slice(1).join(" ") });
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
case "list":
|
|
152
|
+
case "ls":
|
|
153
|
+
await inboxListCmd({
|
|
154
|
+
all: flags.all === true,
|
|
155
|
+
status: flags.status as "pending" | "promoted" | "discarded" | undefined,
|
|
156
|
+
});
|
|
157
|
+
break;
|
|
158
|
+
|
|
159
|
+
case "get":
|
|
160
|
+
if (!args[1]) {
|
|
161
|
+
console.log(c.error("Usage: task inbox get <item-id>"));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
await inboxGetCmd(args[1]);
|
|
165
|
+
break;
|
|
166
|
+
|
|
167
|
+
case "promote":
|
|
168
|
+
if (!args[1]) {
|
|
169
|
+
console.log(c.error("Usage: task inbox promote <item-id> [--project <id>] [--priority <p>]"));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
await inboxPromoteCmd({
|
|
173
|
+
itemId: args[1],
|
|
174
|
+
projectId: flags.project as string | undefined,
|
|
175
|
+
title: flags.title as string | undefined,
|
|
176
|
+
priority: flags.priority as "critical" | "high" | "medium" | "low" | undefined,
|
|
177
|
+
});
|
|
178
|
+
break;
|
|
179
|
+
|
|
180
|
+
case "discard":
|
|
181
|
+
if (!args[1]) {
|
|
182
|
+
console.log(c.error("Usage: task inbox discard <item-id>"));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
await inboxDiscardCmd(args[1]);
|
|
186
|
+
break;
|
|
187
|
+
|
|
188
|
+
case "delete":
|
|
189
|
+
if (!args[1]) {
|
|
190
|
+
console.log(c.error("Usage: task inbox delete <item-id>"));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
await inboxDeleteCmd(args[1]);
|
|
194
|
+
break;
|
|
195
|
+
|
|
196
|
+
case "count":
|
|
197
|
+
await inboxCountCmd();
|
|
198
|
+
break;
|
|
199
|
+
|
|
200
|
+
default:
|
|
201
|
+
// No subcommand = list pending
|
|
202
|
+
await inboxListCmd({ all: false });
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
105
207
|
async function main(): Promise<void> {
|
|
106
208
|
const { command, args, flags } = parseArgs(process.argv);
|
|
107
209
|
|
|
210
|
+
// Handle -i flag for interactive mode
|
|
211
|
+
if (flags.i === true || flags.interactive === true) {
|
|
212
|
+
await startInteractive();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
108
216
|
try {
|
|
109
217
|
switch (command) {
|
|
110
218
|
case "dashboard":
|
|
@@ -129,6 +237,15 @@ async function main(): Promise<void> {
|
|
|
129
237
|
});
|
|
130
238
|
break;
|
|
131
239
|
|
|
240
|
+
case "interactive":
|
|
241
|
+
case "i":
|
|
242
|
+
await startInteractive();
|
|
243
|
+
break;
|
|
244
|
+
|
|
245
|
+
case "inbox":
|
|
246
|
+
await handleInboxCommand(args, flags);
|
|
247
|
+
break;
|
|
248
|
+
|
|
132
249
|
case "help":
|
|
133
250
|
case "-h":
|
|
134
251
|
case "--help":
|
|
@@ -156,4 +273,7 @@ async function main(): Promise<void> {
|
|
|
156
273
|
}
|
|
157
274
|
}
|
|
158
275
|
|
|
159
|
-
main
|
|
276
|
+
// Only run main when directly executed, not when imported
|
|
277
|
+
if (import.meta.main) {
|
|
278
|
+
main();
|
|
279
|
+
}
|