@ionivetech/mugiwara 0.1.0

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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +488 -0
  3. package/content/agents/brook-healing.md +36 -0
  4. package/content/agents/chopper-checkpoint.md +41 -0
  5. package/content/agents/eval-runner.md +44 -0
  6. package/content/agents/franky-gates.md +35 -0
  7. package/content/agents/jinbe-security.md +41 -0
  8. package/content/agents/luffy-orchestrator.md +44 -0
  9. package/content/agents/memory-keeper.md +37 -0
  10. package/content/agents/nami-planner.md +42 -0
  11. package/content/agents/resume-coordinator.md +39 -0
  12. package/content/agents/robin-reviewer.md +40 -0
  13. package/content/agents/sanji-quality.md +36 -0
  14. package/content/agents/skeptic-verifier.md +39 -0
  15. package/content/agents/using-mugiwara.md +36 -0
  16. package/content/agents/usopp-brainstorm.md +36 -0
  17. package/content/agents/zoro-execution.md +39 -0
  18. package/content/skills/mugiwara-agent-security/SKILL.md +58 -0
  19. package/content/skills/mugiwara-backend/SKILL.md +90 -0
  20. package/content/skills/mugiwara-brainstorm/SKILL.md +53 -0
  21. package/content/skills/mugiwara-checkpoint/SKILL.md +62 -0
  22. package/content/skills/mugiwara-dynamic-workflow/SKILL.md +85 -0
  23. package/content/skills/mugiwara-eval/SKILL.md +82 -0
  24. package/content/skills/mugiwara-execution/SKILL.md +81 -0
  25. package/content/skills/mugiwara-frontend/SKILL.md +122 -0
  26. package/content/skills/mugiwara-gates/SKILL.md +50 -0
  27. package/content/skills/mugiwara-git/SKILL.md +67 -0
  28. package/content/skills/mugiwara-healing/SKILL.md +62 -0
  29. package/content/skills/mugiwara-lessons/SKILL.md +57 -0
  30. package/content/skills/mugiwara-observability/SKILL.md +54 -0
  31. package/content/skills/mugiwara-orchestration/SKILL.md +55 -0
  32. package/content/skills/mugiwara-planning/SKILL.md +98 -0
  33. package/content/skills/mugiwara-quality/SKILL.md +39 -0
  34. package/content/skills/mugiwara-resume/SKILL.md +49 -0
  35. package/content/skills/mugiwara-review/SKILL.md +86 -0
  36. package/content/skills/mugiwara-security/SKILL.md +87 -0
  37. package/content/skills/mugiwara-ship/SKILL.md +58 -0
  38. package/content/skills/mugiwara-workflow/SKILL.md +90 -0
  39. package/dist/mugiwara.js +602 -0
  40. package/package.json +29 -0
  41. package/scripts/install.ps1 +14 -0
  42. package/scripts/install.sh +17 -0
  43. package/src/args.ts +31 -0
  44. package/src/cli.ts +187 -0
  45. package/src/frontmatter.ts +20 -0
  46. package/src/installer.ts +118 -0
  47. package/src/manifest.ts +29 -0
  48. package/src/prompt.ts +37 -0
  49. package/src/targets/antigravity.ts +10 -0
  50. package/src/targets/claude.ts +25 -0
  51. package/src/targets/cline.ts +10 -0
  52. package/src/targets/codex.ts +10 -0
  53. package/src/targets/copilot.ts +26 -0
  54. package/src/targets/gemini.ts +10 -0
  55. package/src/targets/generic.ts +43 -0
  56. package/src/targets/index.ts +14 -0
  57. package/src/targets/kilo.ts +10 -0
  58. package/src/targets/opencode.ts +25 -0
  59. package/src/targets/windsurf.ts +10 -0
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: mugiwara-workflow
3
+ description: Use at the start of any non-trivial mission to run the Mugiwara crew harness - Luffy triage gateway first, then brainstorm, planning, execution, checkpoint, quality, gates, review, healing, and closure waves.
4
+ ---
5
+
6
+ # Mugiwara Workflow
7
+
8
+ The Straw Hat harness: Wave 0 triage + Waves 1-9, with an optional adversarial pass at Wave 4.5. Waves are phases of the mission, not files — Nami writes them into the plan doc, Zoro executes them. The harness always runs through Luffy unless the user summons a crew member directly.
9
+
10
+ ## Workspace layout
11
+
12
+ Every mission creates and works inside `.mugiwara/` at the repo root:
13
+
14
+ ```
15
+ .mugiwara/
16
+ ├── spec/ # brainstorm output: YYYY-MM-DD-<mission>.md
17
+ ├── plans/ # plan doc: YYYY-MM-DD-<mission>.md — single source of truth from Wave 2
18
+ ├── results/ # wave results: audit reports, quality/gate reports, test output
19
+ ├── review/ # review + security findings
20
+ ├── issues/ # blocker log: YYYY-MM-DD-<mission>-blockers.md
21
+ └── logs/ # Luffy's decision log
22
+ ```
23
+
24
+ The owning agent creates the folder it needs on first write. No mission artifacts go outside `.mugiwara/`.
25
+
26
+ ## Resume
27
+
28
+ At session start, after context loss, or on any "where were we?" — dispatch `resume-coordinator` (mugiwara-resume) BEFORE Wave 0 triage. It rebuilds the picture from disk (plan, todos, trace, blockers) and reports the resume point. Resume before any wave; never start over. Disk state is truth.
29
+
30
+ ## Wave 0 — Luffy Triage (always first)
31
+
32
+ Front door: dispatch `using-mugiwara` (easy to remember) — it routes to the right crew member and records the route. For a full triage dispatch `luffy-orchestrator`. NEVER start directly with brainstorming or planning. Luffy classifies every request 5 ways (Trivial / Explicit / Exploratory / Open-ended / Ambiguous) and routes: Trivial and Explicit → Wave 2 directly; Exploratory, Open-ended, and Ambiguous → Wave 1 brainstorm first. The user may summon any crew member directly — Luffy still records the route.
33
+
34
+ ## Waves
35
+
36
+ | Wave | Owner | Skill | Output |
37
+ |------|-------|-------|--------|
38
+ | 0 Triage | Luffy | mugiwara-orchestration | route decision + reason |
39
+ | 1 Brainstorm | Usopp | mugiwara-brainstorm | refined direction, options, recommendation |
40
+ | 2 Planning | Nami | mugiwara-planning | plan doc: waves/tasks/criteria, parallel markers |
41
+ | 3 Execution | Zoro | mugiwara-execution | implemented tasks with evidence |
42
+ | 4 Checkpoint | Chopper | mugiwara-checkpoint | audit report + failure ledger |
43
+ | 4.5 Adversarial | Skeptic | mugiwara-dynamic-workflow | findings report + failure ledger |
44
+ | 5 Quality | Sanji | mugiwara-quality | formatter/linter/test results |
45
+ | 6 Gates | Franky | mugiwara-gates | coverage + build verdict |
46
+ | 7 Review | Robin ∥ Jinbe | mugiwara-review + mugiwara-security | severity-tagged findings |
47
+ | 8 Healing | Brook | mugiwara-healing | fixes, then loop back to Wave 4 |
48
+ | 9 Closure | Luffy | mugiwara-orchestration | closure report appended to plan |
49
+
50
+ Wave 4.5 is optional — Luffy invokes Skeptic after Chopper on high-stakes missions (verdicts, plans, reviews), or parallel to Wave 7 review when he calls for it. Skip means recorded without a pass.
51
+
52
+ ## Blockers
53
+
54
+ Any agent that hits a blocker APPENDS a row to `.mugiwara/issues/YYYY-MM-DD-<mission>-blockers.md`:
55
+
56
+ `| <wave> | <task> | <symptom> | <attempted> | <help-needed> |`
57
+
58
+ Never silently work around a blocker. Brook reads this ledger at Wave 8 to decide what to heal; Luffy reviews it at every check-in.
59
+
60
+ ## Cleanup
61
+
62
+ At closure (Wave 9), delete unused intermediate markdown files in `.mugiwara/` — superseded results, review, and issues reports. Keep the plan doc and the closure report.
63
+
64
+ ## Rules
65
+
66
+ 1. Evidence over claims: no wave passes on assertion. The owning agent runs the checks and shows output.
67
+ 2. No wave skipped without the reason recorded in the plan doc.
68
+ 3. Heal loop is bounded: Wave 8 → Wave 4, max 3 cycles. After that, escalate to the human with full history.
69
+ 4. Any agent may consult Luffy mid-flight (re-dispatch `luffy-orchestrator`) for decisions and escalations.
70
+ 5. Wave 7 runs Robin and Jinbe in parallel.
71
+ 6. The plan doc (`.mugiwara/plans/YYYY-MM-DD-<mission>.md`) is the single source of truth from Wave 2 onward.
72
+ 7. Frontend-touching tasks in Wave 3 must apply `mugiwara-frontend` in the same pass.
73
+ 8. One agent may hold many skills (e.g. Usopp holds `mugiwara-brainstorm` + `mugiwara-frontend`; the crew is 11 members); dispatch the agent, not the skill.
74
+ 9. On session start, context loss, or "where were we?" — resume before any wave via `resume-coordinator` (mugiwara-resume); never start over.
75
+
76
+ ## Iron Law
77
+
78
+ EVIDENCE OVER CLAIMS. No wave passes on assertion — the owning agent runs the checks and shows output. A wave that cannot produce evidence is a failed wave.
79
+
80
+ ## Red flags
81
+
82
+ - A wave "passes" on a spoken claim with no command output or file to point at.
83
+ - Heal loop beyond 3 cycles with the same failure still open.
84
+ - A wave skipped with no reason recorded in the plan doc.
85
+ - Execution starts before triage (Wave 0), or planning before brainstorm when triage routed to Wave 1.
86
+ - Mission artifacts landing outside `.mugiwara/`.
87
+ - Wave order drifts from the table (e.g. quality before checkpoint).
88
+ - A blocker worked around silently with no ledger row.
89
+
90
+ All mean: stop the pipeline, diagnose with Chopper's ledger, decide continue / retry / escalate.
@@ -0,0 +1,602 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, realpathSync, rmSync as rmSync2 } from "node:fs";
5
+ import { homedir as homedir2 } from "node:os";
6
+ import { join as join7, resolve } from "node:path";
7
+ import { pathToFileURL } from "node:url";
8
+
9
+ // src/args.ts
10
+ var VALUE_FLAGS = { "--project": "project", "--target": "target", "--type": "type" };
11
+ var BOOL_FLAGS = {
12
+ "--global": "global",
13
+ "--yes": "yes",
14
+ "-y": "yes",
15
+ "--force": "force",
16
+ "--dry-run": "dryRun",
17
+ "--help": "help",
18
+ "-h": "help",
19
+ "--version": "version",
20
+ "-v": "version"
21
+ };
22
+ function parseArgs(argv) {
23
+ const out = { _: [], flags: {}, command: "install" };
24
+ for (let i = 0;i < argv.length; i++) {
25
+ const a = argv[i];
26
+ if (a in BOOL_FLAGS) {
27
+ out.flags[BOOL_FLAGS[a]] = true;
28
+ continue;
29
+ }
30
+ if (a in VALUE_FLAGS) {
31
+ const v = argv[++i];
32
+ if (v === undefined || v.startsWith("-"))
33
+ throw new Error(`Flag ${a} missing value`);
34
+ out.flags[VALUE_FLAGS[a]] = v;
35
+ continue;
36
+ }
37
+ if (a.startsWith("-"))
38
+ throw new Error(`Unknown flag: ${a}`);
39
+ out._.push(a);
40
+ }
41
+ out.command = out._[0] ?? "install";
42
+ return out;
43
+ }
44
+
45
+ // src/prompt.ts
46
+ import readline from "node:readline/promises";
47
+ function createRl() {
48
+ return readline.createInterface({ input: process.stdin, output: process.stdout });
49
+ }
50
+ async function choose(rl, question, options) {
51
+ for (;; ) {
52
+ console.log(`
53
+ ${question}`);
54
+ options.forEach((o, i) => console.log(` ${i + 1}) ${o}`));
55
+ const n = Number((await rl.question("choice> ")).trim());
56
+ if (Number.isInteger(n) && n >= 1 && n <= options.length)
57
+ return n - 1;
58
+ console.log(`Enter a number between 1 and ${options.length}.`);
59
+ }
60
+ }
61
+ async function multiChoose(rl, question, options) {
62
+ for (;; ) {
63
+ console.log(`
64
+ ${question} (comma-separated numbers, or "all")`);
65
+ options.forEach((o, i) => console.log(` ${i + 1}) ${o}`));
66
+ const raw = (await rl.question("choices> ")).trim().toLowerCase();
67
+ if (raw === "all")
68
+ return options.map((_, i) => i);
69
+ const nums = raw.split(",").map((s) => Number(s.trim()));
70
+ if (nums.length > 0 && nums.every((n) => Number.isInteger(n) && n >= 1 && n <= options.length)) {
71
+ return [...new Set(nums.map((n) => n - 1))];
72
+ }
73
+ console.log("Invalid selection.");
74
+ }
75
+ }
76
+ async function confirm(rl, question) {
77
+ const raw = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();
78
+ return raw === "y" || raw === "yes";
79
+ }
80
+
81
+ // src/targets/claude.ts
82
+ import { join } from "node:path";
83
+
84
+ // src/frontmatter.ts
85
+ function parseFrontmatter(text) {
86
+ const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
87
+ if (!m)
88
+ throw new Error("Missing frontmatter fence (---)");
89
+ const data = {};
90
+ for (const line of m[1].split(/\r?\n/)) {
91
+ if (!line.trim())
92
+ continue;
93
+ const i = line.indexOf(":");
94
+ if (i === -1)
95
+ throw new Error(`Bad frontmatter line: ${line}`);
96
+ data[line.slice(0, i).trim()] = line.slice(i + 1).trim();
97
+ }
98
+ return { data, body: text.slice(m[0].length) };
99
+ }
100
+ function stringifyFrontmatter(data, body) {
101
+ const lines = Object.entries(data).map(([k, v]) => `${k}: ${v}`);
102
+ return `---
103
+ ${lines.join(`
104
+ `)}
105
+ ---
106
+ ${body}`;
107
+ }
108
+
109
+ // src/targets/claude.ts
110
+ var target = {
111
+ id: "claude",
112
+ label: "Claude Code",
113
+ native: true,
114
+ paths({ scope, projectDir, home }) {
115
+ const root = scope === "global" ? join(home, ".claude") : join(projectDir, ".claude");
116
+ return { skillsDir: join(root, "skills"), agentsDir: join(root, "agents") };
117
+ },
118
+ transformSkill(data, body) {
119
+ return {
120
+ relPath: join(data.name, "SKILL.md"),
121
+ text: stringifyFrontmatter({ name: data.name, description: data.description }, body)
122
+ };
123
+ },
124
+ transformAgent(data, body) {
125
+ const fm = { name: data.name, description: data.description };
126
+ if (data.tools)
127
+ fm.tools = data.tools;
128
+ return { relPath: `${data.name}.md`, text: stringifyFrontmatter(fm, body) };
129
+ }
130
+ };
131
+
132
+ // src/targets/opencode.ts
133
+ import { join as join2 } from "node:path";
134
+ var target2 = {
135
+ id: "opencode",
136
+ label: "opencode",
137
+ native: true,
138
+ paths({ scope, projectDir, home }) {
139
+ const root = scope === "global" ? join2(home, ".config", "opencode") : join2(projectDir, ".opencode");
140
+ return { skillsDir: join2(root, "skills"), agentsDir: join2(root, "agents") };
141
+ },
142
+ transformSkill(data, body) {
143
+ return {
144
+ relPath: join2(data.name, "SKILL.md"),
145
+ text: stringifyFrontmatter({ name: data.name, description: data.description }, body)
146
+ };
147
+ },
148
+ transformAgent(data, body) {
149
+ const fm = { name: data.name, description: data.description };
150
+ if (data.tools)
151
+ fm.tools = data.tools;
152
+ return { relPath: `${data.name}.md`, text: stringifyFrontmatter(fm, body) };
153
+ }
154
+ };
155
+
156
+ // src/targets/copilot.ts
157
+ import { join as join3 } from "node:path";
158
+ var target3 = {
159
+ id: "copilot",
160
+ label: "GitHub Copilot",
161
+ native: true,
162
+ paths({ scope, projectDir, home }) {
163
+ const root = scope === "global" ? join3(home, ".copilot") : join3(projectDir, ".github");
164
+ return { skillsDir: join3(root, "instructions"), agentsDir: join3(root, "agents") };
165
+ },
166
+ transformSkill(data, body) {
167
+ return {
168
+ relPath: `${data.name}.instructions.md`,
169
+ text: stringifyFrontmatter({ description: data.description, applyTo: "**/*" }, body)
170
+ };
171
+ },
172
+ transformAgent(data, body) {
173
+ return {
174
+ relPath: `${data.name}.md`,
175
+ text: stringifyFrontmatter({ name: data.name, description: data.description }, body)
176
+ };
177
+ }
178
+ };
179
+
180
+ // src/targets/generic.ts
181
+ import { existsSync, writeFileSync } from "node:fs";
182
+ import { join as join4 } from "node:path";
183
+ function makeGeneric(opts) {
184
+ const { id, label, rulesDir, bootstrapFile, bootstrapPointer } = opts;
185
+ return {
186
+ id,
187
+ label,
188
+ native: false,
189
+ paths({ scope, projectDir }) {
190
+ if (scope === "global")
191
+ throw new Error(`${label} supports project scope only`);
192
+ const dir = join4(projectDir, rulesDir);
193
+ return { skillsDir: dir, agentsDir: dir };
194
+ },
195
+ transformSkill(data, body) {
196
+ return { relPath: `${data.name}.md`, text: `# ${data.name}
197
+
198
+ > ${data.description}
199
+
200
+ ${body}` };
201
+ },
202
+ transformAgent(data, body) {
203
+ return { relPath: `agent-${data.name}.md`, text: `# Agent: ${data.name}
204
+
205
+ > ${data.description}
206
+
207
+ Skills used: ${data.skills ?? ""}
208
+
209
+ ${body}` };
210
+ },
211
+ postInstall({ projectDir, dryRun }) {
212
+ if (!bootstrapFile)
213
+ return { written: [], notes: [] };
214
+ const notes = [];
215
+ const written = [];
216
+ const file = join4(projectDir, bootstrapFile);
217
+ if (!existsSync(file)) {
218
+ if (!dryRun)
219
+ writeFileSync(file, `${bootstrapPointer}
220
+ `);
221
+ written.push(file);
222
+ } else {
223
+ notes.push(`add this line to ${bootstrapFile} so the agent finds the crew: "${bootstrapPointer}"`);
224
+ }
225
+ return { written, notes };
226
+ }
227
+ };
228
+ }
229
+
230
+ // src/targets/gemini.ts
231
+ var target4 = makeGeneric({
232
+ id: "gemini",
233
+ label: "Gemini",
234
+ rulesDir: ".gemini/mugiwara",
235
+ bootstrapFile: "GEMINI.md",
236
+ bootstrapPointer: "Mugiwara crew installed in .gemini/mugiwara/ — read .gemini/mugiwara/mugiwara-workflow.md to run the pipeline."
237
+ });
238
+
239
+ // src/targets/codex.ts
240
+ var target5 = makeGeneric({
241
+ id: "codex",
242
+ label: "Codex",
243
+ rulesDir: ".codex/mugiwara",
244
+ bootstrapFile: "AGENTS.md",
245
+ bootstrapPointer: "Mugiwara crew installed in .codex/mugiwara/ — read .codex/mugiwara/mugiwara-workflow.md to run the pipeline."
246
+ });
247
+
248
+ // src/targets/windsurf.ts
249
+ var target6 = makeGeneric({
250
+ id: "windsurf",
251
+ label: "Windsurf",
252
+ rulesDir: ".devin/rules",
253
+ bootstrapFile: null,
254
+ bootstrapPointer: null
255
+ });
256
+
257
+ // src/targets/cline.ts
258
+ var target7 = makeGeneric({
259
+ id: "cline",
260
+ label: "Cline",
261
+ rulesDir: ".clinerules",
262
+ bootstrapFile: null,
263
+ bootstrapPointer: null
264
+ });
265
+
266
+ // src/targets/kilo.ts
267
+ var target8 = makeGeneric({
268
+ id: "kilo",
269
+ label: "Kilo Code",
270
+ rulesDir: ".kilo/rules",
271
+ bootstrapFile: "kilo.jsonc",
272
+ bootstrapPointer: `{
273
+ "instructions": [
274
+ ".kilo/rules/*.md"
275
+ ]
276
+ }`
277
+ });
278
+
279
+ // src/targets/antigravity.ts
280
+ var target9 = makeGeneric({
281
+ id: "antigravity",
282
+ label: "Antigravity",
283
+ rulesDir: ".agents/rules",
284
+ bootstrapFile: null,
285
+ bootstrapPointer: null
286
+ });
287
+
288
+ // src/targets/index.ts
289
+ var targets = { claude: target, opencode: target2, copilot: target3, gemini: target4, codex: target5, windsurf: target6, cline: target7, kilo: target8, antigravity: target9 };
290
+ var TARGET_IDS = Object.keys(targets);
291
+
292
+ // src/installer.ts
293
+ import { existsSync as existsSync2, mkdirSync, readFileSync, readdirSync, writeFileSync as writeFileSync2, copyFileSync, rmSync } from "node:fs";
294
+ import { dirname, join as join5 } from "node:path";
295
+ import { homedir } from "node:os";
296
+ var CONTENT_DIR = join5(import.meta.dirname, "..", "content");
297
+ var pkg = JSON.parse(readFileSync(join5(import.meta.dirname, "..", "package.json"), "utf8"));
298
+ var VERSION = pkg.version;
299
+ function collectContent({ includeFrontend }) {
300
+ const skillNames = readdirSync(join5(CONTENT_DIR, "skills"), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => includeFrontend || name !== "mugiwara-frontend");
301
+ const skills = skillNames.map((name) => {
302
+ const { data, body } = parseFrontmatter(readFileSync(join5(CONTENT_DIR, "skills", name, "SKILL.md"), "utf8"));
303
+ return { name, data, body };
304
+ });
305
+ const agents = readdirSync(join5(CONTENT_DIR, "agents")).filter((f) => f.endsWith(".md")).map((f) => {
306
+ const { data, body } = parseFrontmatter(readFileSync(join5(CONTENT_DIR, "agents", f), "utf8"));
307
+ return { name: f.replace(/\.md$/, ""), data, body };
308
+ });
309
+ return { skills, agents };
310
+ }
311
+ function installTo(target10, opts) {
312
+ const { scope, projectDir, type, dryRun = false, force = false } = opts;
313
+ const home = opts.home ?? homedir();
314
+ const { skills, agents } = collectContent({ includeFrontend: type === "frontend" || type === "fullstack" });
315
+ const dirs = target10.paths({ scope, projectDir, home });
316
+ const backupRoot = join5(scope === "global" ? home : projectDir, ".mugiwara");
317
+ const result = { written: [], skipped: [], backedUp: [], notes: [] };
318
+ const writeOne = (absPath, text) => {
319
+ if (existsSync2(absPath)) {
320
+ if (readFileSync(absPath, "utf8") === text) {
321
+ result.skipped.push(absPath);
322
+ return;
323
+ }
324
+ if (!force) {
325
+ result.skipped.push(absPath);
326
+ result.notes.push(`conflict (not overwritten; run update to replace with backup): ${absPath}`);
327
+ return;
328
+ }
329
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
330
+ const backupFile = join5(backupRoot, "backup", ts, absPath.replace(/[^a-zA-Z0-9]+/g, "_"));
331
+ if (!dryRun) {
332
+ mkdirSync(dirname(backupFile), { recursive: true });
333
+ copyFileSync(absPath, backupFile);
334
+ }
335
+ result.backedUp.push(absPath);
336
+ }
337
+ if (!dryRun) {
338
+ mkdirSync(dirname(absPath), { recursive: true });
339
+ writeFileSync2(absPath, text);
340
+ }
341
+ result.written.push(absPath);
342
+ };
343
+ for (const s of skills) {
344
+ const out = target10.transformSkill(s.data, s.body);
345
+ if (out)
346
+ writeOne(join5(dirs.skillsDir, out.relPath), out.text);
347
+ }
348
+ for (const a of agents) {
349
+ const out = target10.transformAgent(a.data, a.body);
350
+ if (out)
351
+ writeOne(join5(dirs.agentsDir, out.relPath), out.text);
352
+ }
353
+ if (target10.postInstall) {
354
+ const post = target10.postInstall({ scope, projectDir, home, dryRun, files: result.written });
355
+ result.written.push(...post.written);
356
+ result.notes.push(...post.notes);
357
+ }
358
+ return result;
359
+ }
360
+ function removeInstalled(manifest, { dryRun = false } = {}) {
361
+ const removed = [];
362
+ for (const f of manifest.files) {
363
+ if (existsSync2(f)) {
364
+ if (!dryRun)
365
+ rmSync(f);
366
+ removed.push(f);
367
+ }
368
+ }
369
+ if (!dryRun) {
370
+ for (const f of manifest.files) {
371
+ let d = dirname(f);
372
+ while (existsSync2(d) && readdirSync(d).length === 0) {
373
+ rmSync(d, { recursive: true });
374
+ const parent = dirname(d);
375
+ if (parent === d)
376
+ break;
377
+ d = parent;
378
+ }
379
+ }
380
+ }
381
+ return removed;
382
+ }
383
+
384
+ // src/manifest.ts
385
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
386
+ import { dirname as dirname2, join as join6 } from "node:path";
387
+ function manifestPath({ scope, projectDir, home }) {
388
+ return scope === "global" ? join6(home, ".mugiwara", "manifest.json") : join6(projectDir, ".mugiwara", "manifest.json");
389
+ }
390
+ function readManifest(file) {
391
+ return existsSync3(file) ? JSON.parse(readFileSync2(file, "utf8")) : null;
392
+ }
393
+ function writeManifest(file, data) {
394
+ mkdirSync2(dirname2(file), { recursive: true });
395
+ writeFileSync3(file, JSON.stringify(data, null, 2) + `
396
+ `);
397
+ }
398
+
399
+ // src/cli.ts
400
+ var TYPES = ["frontend", "backend", "fullstack", "general"];
401
+ var str = (v) => typeof v === "string" ? v : undefined;
402
+ var flag = (v) => v === true;
403
+ async function run(argv) {
404
+ const { command, flags } = parseArgs(argv);
405
+ if (flag(flags.help) || command === "help")
406
+ return help();
407
+ if (flag(flags.version)) {
408
+ console.log(`mugiwara ${VERSION}`);
409
+ return;
410
+ }
411
+ switch (command) {
412
+ case "install":
413
+ return install(flags);
414
+ case "update":
415
+ return install({ ...flags, force: true });
416
+ case "uninstall":
417
+ return uninstall(flags);
418
+ case "list":
419
+ return list(flags);
420
+ case "skills":
421
+ return skills();
422
+ default:
423
+ throw new Error(`Unknown command: ${command}`);
424
+ }
425
+ }
426
+ async function resolveOptions(flags) {
427
+ const interactive = !flag(flags.yes);
428
+ const rl = interactive ? createRl() : null;
429
+ try {
430
+ let scope = flag(flags.global) ? "global" : str(flags.project) ? "project" : null;
431
+ if (!scope) {
432
+ if (!interactive)
433
+ throw new Error("Specify --global or --project <dir> with --yes");
434
+ scope = await choose(rl, "Install scope?", ["global (user-wide)", "project (this repo)"]) === 0 ? "global" : "project";
435
+ }
436
+ const projectDir = resolve(str(flags.project) ?? process.cwd());
437
+ if (scope === "project" && !existsSync4(projectDir))
438
+ throw new Error(`Project dir not found: ${projectDir}`);
439
+ let targetIds = str(flags.target)?.split(",").map((s) => s.trim()) ?? null;
440
+ if (targetIds && targetIds.includes("all"))
441
+ targetIds = [...TARGET_IDS];
442
+ if (!targetIds) {
443
+ if (!interactive)
444
+ throw new Error("Specify --target <ids|all> with --yes");
445
+ const idx = await multiChoose(rl, "Target AI agents?", ["all targets", ...TARGET_IDS]);
446
+ targetIds = idx.includes(0) ? [...TARGET_IDS] : idx.map((i) => TARGET_IDS[i - 1]);
447
+ }
448
+ for (const id of targetIds) {
449
+ if (!targets[id])
450
+ throw new Error(`Unknown target: ${id} (valid: ${TARGET_IDS.join(", ")}, all)`);
451
+ }
452
+ let type = str(flags.type) ?? null;
453
+ if (!type) {
454
+ if (!interactive)
455
+ throw new Error("Specify --type with --yes");
456
+ type = TYPES[await choose(rl, "Project type?", TYPES)];
457
+ }
458
+ if (!TYPES.includes(type))
459
+ throw new Error(`Unknown type: ${type} (valid: ${TYPES.join(", ")})`);
460
+ return { scope, projectDir, targetIds, type };
461
+ } finally {
462
+ if (rl)
463
+ rl.close();
464
+ }
465
+ }
466
+ async function install(flags) {
467
+ const { scope, projectDir, targetIds, type } = await resolveOptions(flags);
468
+ const home = homedir2();
469
+ const allFiles = [];
470
+ const allNotes = [];
471
+ const installed = [];
472
+ for (const id of targetIds) {
473
+ const t = targets[id];
474
+ if (scope === "global" && !t.native) {
475
+ console.log(`! ${t.label}: project scope only — skipped for global install`);
476
+ continue;
477
+ }
478
+ installed.push(id);
479
+ console.log(`
480
+ -> ${t.label} (${scope})`);
481
+ const r = installTo(t, { scope, projectDir, type, home, dryRun: flag(flags.dryRun), force: flag(flags.force) });
482
+ console.log(` written ${r.written.length}, skipped ${r.skipped.length}, backed up ${r.backedUp.length}`);
483
+ for (const n of r.notes)
484
+ console.log(` note: ${n}`);
485
+ allFiles.push(...r.written);
486
+ allNotes.push(...r.notes);
487
+ }
488
+ if (flag(flags.dryRun)) {
489
+ console.log(`
490
+ Dry run — nothing written.`);
491
+ return;
492
+ }
493
+ const file = manifestPath({ scope, projectDir, home });
494
+ const prev = readManifest(file);
495
+ writeManifest(file, {
496
+ version: VERSION,
497
+ scope,
498
+ type,
499
+ installedAt: new Date().toISOString(),
500
+ targets: [...new Set([...prev?.targets ?? [], ...installed])],
501
+ files: [...new Set([...prev?.files ?? [], ...allFiles])]
502
+ });
503
+ console.log(`
504
+ OK mugiwara ${VERSION} installed (manifest: ${file})`);
505
+ if (allNotes.length)
506
+ console.log(`${allNotes.length} note(s) above may need attention.`);
507
+ }
508
+ async function uninstall(flags) {
509
+ const scope = flag(flags.global) ? "global" : "project";
510
+ const projectDir = resolve(str(flags.project) ?? process.cwd());
511
+ const home = homedir2();
512
+ const file = manifestPath({ scope, projectDir, home });
513
+ const manifest = readManifest(file);
514
+ if (!manifest) {
515
+ console.log("Nothing installed (no manifest found).");
516
+ return;
517
+ }
518
+ console.log(`Will remove ${manifest.files.length} files (targets: ${manifest.targets.join(", ")}).`);
519
+ if (!flag(flags.yes)) {
520
+ const rl = createRl();
521
+ const ok = await confirm(rl, "Proceed?");
522
+ rl.close();
523
+ if (!ok) {
524
+ console.log("Aborted.");
525
+ return;
526
+ }
527
+ }
528
+ const removed = removeInstalled(manifest, { dryRun: flag(flags.dryRun) });
529
+ if (!flag(flags.dryRun))
530
+ rmSync2(file);
531
+ console.log(`OK removed ${removed.length} files`);
532
+ }
533
+ function list(flags) {
534
+ const home = homedir2();
535
+ const projectDir = resolve(str(flags.project) ?? process.cwd());
536
+ let found = false;
537
+ for (const [label, file] of [
538
+ ["project", manifestPath({ scope: "project", projectDir, home })],
539
+ ["global", manifestPath({ scope: "global", projectDir, home })]
540
+ ]) {
541
+ const m = readManifest(file);
542
+ if (!m)
543
+ continue;
544
+ found = true;
545
+ console.log(`${label}: v${m.version} targets=${m.targets.join(",")} files=${m.files.length} installed=${m.installedAt}`);
546
+ }
547
+ if (!found)
548
+ console.log("No mugiwara installation found.");
549
+ }
550
+ function skills() {
551
+ const dir = join7(CONTENT_DIR, "skills");
552
+ const names = readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
553
+ const rows = names.map((name) => {
554
+ const { data } = parseFrontmatter(readFileSync3(join7(dir, name, "SKILL.md"), "utf8"));
555
+ return [name, data.description ?? ""];
556
+ });
557
+ const w = Math.max(...rows.map((r) => r[0].length)) + 2;
558
+ console.log(`mugiwara ${VERSION} — ${rows.length} skills (agentskills.io format):
559
+ `);
560
+ for (const [name, description] of rows)
561
+ console.log(` ${name.padEnd(w)}${description}`);
562
+ console.log(`
563
+ Install skills into any agent via skills.sh:
564
+ npx skills add ionivetech/mugiwara`);
565
+ }
566
+ function help() {
567
+ console.log(`mugiwara ${VERSION} — the Straw Hat crew for AI agents
568
+
569
+ Usage:
570
+ mugiwara [install] install the crew (default; wizard when flags missing)
571
+ mugiwara update replace existing files (backs up differences first)
572
+ mugiwara uninstall remove installed files via manifest
573
+ mugiwara list show installations
574
+ mugiwara skills list installable skills (agentskills.io)
575
+ mugiwara --help this help
576
+ mugiwara --version print version
577
+
578
+ Flags:
579
+ --global user-wide install
580
+ --project <dir> project install (default: cwd)
581
+ --target <ids|all> comma-separated: ${TARGET_IDS.join(", ")}
582
+ --type <t> frontend | backend | fullstack | general
583
+ --yes, -y non-interactive (needs --global/--project, --target, --type)
584
+ --force overwrite differing files (with backup)
585
+ --dry-run print actions without writing`);
586
+ }
587
+ var entry = process.argv[1] !== undefined ? resolve(process.argv[1]) : undefined;
588
+ if (entry !== undefined) {
589
+ try {
590
+ entry = realpathSync(entry);
591
+ } catch {}
592
+ }
593
+ var isMain = entry !== undefined && import.meta.url === pathToFileURL(entry).href;
594
+ if (isMain) {
595
+ run(process.argv.slice(2)).catch((err) => {
596
+ console.error(`mugiwara: ${err.message}`);
597
+ process.exit(1);
598
+ });
599
+ }
600
+ export {
601
+ run
602
+ };