opencode-swarm-plugin 0.43.0 → 0.44.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/bin/cass.characterization.test.ts +422 -0
- package/bin/swarm.serve.test.ts +6 -4
- package/bin/swarm.test.ts +68 -0
- package/bin/swarm.ts +81 -8
- package/dist/compaction-prompt-scoring.js +139 -0
- package/dist/contributor-tools.d.ts +42 -0
- package/dist/contributor-tools.d.ts.map +1 -0
- package/dist/eval-capture.js +12811 -0
- package/dist/hive.d.ts.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7728 -62590
- package/dist/plugin.js +23833 -78695
- package/dist/sessions/agent-discovery.d.ts +59 -0
- package/dist/sessions/agent-discovery.d.ts.map +1 -0
- package/dist/sessions/index.d.ts +10 -0
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm-review.d.ts.map +1 -1
- package/package.json +17 -5
- package/.changeset/swarm-insights-data-layer.md +0 -63
- package/.hive/analysis/eval-failure-analysis-2025-12-25.md +0 -331
- package/.hive/analysis/session-data-quality-audit.md +0 -320
- package/.hive/eval-results.json +0 -483
- package/.hive/issues.jsonl +0 -138
- package/.hive/memories.jsonl +0 -729
- package/.opencode/eval-history.jsonl +0 -327
- package/.turbo/turbo-build.log +0 -9
- package/CHANGELOG.md +0 -2255
- package/SCORER-ANALYSIS.md +0 -598
- package/docs/analysis/subagent-coordination-patterns.md +0 -902
- package/docs/analysis-socratic-planner-pattern.md +0 -504
- package/docs/planning/ADR-001-monorepo-structure.md +0 -171
- package/docs/planning/ADR-002-package-extraction.md +0 -393
- package/docs/planning/ADR-003-performance-improvements.md +0 -451
- package/docs/planning/ADR-004-message-queue-features.md +0 -187
- package/docs/planning/ADR-005-devtools-observability.md +0 -202
- package/docs/planning/ADR-007-swarm-enhancements-worktree-review.md +0 -168
- package/docs/planning/ADR-008-worker-handoff-protocol.md +0 -293
- package/docs/planning/ADR-009-oh-my-opencode-patterns.md +0 -353
- package/docs/planning/ROADMAP.md +0 -368
- package/docs/semantic-memory-cli-syntax.md +0 -123
- package/docs/swarm-mail-architecture.md +0 -1147
- package/docs/testing/context-recovery-test.md +0 -470
- package/evals/ARCHITECTURE.md +0 -1189
- package/evals/README.md +0 -768
- package/evals/compaction-prompt.eval.ts +0 -149
- package/evals/compaction-resumption.eval.ts +0 -289
- package/evals/coordinator-behavior.eval.ts +0 -307
- package/evals/coordinator-session.eval.ts +0 -154
- package/evals/evalite.config.ts.bak +0 -15
- package/evals/example.eval.ts +0 -31
- package/evals/fixtures/compaction-cases.ts +0 -350
- package/evals/fixtures/compaction-prompt-cases.ts +0 -311
- package/evals/fixtures/coordinator-sessions.ts +0 -328
- package/evals/fixtures/decomposition-cases.ts +0 -105
- package/evals/lib/compaction-loader.test.ts +0 -248
- package/evals/lib/compaction-loader.ts +0 -320
- package/evals/lib/data-loader.evalite-test.ts +0 -289
- package/evals/lib/data-loader.test.ts +0 -345
- package/evals/lib/data-loader.ts +0 -281
- package/evals/lib/llm.ts +0 -115
- package/evals/scorers/compaction-prompt-scorers.ts +0 -145
- package/evals/scorers/compaction-scorers.ts +0 -305
- package/evals/scorers/coordinator-discipline.evalite-test.ts +0 -539
- package/evals/scorers/coordinator-discipline.ts +0 -325
- package/evals/scorers/index.test.ts +0 -146
- package/evals/scorers/index.ts +0 -328
- package/evals/scorers/outcome-scorers.evalite-test.ts +0 -27
- package/evals/scorers/outcome-scorers.ts +0 -349
- package/evals/swarm-decomposition.eval.ts +0 -121
- package/examples/commands/swarm.md +0 -745
- package/examples/plugin-wrapper-template.ts +0 -2426
- package/examples/skills/hive-workflow/SKILL.md +0 -212
- package/examples/skills/skill-creator/SKILL.md +0 -223
- package/examples/skills/swarm-coordination/SKILL.md +0 -292
- package/global-skills/cli-builder/SKILL.md +0 -344
- package/global-skills/cli-builder/references/advanced-patterns.md +0 -244
- package/global-skills/learning-systems/SKILL.md +0 -644
- package/global-skills/skill-creator/LICENSE.txt +0 -202
- package/global-skills/skill-creator/SKILL.md +0 -352
- package/global-skills/skill-creator/references/output-patterns.md +0 -82
- package/global-skills/skill-creator/references/workflows.md +0 -28
- package/global-skills/swarm-coordination/SKILL.md +0 -995
- package/global-skills/swarm-coordination/references/coordinator-patterns.md +0 -235
- package/global-skills/swarm-coordination/references/strategies.md +0 -138
- package/global-skills/system-design/SKILL.md +0 -213
- package/global-skills/testing-patterns/SKILL.md +0 -430
- package/global-skills/testing-patterns/references/dependency-breaking-catalog.md +0 -586
- package/opencode-swarm-plugin-0.30.7.tgz +0 -0
- package/opencode-swarm-plugin-0.31.0.tgz +0 -0
- package/scripts/cleanup-test-memories.ts +0 -346
- package/scripts/init-skill.ts +0 -222
- package/scripts/migrate-unknown-sessions.ts +0 -349
- package/scripts/validate-skill.ts +0 -204
- package/src/agent-mail.ts +0 -1724
- package/src/anti-patterns.test.ts +0 -1167
- package/src/anti-patterns.ts +0 -448
- package/src/compaction-capture.integration.test.ts +0 -257
- package/src/compaction-hook.test.ts +0 -838
- package/src/compaction-hook.ts +0 -1204
- package/src/compaction-observability.integration.test.ts +0 -139
- package/src/compaction-observability.test.ts +0 -187
- package/src/compaction-observability.ts +0 -324
- package/src/compaction-prompt-scorers.test.ts +0 -475
- package/src/compaction-prompt-scoring.ts +0 -300
- package/src/dashboard.test.ts +0 -611
- package/src/dashboard.ts +0 -462
- package/src/error-enrichment.test.ts +0 -403
- package/src/error-enrichment.ts +0 -219
- package/src/eval-capture.test.ts +0 -1015
- package/src/eval-capture.ts +0 -929
- package/src/eval-gates.test.ts +0 -306
- package/src/eval-gates.ts +0 -218
- package/src/eval-history.test.ts +0 -508
- package/src/eval-history.ts +0 -214
- package/src/eval-learning.test.ts +0 -378
- package/src/eval-learning.ts +0 -360
- package/src/eval-runner.test.ts +0 -223
- package/src/eval-runner.ts +0 -402
- package/src/export-tools.test.ts +0 -476
- package/src/export-tools.ts +0 -257
- package/src/hive.integration.test.ts +0 -2241
- package/src/hive.ts +0 -1628
- package/src/index.ts +0 -935
- package/src/learning.integration.test.ts +0 -1815
- package/src/learning.ts +0 -1079
- package/src/logger.test.ts +0 -189
- package/src/logger.ts +0 -135
- package/src/mandate-promotion.test.ts +0 -473
- package/src/mandate-promotion.ts +0 -239
- package/src/mandate-storage.integration.test.ts +0 -601
- package/src/mandate-storage.test.ts +0 -578
- package/src/mandate-storage.ts +0 -794
- package/src/mandates.ts +0 -540
- package/src/memory-tools.test.ts +0 -195
- package/src/memory-tools.ts +0 -344
- package/src/memory.integration.test.ts +0 -334
- package/src/memory.test.ts +0 -158
- package/src/memory.ts +0 -527
- package/src/model-selection.test.ts +0 -188
- package/src/model-selection.ts +0 -68
- package/src/observability-tools.test.ts +0 -359
- package/src/observability-tools.ts +0 -871
- package/src/output-guardrails.test.ts +0 -438
- package/src/output-guardrails.ts +0 -381
- package/src/pattern-maturity.test.ts +0 -1160
- package/src/pattern-maturity.ts +0 -525
- package/src/planning-guardrails.test.ts +0 -491
- package/src/planning-guardrails.ts +0 -438
- package/src/plugin.ts +0 -23
- package/src/post-compaction-tracker.test.ts +0 -251
- package/src/post-compaction-tracker.ts +0 -237
- package/src/query-tools.test.ts +0 -636
- package/src/query-tools.ts +0 -324
- package/src/rate-limiter.integration.test.ts +0 -466
- package/src/rate-limiter.ts +0 -774
- package/src/replay-tools.test.ts +0 -496
- package/src/replay-tools.ts +0 -240
- package/src/repo-crawl.integration.test.ts +0 -441
- package/src/repo-crawl.ts +0 -610
- package/src/schemas/cell-events.test.ts +0 -347
- package/src/schemas/cell-events.ts +0 -807
- package/src/schemas/cell.ts +0 -257
- package/src/schemas/evaluation.ts +0 -166
- package/src/schemas/index.test.ts +0 -199
- package/src/schemas/index.ts +0 -286
- package/src/schemas/mandate.ts +0 -232
- package/src/schemas/swarm-context.ts +0 -115
- package/src/schemas/task.ts +0 -161
- package/src/schemas/worker-handoff.test.ts +0 -302
- package/src/schemas/worker-handoff.ts +0 -131
- package/src/skills.integration.test.ts +0 -1192
- package/src/skills.test.ts +0 -643
- package/src/skills.ts +0 -1549
- package/src/storage.integration.test.ts +0 -341
- package/src/storage.ts +0 -884
- package/src/structured.integration.test.ts +0 -817
- package/src/structured.test.ts +0 -1046
- package/src/structured.ts +0 -762
- package/src/swarm-decompose.test.ts +0 -188
- package/src/swarm-decompose.ts +0 -1302
- package/src/swarm-deferred.integration.test.ts +0 -157
- package/src/swarm-deferred.test.ts +0 -38
- package/src/swarm-insights.test.ts +0 -214
- package/src/swarm-insights.ts +0 -459
- package/src/swarm-mail.integration.test.ts +0 -970
- package/src/swarm-mail.ts +0 -739
- package/src/swarm-orchestrate.integration.test.ts +0 -282
- package/src/swarm-orchestrate.test.ts +0 -548
- package/src/swarm-orchestrate.ts +0 -3084
- package/src/swarm-prompts.test.ts +0 -1270
- package/src/swarm-prompts.ts +0 -2077
- package/src/swarm-research.integration.test.ts +0 -701
- package/src/swarm-research.test.ts +0 -698
- package/src/swarm-research.ts +0 -472
- package/src/swarm-review.integration.test.ts +0 -285
- package/src/swarm-review.test.ts +0 -879
- package/src/swarm-review.ts +0 -709
- package/src/swarm-strategies.ts +0 -407
- package/src/swarm-worktree.test.ts +0 -501
- package/src/swarm-worktree.ts +0 -575
- package/src/swarm.integration.test.ts +0 -2377
- package/src/swarm.ts +0 -38
- package/src/tool-adapter.integration.test.ts +0 -1221
- package/src/tool-availability.ts +0 -461
- package/tsconfig.json +0 -28
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cli-builder
|
|
3
|
-
description: Guide for building TypeScript CLIs with Bun. Use when creating command-line tools, adding subcommands to existing CLIs, or building developer tooling. Covers argument parsing, subcommand patterns, output formatting, and distribution.
|
|
4
|
-
tags:
|
|
5
|
-
- cli
|
|
6
|
-
- typescript
|
|
7
|
-
- bun
|
|
8
|
-
- tooling
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# CLI Builder
|
|
12
|
-
|
|
13
|
-
Build TypeScript command-line tools with Bun.
|
|
14
|
-
|
|
15
|
-
## When to Build a CLI
|
|
16
|
-
|
|
17
|
-
CLIs are ideal for:
|
|
18
|
-
- Developer tools and automation
|
|
19
|
-
- Project-specific commands (`swarm`, `bd`, etc.)
|
|
20
|
-
- Scripts that need arguments/flags
|
|
21
|
-
- Tools that compose with shell pipelines
|
|
22
|
-
|
|
23
|
-
## Quick Start
|
|
24
|
-
|
|
25
|
-
### Minimal CLI
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
#!/usr/bin/env bun
|
|
29
|
-
// scripts/my-tool.ts
|
|
30
|
-
|
|
31
|
-
const args = process.argv.slice(2);
|
|
32
|
-
const command = args[0];
|
|
33
|
-
|
|
34
|
-
if (!command || command === "help") {
|
|
35
|
-
console.log(`
|
|
36
|
-
Usage: my-tool <command>
|
|
37
|
-
|
|
38
|
-
Commands:
|
|
39
|
-
hello Say hello
|
|
40
|
-
help Show this message
|
|
41
|
-
`);
|
|
42
|
-
process.exit(0);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (command === "hello") {
|
|
46
|
-
console.log("Hello, world!");
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Run with: `bun scripts/my-tool.ts hello`
|
|
51
|
-
|
|
52
|
-
### With Argument Parsing
|
|
53
|
-
|
|
54
|
-
Use `parseArgs` from Node's `util` module (works in Bun):
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
#!/usr/bin/env bun
|
|
58
|
-
import { parseArgs } from "util";
|
|
59
|
-
|
|
60
|
-
const { values, positionals } = parseArgs({
|
|
61
|
-
args: process.argv.slice(2),
|
|
62
|
-
options: {
|
|
63
|
-
name: { type: "string", short: "n" },
|
|
64
|
-
verbose: { type: "boolean", short: "v", default: false },
|
|
65
|
-
help: { type: "boolean", short: "h", default: false },
|
|
66
|
-
},
|
|
67
|
-
allowPositionals: true,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
if (values.help) {
|
|
71
|
-
console.log(`
|
|
72
|
-
Usage: greet [options] <message>
|
|
73
|
-
|
|
74
|
-
Options:
|
|
75
|
-
-n, --name <name> Name to greet
|
|
76
|
-
-v, --verbose Verbose output
|
|
77
|
-
-h, --help Show help
|
|
78
|
-
`);
|
|
79
|
-
process.exit(0);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const message = positionals[0] || "Hello";
|
|
83
|
-
const name = values.name || "World";
|
|
84
|
-
|
|
85
|
-
console.log(`${message}, ${name}!`);
|
|
86
|
-
if (values.verbose) {
|
|
87
|
-
console.log(` (greeted at ${new Date().toISOString()})`);
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Subcommand Pattern
|
|
92
|
-
|
|
93
|
-
For CLIs with multiple commands, use a command registry:
|
|
94
|
-
|
|
95
|
-
```typescript
|
|
96
|
-
#!/usr/bin/env bun
|
|
97
|
-
import { parseArgs } from "util";
|
|
98
|
-
|
|
99
|
-
type Command = {
|
|
100
|
-
description: string;
|
|
101
|
-
run: (args: string[]) => Promise<void>;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const commands: Record<string, Command> = {
|
|
105
|
-
init: {
|
|
106
|
-
description: "Initialize a new project",
|
|
107
|
-
run: async (args) => {
|
|
108
|
-
const { values } = parseArgs({
|
|
109
|
-
args,
|
|
110
|
-
options: {
|
|
111
|
-
template: { type: "string", short: "t", default: "default" },
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
console.log(`Initializing with template: ${values.template}`);
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
build: {
|
|
119
|
-
description: "Build the project",
|
|
120
|
-
run: async (args) => {
|
|
121
|
-
const { values } = parseArgs({
|
|
122
|
-
args,
|
|
123
|
-
options: {
|
|
124
|
-
watch: { type: "boolean", short: "w", default: false },
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
console.log(`Building...${values.watch ? " (watch mode)" : ""}`);
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
function showHelp() {
|
|
133
|
-
console.log(`
|
|
134
|
-
Usage: mytool <command> [options]
|
|
135
|
-
|
|
136
|
-
Commands:`);
|
|
137
|
-
for (const [name, cmd] of Object.entries(commands)) {
|
|
138
|
-
console.log(` ${name.padEnd(12)} ${cmd.description}`);
|
|
139
|
-
}
|
|
140
|
-
console.log(`
|
|
141
|
-
Run 'mytool <command> --help' for command-specific help.
|
|
142
|
-
`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Main
|
|
146
|
-
const [command, ...args] = process.argv.slice(2);
|
|
147
|
-
|
|
148
|
-
if (!command || command === "help" || command === "--help") {
|
|
149
|
-
showHelp();
|
|
150
|
-
process.exit(0);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const cmd = commands[command];
|
|
154
|
-
if (!cmd) {
|
|
155
|
-
console.error(`Unknown command: ${command}`);
|
|
156
|
-
showHelp();
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
await cmd.run(args);
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
## Output Formatting
|
|
164
|
-
|
|
165
|
-
### Colors (without dependencies)
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
const colors = {
|
|
169
|
-
reset: "\x1b[0m",
|
|
170
|
-
red: "\x1b[31m",
|
|
171
|
-
green: "\x1b[32m",
|
|
172
|
-
yellow: "\x1b[33m",
|
|
173
|
-
blue: "\x1b[34m",
|
|
174
|
-
dim: "\x1b[2m",
|
|
175
|
-
bold: "\x1b[1m",
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
function success(msg: string) {
|
|
179
|
-
console.log(`${colors.green}✓${colors.reset} ${msg}`);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function error(msg: string) {
|
|
183
|
-
console.error(`${colors.red}✗${colors.reset} ${msg}`);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function warn(msg: string) {
|
|
187
|
-
console.log(`${colors.yellow}⚠${colors.reset} ${msg}`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function info(msg: string) {
|
|
191
|
-
console.log(`${colors.blue}ℹ${colors.reset} ${msg}`);
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### JSON Output Mode
|
|
196
|
-
|
|
197
|
-
Support `--json` for scriptable output:
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
const { values } = parseArgs({
|
|
201
|
-
args: process.argv.slice(2),
|
|
202
|
-
options: {
|
|
203
|
-
json: { type: "boolean", default: false },
|
|
204
|
-
},
|
|
205
|
-
allowPositionals: true,
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const result = { status: "ok", items: ["a", "b", "c"] };
|
|
209
|
-
|
|
210
|
-
if (values.json) {
|
|
211
|
-
console.log(JSON.stringify(result, null, 2));
|
|
212
|
-
} else {
|
|
213
|
-
console.log("Status:", result.status);
|
|
214
|
-
console.log("Items:", result.items.join(", "));
|
|
215
|
-
}
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### Progress Indicators
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
function spinner(message: string) {
|
|
222
|
-
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
223
|
-
let i = 0;
|
|
224
|
-
|
|
225
|
-
const id = setInterval(() => {
|
|
226
|
-
process.stdout.write(`\r${frames[i++ % frames.length]} ${message}`);
|
|
227
|
-
}, 80);
|
|
228
|
-
|
|
229
|
-
return {
|
|
230
|
-
stop: (finalMessage?: string) => {
|
|
231
|
-
clearInterval(id);
|
|
232
|
-
process.stdout.write(`\r${finalMessage || message}\n`);
|
|
233
|
-
},
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Usage
|
|
238
|
-
const spin = spinner("Loading...");
|
|
239
|
-
await someAsyncWork();
|
|
240
|
-
spin.stop("✓ Done!");
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
## File System Operations
|
|
244
|
-
|
|
245
|
-
```typescript
|
|
246
|
-
import { readFile, writeFile, mkdir, readdir } from "fs/promises";
|
|
247
|
-
import { existsSync } from "fs";
|
|
248
|
-
import { join, dirname } from "path";
|
|
249
|
-
|
|
250
|
-
// Ensure directory exists before writing
|
|
251
|
-
async function writeFileWithDir(path: string, content: string) {
|
|
252
|
-
await mkdir(dirname(path), { recursive: true });
|
|
253
|
-
await writeFile(path, content);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Read JSON with defaults
|
|
257
|
-
async function readJsonFile<T>(path: string, defaults: T): Promise<T> {
|
|
258
|
-
if (!existsSync(path)) return defaults;
|
|
259
|
-
const content = await readFile(path, "utf-8");
|
|
260
|
-
return { ...defaults, ...JSON.parse(content) };
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
## Shell Execution
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
import { $ } from "bun";
|
|
268
|
-
|
|
269
|
-
// Simple command
|
|
270
|
-
const result = await $`git status`.text();
|
|
271
|
-
|
|
272
|
-
// With error handling
|
|
273
|
-
try {
|
|
274
|
-
await $`npm test`.quiet();
|
|
275
|
-
console.log("Tests passed!");
|
|
276
|
-
} catch (error) {
|
|
277
|
-
console.error("Tests failed");
|
|
278
|
-
process.exit(1);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Capture output
|
|
282
|
-
const branch = await $`git branch --show-current`.text();
|
|
283
|
-
console.log(`Current branch: ${branch.trim()}`);
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
## Error Handling
|
|
287
|
-
|
|
288
|
-
```typescript
|
|
289
|
-
class CLIError extends Error {
|
|
290
|
-
constructor(message: string, public exitCode = 1) {
|
|
291
|
-
super(message);
|
|
292
|
-
this.name = "CLIError";
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async function main() {
|
|
297
|
-
try {
|
|
298
|
-
await runCommand();
|
|
299
|
-
} catch (error) {
|
|
300
|
-
if (error instanceof CLIError) {
|
|
301
|
-
console.error(`Error: ${error.message}`);
|
|
302
|
-
process.exit(error.exitCode);
|
|
303
|
-
}
|
|
304
|
-
throw error; // Re-throw unexpected errors
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
main();
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
## Distribution
|
|
312
|
-
|
|
313
|
-
### package.json bin field
|
|
314
|
-
|
|
315
|
-
```json
|
|
316
|
-
{
|
|
317
|
-
"name": "my-cli",
|
|
318
|
-
"bin": {
|
|
319
|
-
"mycli": "./dist/cli.js"
|
|
320
|
-
},
|
|
321
|
-
"scripts": {
|
|
322
|
-
"build": "bun build ./src/cli.ts --outfile ./dist/cli.js --target node"
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
### Shebang for direct execution
|
|
328
|
-
|
|
329
|
-
```typescript
|
|
330
|
-
#!/usr/bin/env bun
|
|
331
|
-
// First line of your CLI script
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
Make executable: `chmod +x scripts/my-cli.ts`
|
|
335
|
-
|
|
336
|
-
## Best Practices
|
|
337
|
-
|
|
338
|
-
1. **Always provide --help** - Users expect it
|
|
339
|
-
2. **Exit codes matter** - 0 for success, non-zero for errors
|
|
340
|
-
3. **Support --json** - For scriptability and piping
|
|
341
|
-
4. **Fail fast** - Validate inputs early
|
|
342
|
-
5. **Be quiet by default** - Use --verbose for noise
|
|
343
|
-
6. **Respect NO_COLOR** - Check `process.env.NO_COLOR`
|
|
344
|
-
7. **Stream large output** - Don't buffer everything in memory
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
# Advanced CLI Patterns
|
|
2
|
-
|
|
3
|
-
## Interactive Prompts
|
|
4
|
-
|
|
5
|
-
For interactive CLIs, use `@clack/prompts` (lightweight, pretty):
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import * as p from "@clack/prompts";
|
|
9
|
-
|
|
10
|
-
async function setup() {
|
|
11
|
-
p.intro("Project Setup");
|
|
12
|
-
|
|
13
|
-
const name = await p.text({
|
|
14
|
-
message: "Project name?",
|
|
15
|
-
placeholder: "my-project",
|
|
16
|
-
validate: (v) => (v.length < 1 ? "Name required" : undefined),
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const template = await p.select({
|
|
20
|
-
message: "Choose a template",
|
|
21
|
-
options: [
|
|
22
|
-
{ value: "basic", label: "Basic" },
|
|
23
|
-
{ value: "full", label: "Full Featured" },
|
|
24
|
-
],
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const features = await p.multiselect({
|
|
28
|
-
message: "Select features",
|
|
29
|
-
options: [
|
|
30
|
-
{ value: "typescript", label: "TypeScript" },
|
|
31
|
-
{ value: "testing", label: "Testing" },
|
|
32
|
-
{ value: "linting", label: "Linting" },
|
|
33
|
-
],
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const confirmed = await p.confirm({
|
|
37
|
-
message: "Create project?",
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
if (p.isCancel(confirmed) || !confirmed) {
|
|
41
|
-
p.cancel("Cancelled");
|
|
42
|
-
process.exit(0);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const s = p.spinner();
|
|
46
|
-
s.start("Creating project...");
|
|
47
|
-
await createProject({ name, template, features });
|
|
48
|
-
s.stop("Project created!");
|
|
49
|
-
|
|
50
|
-
p.outro("Done! Run `cd ${name} && bun dev`");
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Config File Loading
|
|
55
|
-
|
|
56
|
-
Support multiple config formats:
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
import { existsSync } from "fs";
|
|
60
|
-
import { readFile } from "fs/promises";
|
|
61
|
-
|
|
62
|
-
interface Config {
|
|
63
|
-
name: string;
|
|
64
|
-
debug: boolean;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const CONFIG_FILES = [
|
|
68
|
-
"myapp.config.ts",
|
|
69
|
-
"myapp.config.js",
|
|
70
|
-
"myapp.config.json",
|
|
71
|
-
".myapprc",
|
|
72
|
-
".myapprc.json",
|
|
73
|
-
];
|
|
74
|
-
|
|
75
|
-
async function loadConfig(): Promise<Config> {
|
|
76
|
-
const defaults: Config = { name: "default", debug: false };
|
|
77
|
-
|
|
78
|
-
for (const file of CONFIG_FILES) {
|
|
79
|
-
if (!existsSync(file)) continue;
|
|
80
|
-
|
|
81
|
-
if (file.endsWith(".ts") || file.endsWith(".js")) {
|
|
82
|
-
const mod = await import(`./${file}`);
|
|
83
|
-
return { ...defaults, ...mod.default };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (file.endsWith(".json") || file.startsWith(".")) {
|
|
87
|
-
const content = await readFile(file, "utf-8");
|
|
88
|
-
return { ...defaults, ...JSON.parse(content) };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return defaults;
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Plugin System
|
|
97
|
-
|
|
98
|
-
Allow extending CLI with plugins:
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
interface Plugin {
|
|
102
|
-
name: string;
|
|
103
|
-
commands?: Record<string, Command>;
|
|
104
|
-
hooks?: {
|
|
105
|
-
beforeRun?: () => Promise<void>;
|
|
106
|
-
afterRun?: () => Promise<void>;
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
class CLI {
|
|
111
|
-
private plugins: Plugin[] = [];
|
|
112
|
-
private commands: Record<string, Command> = {};
|
|
113
|
-
|
|
114
|
-
use(plugin: Plugin) {
|
|
115
|
-
this.plugins.push(plugin);
|
|
116
|
-
if (plugin.commands) {
|
|
117
|
-
Object.assign(this.commands, plugin.commands);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async run(args: string[]) {
|
|
122
|
-
// Run beforeRun hooks
|
|
123
|
-
for (const p of this.plugins) {
|
|
124
|
-
await p.hooks?.beforeRun?.();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Execute command
|
|
128
|
-
const [cmd, ...rest] = args;
|
|
129
|
-
await this.commands[cmd]?.run(rest);
|
|
130
|
-
|
|
131
|
-
// Run afterRun hooks
|
|
132
|
-
for (const p of this.plugins) {
|
|
133
|
-
await p.hooks?.afterRun?.();
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## Watching Files
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
import { watch } from "fs";
|
|
143
|
-
|
|
144
|
-
function watchFiles(
|
|
145
|
-
dir: string,
|
|
146
|
-
callback: (event: string, filename: string) => void
|
|
147
|
-
) {
|
|
148
|
-
const watcher = watch(dir, { recursive: true }, (event, filename) => {
|
|
149
|
-
if (filename && !filename.includes("node_modules")) {
|
|
150
|
-
callback(event, filename);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// Cleanup on exit
|
|
155
|
-
process.on("SIGINT", () => {
|
|
156
|
-
watcher.close();
|
|
157
|
-
process.exit(0);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
return watcher;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Usage
|
|
164
|
-
watchFiles("./src", (event, file) => {
|
|
165
|
-
console.log(`${event}: ${file}`);
|
|
166
|
-
// Trigger rebuild, restart, etc.
|
|
167
|
-
});
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
## Parallel Execution
|
|
171
|
-
|
|
172
|
-
```typescript
|
|
173
|
-
async function runParallel<T>(
|
|
174
|
-
items: T[],
|
|
175
|
-
fn: (item: T) => Promise<void>,
|
|
176
|
-
concurrency = 4
|
|
177
|
-
) {
|
|
178
|
-
const chunks = [];
|
|
179
|
-
for (let i = 0; i < items.length; i += concurrency) {
|
|
180
|
-
chunks.push(items.slice(i, i + concurrency));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
for (const chunk of chunks) {
|
|
184
|
-
await Promise.all(chunk.map(fn));
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Usage
|
|
189
|
-
await runParallel(files, async (file) => {
|
|
190
|
-
await processFile(file);
|
|
191
|
-
}, 8);
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
## Testing CLIs
|
|
195
|
-
|
|
196
|
-
```typescript
|
|
197
|
-
import { describe, test, expect } from "bun:test";
|
|
198
|
-
import { $ } from "bun";
|
|
199
|
-
|
|
200
|
-
describe("mycli", () => {
|
|
201
|
-
test("--help shows usage", async () => {
|
|
202
|
-
const result = await $`bun ./src/cli.ts --help`.text();
|
|
203
|
-
expect(result).toContain("Usage:");
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test("unknown command fails", async () => {
|
|
207
|
-
try {
|
|
208
|
-
await $`bun ./src/cli.ts unknown`.quiet();
|
|
209
|
-
expect(true).toBe(false); // Should not reach
|
|
210
|
-
} catch (error) {
|
|
211
|
-
expect(error.exitCode).toBe(1);
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("init creates files", async () => {
|
|
216
|
-
const tmpDir = await $`mktemp -d`.text();
|
|
217
|
-
await $`bun ./src/cli.ts init --path ${tmpDir.trim()}`;
|
|
218
|
-
|
|
219
|
-
const files = await $`ls ${tmpDir.trim()}`.text();
|
|
220
|
-
expect(files).toContain("package.json");
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
## Graceful Shutdown
|
|
226
|
-
|
|
227
|
-
```typescript
|
|
228
|
-
let isShuttingDown = false;
|
|
229
|
-
|
|
230
|
-
async function shutdown() {
|
|
231
|
-
if (isShuttingDown) return;
|
|
232
|
-
isShuttingDown = true;
|
|
233
|
-
|
|
234
|
-
console.log("\nShutting down...");
|
|
235
|
-
|
|
236
|
-
// Cleanup: close connections, save state, etc.
|
|
237
|
-
await cleanup();
|
|
238
|
-
|
|
239
|
-
process.exit(0);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
process.on("SIGINT", shutdown);
|
|
243
|
-
process.on("SIGTERM", shutdown);
|
|
244
|
-
```
|