@task-mcp/cli 1.0.4 → 1.0.6
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 +5 -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 -14
- package/src/commands/dashboard.ts +371 -40
- package/src/commands/inbox.ts +267 -0
- package/src/commands/list.ts +1 -1
- package/src/index.ts +125 -4
- package/src/interactive.ts +400 -0
- package/src/storage.ts +355 -28
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
|
-
##
|
|
27
|
+
## Quick Start
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
# Show dashboard (default)
|
|
31
31
|
task
|
|
32
32
|
|
|
33
|
-
#
|
|
34
|
-
task
|
|
35
|
-
task
|
|
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
|
-
|
|
38
|
-
task list
|
|
39
|
-
task ls
|
|
55
|
+
### Interactive Mode
|
|
40
56
|
|
|
41
|
-
|
|
42
|
-
task
|
|
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 --
|
|
79
|
+
task list --priority critical,high
|
|
80
|
+
```
|
|
45
81
|
|
|
46
|
-
|
|
47
|
-
task projects
|
|
48
|
-
task p
|
|
49
|
-
task projects --all # Include archived
|
|
82
|
+
### Projects
|
|
50
83
|
|
|
51
|
-
|
|
52
|
-
task
|
|
53
|
-
task
|
|
84
|
+
```bash
|
|
85
|
+
task projects # List active projects
|
|
86
|
+
task p # Alias
|
|
87
|
+
task projects --all # Include archived
|
|
54
88
|
```
|
|
55
89
|
|
|
56
|
-
|
|
90
|
+
### Inbox
|
|
57
91
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
|
71
|
-
| `--
|
|
72
|
-
| `--
|
|
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
|
-
|
|
140
|
+
### Status Values
|
|
141
|
+
`pending`, `in_progress`, `blocked`, `completed`, `cancelled`
|
|
75
142
|
|
|
76
|
-
|
|
77
|
-
|
|
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.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Zero-dependency CLI for task-mcp with Bun native visualization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
"url": "git+https://github.com/addsalt1t/task-mcp.git",
|
|
30
30
|
"directory": "packages/cli"
|
|
31
31
|
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@task-mcp/mcp-server": "workspace:*",
|
|
34
|
+
"@task-mcp/shared": "workspace:*"
|
|
35
|
+
},
|
|
32
36
|
"devDependencies": {
|
|
33
37
|
"@types/bun": "^1.1.14",
|
|
34
38
|
"typescript": "^5.7.2"
|
|
@@ -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
|
+
});
|