@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 CHANGED
@@ -24,64 +24,156 @@ bun add -g @task-mcp/cli
24
24
  bunx @task-mcp/cli
25
25
  ```
26
26
 
27
- ## Usage
27
+ ## Quick Start
28
28
 
29
29
  ```bash
30
30
  # Show dashboard (default)
31
31
  task
32
32
 
33
- # Show dashboard for specific project
34
- task dashboard proj_abc123
35
- task d proj_abc123
33
+ # Enter interactive mode
34
+ task -i
35
+ task interactive
36
+
37
+ # Quick capture idea to inbox
38
+ task inbox "Review API design tomorrow"
39
+
40
+ # List pending tasks
41
+ task list --status pending
42
+ ```
43
+
44
+ ## Commands
45
+
46
+ ### Dashboard
47
+
48
+ ```bash
49
+ task # Show dashboard for all projects
50
+ task dashboard # Same as above
51
+ task d # Alias
52
+ task dashboard proj_123 # Show specific project
53
+ ```
36
54
 
37
- # List all tasks
38
- task list
39
- task ls
55
+ ### Interactive Mode
40
56
 
41
- # List tasks with filters
42
- task list --status pending --priority high
57
+ ```bash
58
+ task -i # Start interactive mode
59
+ task --interactive # Same as above
60
+ task interactive # Full command
61
+ task i # Alias
62
+ ```
63
+
64
+ Interactive mode provides a menu-driven interface:
65
+ - **d** - Dashboard view
66
+ - **p** - Projects management
67
+ - **t** - Tasks with filters
68
+ - **a** - Analysis tools
69
+ - **q** - Quick actions
70
+ - **x** - Exit
71
+
72
+ ### Task List
73
+
74
+ ```bash
75
+ task list # List active tasks
76
+ task ls # Alias
77
+ task list --all # Include completed/cancelled
43
78
  task list --status pending,in_progress
44
- task list --all # Include completed/cancelled
79
+ task list --priority critical,high
80
+ ```
45
81
 
46
- # List projects
47
- task projects
48
- task p
49
- task projects --all # Include archived
82
+ ### Projects
50
83
 
51
- # Help & version
52
- task help
53
- task version
84
+ ```bash
85
+ task projects # List active projects
86
+ task p # Alias
87
+ task projects --all # Include archived
54
88
  ```
55
89
 
56
- ## Commands
90
+ ### Inbox
57
91
 
58
- | Command | Alias | Description |
59
- |---------|-------|-------------|
60
- | `dashboard [id]` | `d` | Show project dashboard with stats and task table |
61
- | `list` | `ls` | List tasks with optional filters |
62
- | `projects` | `p` | List all projects |
63
- | `help` | - | Show help message |
64
- | `version` | - | Show version |
92
+ Quick capture ideas and promote them to tasks later.
93
+
94
+ ```bash
95
+ # Quick capture
96
+ task inbox "Fix login bug tomorrow"
97
+ task inbox "Review PR #123 #dev !high"
98
+
99
+ # Subcommands
100
+ task inbox # List pending items
101
+ task inbox list # Same as above
102
+ task inbox list --all # Include promoted/discarded
103
+ task inbox get <id> # Show item details
104
+ task inbox promote <id> # Promote to task
105
+ task inbox discard <id> # Discard item
106
+ task inbox delete <id> # Permanently delete
107
+ task inbox count # Show pending count
108
+ ```
109
+
110
+ **Natural language parsing:**
111
+ - `#tag` - Add tags
112
+ - `!priority` - Set priority (critical, high, medium, low)
113
+ - `@context` - Add context (focus, office, home)
114
+ - Date words: tomorrow, next week, monday, etc.
115
+
116
+ ## Dashboard Widgets
117
+
118
+ The dashboard displays 7 widgets:
119
+
120
+ | Widget | Description |
121
+ |--------|-------------|
122
+ | **Overview** | Progress bar, status counts, priority breakdown |
123
+ | **Schedule** | Overdue, today, this week task counts |
124
+ | **Inbox** | Pending quick capture items |
125
+ | **Work Queue** | Ready tasks, blocked tasks, bottlenecks, next task suggestion |
126
+ | **Critical Path** | CPM analysis, path length, estimated duration |
127
+ | **Analysis** | Complexity scores, tech stack distribution, risk levels |
128
+ | **Hierarchy** | Task tree structure with subtasks |
65
129
 
66
130
  ## Options
67
131
 
68
132
  | Option | Description |
69
133
  |--------|-------------|
70
- | `--status <status>` | Filter by status (pending, in_progress, blocked, completed, cancelled) |
71
- | `--priority <priority>` | Filter by priority (critical, high, medium, low) |
72
- | `--all` | Include completed/cancelled tasks or archived projects |
134
+ | `-i, --interactive` | Start interactive mode |
135
+ | `--status <status>` | Filter by status (comma-separated) |
136
+ | `--priority <priority>` | Filter by priority (comma-separated) |
137
+ | `--all` | Include completed/cancelled/archived |
138
+ | `--project <id>` | Specify project for inbox promote |
73
139
 
74
- ## Dashboard Features
140
+ ### Status Values
141
+ `pending`, `in_progress`, `blocked`, `completed`, `cancelled`
75
142
 
76
- - **Progress Bar**: Visual task completion progress
77
- - **Priority Breakdown**: Tasks grouped by priority level
78
- - **Dependency Metrics**: Ready tasks, blocked tasks, most depended-on task
79
- - **Next Task Suggestion**: AI-powered recommendation for next task
80
- - **Task Table**: Full task list with status, priority, and dependencies
143
+ ### Priority Values
144
+ `critical`, `high`, `medium`, `low`
81
145
 
82
146
  ## Data Location
83
147
 
84
- The CLI reads data from the `.tasks/` directory created by the task-mcp MCP server.
148
+ The CLI reads data from the `.tasks/` directory created by the task-mcp MCP server:
149
+
150
+ ```
151
+ .tasks/
152
+ ├── projects.json # Project metadata
153
+ ├── inbox.json # Quick capture items
154
+ └── <project-id>/
155
+ └── tasks.json # Tasks for each project
156
+ ```
157
+
158
+ ## Examples
159
+
160
+ ```bash
161
+ # View all high-priority blocked tasks
162
+ task list --status blocked --priority high
163
+
164
+ # Start interactive mode for project exploration
165
+ task -i
166
+
167
+ # Capture idea and check inbox
168
+ task inbox "Refactor auth module !critical #backend"
169
+ task inbox list
170
+
171
+ # Promote inbox item to task
172
+ task inbox promote inb_abc123 --project proj_xyz
173
+
174
+ # Show dashboard for specific project
175
+ task d proj_main
176
+ ```
85
177
 
86
178
  ## License
87
179
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@task-mcp/cli",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Zero-dependency CLI for task-mcp with Bun native visualization",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,221 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ color,
4
+ style,
5
+ styled,
6
+ stripAnsi,
7
+ displayWidth,
8
+ pad,
9
+ truncate,
10
+ progressBar,
11
+ hline,
12
+ c,
13
+ } from "../ansi.js";
14
+
15
+ describe("ansi", () => {
16
+ describe("color", () => {
17
+ test("applies color code to text", () => {
18
+ const result = color("hello", "red");
19
+ expect(result).toContain("hello");
20
+ expect(result).toContain("\x1b[31m"); // red color code
21
+ expect(result).toContain("\x1b[0m"); // reset code
22
+ });
23
+
24
+ test("applies different colors", () => {
25
+ expect(color("test", "green")).toContain("\x1b[32m");
26
+ expect(color("test", "blue")).toContain("\x1b[34m");
27
+ expect(color("test", "cyan")).toContain("\x1b[36m");
28
+ });
29
+ });
30
+
31
+ describe("style", () => {
32
+ test("applies bold style", () => {
33
+ const result = style("hello", "bold");
34
+ expect(result).toContain("hello");
35
+ expect(result).toContain("\x1b[1m"); // bold code
36
+ });
37
+
38
+ test("applies dim style", () => {
39
+ const result = style("hello", "dim");
40
+ expect(result).toContain("\x1b[2m"); // dim code
41
+ });
42
+ });
43
+
44
+ describe("styled", () => {
45
+ test("combines color and style", () => {
46
+ const result = styled("hello", "red", "bold");
47
+ expect(result).toContain("hello");
48
+ expect(result).toContain("1;31m"); // bold + red
49
+ });
50
+
51
+ test("works without style", () => {
52
+ const result = styled("hello", "green");
53
+ expect(result).toContain("\x1b[32m");
54
+ });
55
+ });
56
+
57
+ describe("c convenience functions", () => {
58
+ test("c.red applies red color", () => {
59
+ expect(c.red("test")).toContain("\x1b[31m");
60
+ });
61
+
62
+ test("c.green applies green color", () => {
63
+ expect(c.green("test")).toContain("\x1b[32m");
64
+ });
65
+
66
+ test("c.bold applies bold style", () => {
67
+ expect(c.bold("test")).toContain("\x1b[1m");
68
+ });
69
+
70
+ test("semantic colors work", () => {
71
+ expect(c.success("ok")).toContain("\x1b[32m"); // green
72
+ expect(c.error("fail")).toContain("\x1b[31m"); // red
73
+ expect(c.warning("warn")).toContain("\x1b[33m"); // yellow
74
+ });
75
+ });
76
+
77
+ describe("stripAnsi", () => {
78
+ test("removes ANSI codes from string", () => {
79
+ const colored = "\x1b[31mhello\x1b[0m";
80
+ expect(stripAnsi(colored)).toBe("hello");
81
+ });
82
+
83
+ test("handles multiple ANSI codes", () => {
84
+ const styled = "\x1b[1;31mhello\x1b[0m \x1b[32mworld\x1b[0m";
85
+ expect(stripAnsi(styled)).toBe("hello world");
86
+ });
87
+
88
+ test("returns original string if no ANSI codes", () => {
89
+ expect(stripAnsi("hello world")).toBe("hello world");
90
+ });
91
+
92
+ test("handles empty string", () => {
93
+ expect(stripAnsi("")).toBe("");
94
+ });
95
+ });
96
+
97
+ describe("displayWidth", () => {
98
+ test("returns correct width for ASCII", () => {
99
+ expect(displayWidth("hello")).toBe(5);
100
+ expect(displayWidth("test")).toBe(4);
101
+ });
102
+
103
+ test("returns correct width for Korean (2 per char)", () => {
104
+ expect(displayWidth("안녕")).toBe(4); // 2 chars * 2 width
105
+ expect(displayWidth("테스트")).toBe(6); // 3 chars * 2 width
106
+ });
107
+
108
+ test("handles mixed ASCII and Korean", () => {
109
+ expect(displayWidth("hello안녕")).toBe(9); // 5 + 4
110
+ });
111
+
112
+ test("strips ANSI codes before calculating", () => {
113
+ const colored = c.red("hello");
114
+ expect(displayWidth(colored)).toBe(5);
115
+ });
116
+
117
+ test("handles empty string", () => {
118
+ expect(displayWidth("")).toBe(0);
119
+ });
120
+ });
121
+
122
+ describe("pad", () => {
123
+ test("pads string to width (left align)", () => {
124
+ expect(pad("hi", 5)).toBe("hi ");
125
+ });
126
+
127
+ test("pads string to width (right align)", () => {
128
+ expect(pad("hi", 5, "right")).toBe(" hi");
129
+ });
130
+
131
+ test("pads string to width (center align)", () => {
132
+ expect(pad("hi", 6, "center")).toBe(" hi ");
133
+ });
134
+
135
+ test("returns original string if already at width", () => {
136
+ expect(pad("hello", 5)).toBe("hello");
137
+ });
138
+
139
+ test("returns original string if longer than width", () => {
140
+ expect(pad("hello world", 5)).toBe("hello world");
141
+ });
142
+
143
+ test("handles ANSI codes correctly", () => {
144
+ const colored = c.red("hi");
145
+ const padded = pad(colored, 5);
146
+ expect(stripAnsi(padded)).toBe("hi ");
147
+ });
148
+ });
149
+
150
+ describe("truncate", () => {
151
+ test("truncates long string with suffix", () => {
152
+ expect(truncate("hello world", 8)).toBe("hello...");
153
+ });
154
+
155
+ test("returns original string if within limit", () => {
156
+ expect(truncate("hello", 10)).toBe("hello");
157
+ });
158
+
159
+ test("uses custom suffix", () => {
160
+ expect(truncate("hello world", 8, "~")).toBe("hello w~");
161
+ });
162
+
163
+ test("handles Korean characters", () => {
164
+ const result = truncate("안녕하세요", 6);
165
+ expect(displayWidth(result)).toBeLessThanOrEqual(6);
166
+ });
167
+
168
+ test("handles empty string", () => {
169
+ expect(truncate("", 5)).toBe("");
170
+ });
171
+ });
172
+
173
+ describe("progressBar", () => {
174
+ test("creates progress bar at 0%", () => {
175
+ const bar = progressBar(0, 10);
176
+ expect(bar).toContain("0%");
177
+ expect(stripAnsi(bar)).toContain("░░░░░░░░░░░░░░░░░░░░");
178
+ });
179
+
180
+ test("creates progress bar at 50%", () => {
181
+ const bar = progressBar(5, 10);
182
+ expect(bar).toContain("50%");
183
+ });
184
+
185
+ test("creates progress bar at 100%", () => {
186
+ const bar = progressBar(10, 10);
187
+ expect(bar).toContain("100%");
188
+ expect(stripAnsi(bar)).toContain("████████████████████");
189
+ });
190
+
191
+ test("handles custom width", () => {
192
+ const bar = progressBar(5, 10, { width: 10, showPercent: false });
193
+ const stripped = stripAnsi(bar);
194
+ expect(stripped.length).toBe(10);
195
+ });
196
+
197
+ test("handles zero total", () => {
198
+ const bar = progressBar(0, 0);
199
+ expect(bar).toContain("0%");
200
+ });
201
+
202
+ test("hides percent when showPercent is false", () => {
203
+ const bar = progressBar(5, 10, { showPercent: false });
204
+ expect(bar).not.toContain("%");
205
+ });
206
+ });
207
+
208
+ describe("hline", () => {
209
+ test("creates horizontal line of specified width", () => {
210
+ expect(hline(5)).toBe("─────");
211
+ });
212
+
213
+ test("uses custom character", () => {
214
+ expect(hline(3, "=")).toBe("===");
215
+ });
216
+
217
+ test("handles zero width", () => {
218
+ expect(hline(0)).toBe("");
219
+ });
220
+ });
221
+ });
@@ -0,0 +1,140 @@
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
+ });