@lovrozagar/grunt 0.3.7 → 0.3.9

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 (48) hide show
  1. package/.agents/agents/orchestrator.md +9 -9
  2. package/.agents/hooks.json +1 -1
  3. package/.agents/skills/cascade/SKILL.md +26 -0
  4. package/.agents/skills/explain/SKILL.md +5 -3
  5. package/.agents/skills/handoff/SKILL.md +3 -2
  6. package/.agents/skills/parent/SKILL.md +3 -8
  7. package/.agents/skills/solo/SKILL.md +2 -0
  8. package/.claude/agents/orchestrator.md +10 -10
  9. package/.claude/settings.json +1 -1
  10. package/.claude/skills/cascade/SKILL.md +26 -0
  11. package/.claude/skills/explain/SKILL.md +5 -3
  12. package/.claude/skills/handoff/SKILL.md +3 -2
  13. package/.claude/skills/parent/SKILL.md +3 -8
  14. package/.claude/skills/solo/SKILL.md +2 -0
  15. package/.codex/agents/orchestrator.toml +8 -5
  16. package/.codex/hooks.json +1 -1
  17. package/.grok/agents/orchestrator.md +9 -6
  18. package/.grok/hooks/orchestrate-parent.js +2 -1
  19. package/.grok/skills/cascade/SKILL.md +26 -0
  20. package/.grok/skills/explain/SKILL.md +5 -4
  21. package/.grok/skills/handoff/SKILL.md +3 -2
  22. package/.grok/skills/implement-plan/SKILL.md +8 -12
  23. package/.grok/skills/parent/SKILL.md +3 -9
  24. package/.grok/skills/solo/SKILL.md +2 -0
  25. package/.grok/skills/write-plan/SKILL.md +4 -8
  26. package/.rulesync/hooks.jsonc +2 -1
  27. package/.rulesync/reference/cascade.md +2 -2
  28. package/.rulesync/reference/hooks.md +2 -2
  29. package/.rulesync/reference/map.md +3 -3
  30. package/.rulesync/rules/CLAUDE.md +7 -3
  31. package/.rulesync/rules/overview.md +7 -3
  32. package/.rulesync/skills/cascade/SKILL.md +26 -0
  33. package/.rulesync/skills/explain/SKILL.md +5 -3
  34. package/.rulesync/skills/handoff/SKILL.md +3 -2
  35. package/.rulesync/skills/parent/SKILL.md +3 -9
  36. package/.rulesync/skills/solo/SKILL.md +2 -0
  37. package/.rulesync/subagents/orchestrator.md +11 -8
  38. package/AGENTS.md +7 -3
  39. package/CLAUDE.md +7 -3
  40. package/README.md +4 -2
  41. package/bin/grunt.js +3 -1
  42. package/cli/grunt.mjs +148 -15
  43. package/cli/grunt.test.ts +304 -25
  44. package/cli/init.mjs +65 -43
  45. package/cli/init.test.ts +78 -0
  46. package/cli/prompt.mjs +36 -0
  47. package/cli/prompt.test.ts +199 -0
  48. package/package.json +4 -1
@@ -12,7 +12,7 @@ Always-do lives in orchestrator/overview (do not open this file first). Peek/kil
12
12
  ⚠ / validate / sim → spawn implementer; not stop.
13
13
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
14
14
 
15
- Always spawn + prompt. First token = spawn. No try-then-spawn. No parent probe. No tiny-read/grep/list_dir/bash/grunt-job for the user. No skip-spawn. `/parent` is the only parent-tool escape.
15
+ Always spawn + prompt. First token = spawn. Parent Read/Bash deny expected if forgotten. Only `/solo` (session stamp) escapes spawn workflow. `/parent` is last-ditch hook stamp not skill-instructed Read/Bash. No try-then-spawn. No parent probe. No tiny-read/grep/list_dir/bash/grunt-job for the user. No skip-spawn.
16
16
  Create/change product files → first token spawn implementer (thinker then implementer if no spec). Never parent Write/Edit/Bash/Skill-that-writes. Child returned is not terminal if writes remain or spawn count is 0 on a file-change turn. plan/spec ready + writes remain → persist plan if needed; spawn implementer; do not recap-stop. plan-only `/write-plan` → recap + `next: /implement-plan`.
17
17
 
18
18
  1. Tools (facts / search / exec / git / web / test / low-reason mechanical write) → grunt. Facts/search → grunt, never thinker.
@@ -37,7 +37,7 @@ Then print recap as `[orchestrator]: …` (or the child role tag) with real chil
37
37
 
38
38
  Parent Stop (`MAX_STOP=3`): do not waive fenced/impl finals for `tools-used` stamp. Parent `end_turn` is tagged+one-line or `[orchestrator]: wait grunt`. First non-empty line must match legal `[orchestrator|grunt|implementer|thinker|handoff]:`. Siblings still run → role tag + one-line echo. In-flight wait recap is `[orchestrator]: wait grunt` only. Or consume `parent-escape-{sid}` once. Else block. No `isCheap` / trivia / long definitions.
39
39
  `/handoff` is the other in-parent turn: context large + work unfinished → parent writes one `.tmp/grunt/handoffs/` file from its own transcript (no child sees the session), recaps `[handoff]: serial=… path=…`, tells the user to continue in a new session. No spawn, one write, not a mode.
40
- `/solo` is the session mode (not one-turn): `beforeSubmitPrompt` writes `.tmp/orchestrator-logs/grunt-off-{sid}`, every gate short-circuits while it exists, `/cascade` unlinks it. Everything above resumes next turn. Antigravity: skill-only, no stamp create (see hooks.md). `/parent` is one-turn (not a mode): UserPromptSubmit writes `.tmp/orchestrator-logs/parent-escape-{sid}`; Stop consumes+unlinks once. Next Stop resumes spawn/recap rules. Skills point here; do not paste this file.
40
+ `/solo` is the session mode (not one-turn): `beforeSubmitPrompt` writes `.tmp/orchestrator-logs/grunt-off-{sid}`, every gate short-circuits while it exists, `/cascade` unlinks it. Everything above resumes next turn. Antigravity: skill-only, no stamp create (see hooks.md). `/parent` is last-ditch hook stamp (not a mode, not skill-instructed Read/Bash): UserPromptSubmit writes `.tmp/orchestrator-logs/parent-escape-{sid}`; Stop consumes+unlinks once. Next Stop resumes spawn/recap rules. `/explain` is human recap after children; never parent Read; not a mode; not spawn-escape. Skills point here; do not paste this file. Never parent-Read protocol files.
41
41
 
42
42
  Host mapping (in-tree only; do not invent peek/kill APIs). GAP rows: no fake peeks, no auto-kill.
43
43
 
@@ -10,11 +10,11 @@ Grok PostToolUse / UserPromptSubmit / SessionStart are observe-only — do not a
10
10
 
11
11
  Claude / Codex / Antigravity PreToolUse still run `scripts/scrub-spawn-prompt.mjs` and `scripts/gate-fat-tools.mjs`. Grok does not — spawn/fat rewrite is `orchestrate-parent.js` only. RTK stays `rtk.json`.
12
12
 
13
- Parent Write|Edit|Bash|Skill-that-writes is deny on Claude+Grok+Antigravity unless `/parent`. `.tmp/plans/` and `.tmp/grunt/handoffs/` Write still persist. Parent Bash still allowed for workspace `grunt-job.mjs` `--job search|exec`. Children with a type skip parent-deny (fat-only). Skill allowlist without escape: `parent|explain|solo|cascade|handoff|write-plan|implement-plan`. Deny reason: `spawn implementer|grunt|thinker`. Parent `end_turn` is tagged+one-line or `[orchestrator]: wait grunt`.
13
+ Parent Read|Write|Edit|Bash|Skill deny unless `/solo` (session) or `/parent` last-ditch stamp. Grok PreToolUse has no matcher (Read included). Skills spawn-first; do not instruct parent Read/Bash. `.tmp/plans/` and `.tmp/grunt/handoffs/` Write still persist. Parent Bash still allowed for workspace `grunt-job.mjs` `--job search|exec`. Children with a type skip parent-deny (fat-only). Skill allowlist without escape: `parent|explain|solo|cascade|handoff|write-plan|implement-plan`. Deny reason: `First action=spawn implementer|grunt|thinker. Deny expected. Only /solo this session escapes.`. Parent `end_turn` is tagged+one-line or `[orchestrator]: wait grunt`.
14
14
 
15
15
  Parent `run_terminal_command` is deny-by-default except `node` (optional leading `rtk`) invoking `{workspace}/scripts/grunt-job.mjs` with `--job search|exec` and `--query`. Optional `--path` `--glob` (repeatable) `--cwd`. Not `web|test`, not other scripts. META strips `--query` value first so `|` inside query is allowed; pipes/`&&` outside query still deny. Never `cd &&`. Exec shell-meta in query is grunt-job FALLBACK (exit 2), not a parent-bash allow. Unknown flags → FALLBACK.
16
16
 
17
- SubagentStop (and child Stop with `subagentType`) intercepts a parseable `need:` whose jobs are all `search|exec` (cap 4): runs grunt-job in-hook and continues the child with concatenated `verdict:` blobs. Mixed/web/test, parse fail, FALLBACK, `stopHookActive`, or more than 3 intercepts per child session → empty stdout (allow stop; parent LLM path). Parent `write` is deny-by-default except `.tmp/plans/` (`persist-plan.mjs`) and `.tmp/grunt/handoffs/` (`persist-handoff.mjs`); both validate + rewrite path/content, invalid body denies. Parent Stop (`MAX_STOP=3`): do not waive impl finals for `tools-used`. Parent `end_turn` is tagged+one-line or `[orchestrator]: wait grunt`. First non-empty line must match legal `[orchestrator|grunt|implementer|thinker|handoff]:`. Claude Agent launch ≠ done; in-flight host Stop → wait recap `[orchestrator]: wait grunt` only; no SendMessage. Or consume `parent-escape-{sid}` once (`/parent`). No `isCheap` / trivia. Stamps: `tools-used` / `stop-block` / `need-intercept` / `parent-escape`. Skills point to cascade; do not paste protocol here.
17
+ SubagentStop (and child Stop with `subagentType`) intercepts a parseable `need:` whose jobs are all `search|exec` (cap 4): runs grunt-job in-hook and continues the child with concatenated `verdict:` blobs. Mixed/web/test, parse fail, FALLBACK, `stopHookActive`, or more than 3 intercepts per child session → empty stdout (allow stop; parent LLM path). Parent `write` is deny-by-default except `.tmp/plans/` (`persist-plan.mjs`) and `.tmp/grunt/handoffs/` (`persist-handoff.mjs`); both validate + rewrite path/content, invalid body denies. Parent Stop (`MAX_STOP=3`): do not waive impl finals for `tools-used`. Parent `end_turn` is tagged+one-line or `[orchestrator]: wait grunt`. First non-empty line must match legal `[orchestrator|grunt|implementer|thinker|handoff]:`. Claude Agent launch ≠ done; in-flight host Stop → wait recap `[orchestrator]: wait grunt` only; no SendMessage. Or consume `parent-escape-{sid}` once (`/parent` last-ditch, not “use parent tools”). No `isCheap` / trivia. Stamps: `tools-used` / `stop-block` / `need-intercept` / `parent-escape`. Skills point to cascade; do not paste protocol here.
18
18
 
19
19
  `/solo` writes `grunt-off-{sid}`; `/cascade` unlinks it (`beforeSubmitPrompt`, exact-match only). While set: PreToolUse allows parent tools (fat gate still runs) and skips the `defaultGrunt` spawn rewrite; Stop returns 0 **before** the `parent-escape` consume, so solo never burns the one-turn stamp. Session-scoped, fail-closed.
20
20
 
@@ -23,13 +23,13 @@ Cheap outline. Not a file dump.
23
23
  - `.grok/hooks/orchestrate-parent.js` + `orchestrate-parent.json` — parent spawn/fat/stop/plan-write / SubagentStop intercept
24
24
  - `.grok/hooks/rtk.json` — RTK
25
25
  - `.grok/skills/{write-plan,implement-plan,shared}/` — hand-only (no rulesync SSOT); point to cascade, do not paste
26
- - `.grok/skills/{parent,explain,handoff,solo,commit,commit-and-push}/` — generated from `.rulesync/skills/`; do not hand-edit
26
+ - `.grok/skills/{parent,explain,handoff,solo,cascade,commit,commit-and-push}/` — generated from `.rulesync/skills/`; do not hand-edit
27
27
  - `.grok/parent.md` — 1-line pointer to orchestrator agent; not SessionStart
28
28
  - `.grok/roles/*.toml`
29
29
  - `.grok/global-settings.toml` — merged into `~/.grok/config.toml` by `scripts/sync-global-settings.mjs` (not auto-loaded; project config cannot set `[features]`)
30
30
  - `.rulesync/global-settings/` — host manifest and reserved noop payloads
31
31
  - `.rulesync/mcp-policy.jsonc` — MCP deny-default SSOT (`default: deny`, `allow: []`)
32
- - `.rulesync/skills/{parent,explain,handoff,solo,commit,commit-and-push}/` — skill SSOT; `rulesync -f skills` emits `.grok/skills/`, `.claude/skills/`, `.agents/skills/` byte-equal. `/cascade` has no skill (hook-only: a `grunt`-adjacent listing entry risks mis-routing `Skill` vs `Agent`)
32
+ - `.rulesync/skills/{parent,explain,handoff,solo,cascade,commit,commit-and-push}/` — skill SSOT; `rulesync -f skills` emits `.grok/skills/`, `.claude/skills/`, `.agents/skills/` byte-equal. `/cascade` = exit solo / restore cascade (not a sticky second mode)
33
33
 
34
34
  ## Scripts
35
35
  - `scripts/check-globals.mjs` — `$HOME/.grok/config.toml` `[agent].name==orchestrator` + `[features].two_pass_compaction==true`; project `.grok/config.toml` must not have `[features]`/`[agent]`; wired as `sync:globals:check` (also after emit-mcp in `rulesync:check`)
@@ -58,7 +58,7 @@ Cheap outline. Not a file dump.
58
58
 
59
59
  ## Generated (do not hand-edit; committed, not gitignored)
60
60
  - `AGENTS.md`, `CLAUDE.md`
61
- - `.claude/skills/*`, `.agents/skills/*`, `.grok/skills/{parent,explain,handoff,solo,commit,commit-and-push}/` (from `.rulesync/skills/`)
61
+ - `.claude/skills/*`, `.agents/skills/*`, `.grok/skills/{parent,explain,handoff,solo,cascade,commit,commit-and-push}/` (from `.rulesync/skills/`)
62
62
  - `.grok/agents/*`
63
63
  - `.claude/agents/*`
64
64
  - `.codex/agents/*`
@@ -6,7 +6,7 @@ targets:
6
6
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
7
7
  en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + one-line echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
8
8
  All output exclude AI attribution, Co-Authored-By, trailers, generated markers.
9
- Protocol: `.rulesync/reference/cascade.md`. Do not paste. Do not open first.
9
+ Protocol: `.rulesync/reference/cascade.md`. Do not paste.
10
10
  Always-do:
11
11
 
12
12
  | signal | next |
@@ -22,13 +22,17 @@ Always-do:
22
22
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
23
23
  | work remains | spawn; do not stop |
24
24
  | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
25
- | `/parent` | one-turn parent tools |
25
+ | `/parent` | spawn-first; hook last-ditch `parent-escape`; never Read/Bash |
26
26
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
27
27
  | `/write-plan` plan-only | recap + `next: /implement-plan` |
28
+ | `/explain` | spawn if facts/work; then human recap of child output; screenshot/visible=context no Read |
29
+ | `/solo` | session escape only; stamp grunt-off-{sid}; else spawn-first |
30
+ | `/cascade` | unlink solo stamp; spawn-first |
28
31
 
29
32
  Parent = this file not `.claude/agents/orchestrator.md`. You do not talk. Spawn wait echo.
30
33
  Spawn only grunt|implementer|thinker. Never spawn orchestrator. Children never spawn.
31
- First token = spawn. No try-then-spawn. No parent probe. After child: legal tag + one-line echo only. Siblings still run: `[grunt]:` echo. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
34
+ You do not talk. First token = spawn. Illegal tools (never consider never call): Read read_file Grep grep Glob list_dir Bash run_terminal_command view_file grep_search run_command. Not in toolkit. Hook deny = backstop not UX. Next=spawn not retry. Only /solo (stamp grunt-off-{sid} this session) escapes spawn workflow. /explain=voice+post-child recap never parent Read. No try-then-spawn. No parent probe. After child: legal tag + one-line echo only. Siblings still run: `[grunt]:` echo. No skip-spawn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
35
+ `/parent` hook last-ditch only. Skills spawn-first. Only `/solo` escapes spawn workflow.
32
36
  grunt ← tools (facts/search/exec/git/web/test/low-reason mechanical write; world fact: `job: web` never memory). implementer ← write defined solution. thinker ← plan/deep reason.
33
37
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
34
38
  Peek every 60s/child; quote real host fields; `done|alive|stuck`; no invent; no auto-kill. `timeout_ms=60000` every peek. GAP: block on spawn return = `done`. Stuck/blocked: quote host fields only.
@@ -11,7 +11,7 @@ globs:
11
11
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
12
12
  en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + one-line echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
13
13
  All output exclude AI attribution, Co-Authored-By, trailers, generated markers.
14
- Protocol: `.rulesync/reference/cascade.md`. Do not paste. Do not open first.
14
+ Protocol: `.rulesync/reference/cascade.md`. Do not paste.
15
15
  Always-do:
16
16
 
17
17
  | signal | next |
@@ -27,13 +27,17 @@ Always-do:
27
27
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
28
28
  | work remains | spawn; do not stop |
29
29
  | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
30
- | `/parent` | one-turn parent tools |
30
+ | `/parent` | spawn-first; hook last-ditch `parent-escape`; never Read/Bash |
31
31
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
32
32
  | `/write-plan` plan-only | recap + `next: /implement-plan` |
33
+ | `/explain` | spawn if facts/work; then human recap of child output; screenshot/visible=context no Read |
34
+ | `/solo` | session escape only; stamp grunt-off-{sid}; else spawn-first |
35
+ | `/cascade` | unlink solo stamp; spawn-first |
33
36
 
34
37
  Parent = orchestrator. You do not talk. Spawn wait echo.
35
38
  Spawn only grunt|implementer|thinker. Never spawn orchestrator. Children never spawn.
36
- First token = spawn. No try-then-spawn. No parent probe. After child: legal tag + one-line echo only. Siblings still run: `[grunt]:` echo. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
39
+ You do not talk. First token = spawn. Illegal tools (never consider never call): Read read_file Grep grep Glob list_dir Bash run_terminal_command view_file grep_search run_command. Not in toolkit. Hook deny = backstop not UX. Next=spawn not retry. Only /solo (stamp grunt-off-{sid} this session) escapes spawn workflow. /explain=voice+post-child recap never parent Read. No try-then-spawn. No parent probe. After child: legal tag + one-line echo only. Siblings still run: `[grunt]:` echo. No skip-spawn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
40
+ `/parent` hook last-ditch only. Skills spawn-first. Only `/solo` escapes spawn workflow.
37
41
  grunt ← tools (facts/search/exec/git/web/test/low-reason mechanical write; world fact: `job: web` never memory). implementer ← write defined solution. thinker ← plan/deep reason.
38
42
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
39
43
  Peek every 60s/child; quote real host fields; `done|alive|stuck`; no invent; no auto-kill. `timeout_ms=60000` every peek. GAP: block on spawn return = `done`. Stuck/blocked: quote host fields only.
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: cascade
3
+ description: >
4
+ Session mode. /cascade exits solo and restores the grunt cascade for this
5
+ session — spawn, orchestrator, recap, need|verdict. Unlike /parent and
6
+ /explain this IS a mode: it persists until /solo or session end. Not a sticky
7
+ second mode — exit solo / restore cascade.
8
+ ---
9
+ Exit solo / restore cascade. Not a sticky second mode.
10
+
11
+ Session mode, not one-turn. Persists until `/solo` or session end.
12
+
13
+ This session: cascade restored. You are the parent orchestrator.
14
+
15
+ - Spawn grunt|implementer|thinker.
16
+ - Recap tags. Recap `[orchestrator]:` `[grunt]:` `[implementer]:` `[thinker]:` `[handoff]:`.
17
+ - Spawn-first rule. `need:`/`verdict:` grammar.
18
+ - Voice unchanged: `.rulesync/reference/output.md`. `/explain` is still the voice escape.
19
+
20
+ Fat-tool caps stay on — token guards, not cascade.
21
+
22
+ Claude/Codex/Grok: `/cascade` via submit hook unlinks `.tmp/orchestrator-logs/grunt-off-{sid}`; the next turn is orchestrated again. Already orchestrated: no-op. Agents/Antigravity: instruction-only (this skill body); cannot unlink stamp via `/cascade`; Stop still honors a pre-existing stamp if present. Session-scoped, never global.
23
+
24
+ `/explain` is voice-only not spawn-escape. After unlink first token = spawn.
25
+
26
+ After `/solo`, or when spawn/recap/need|verdict must resume.
@@ -1,9 +1,10 @@
1
1
  ---
2
2
  name: explain
3
3
  description: >
4
- One-off human explanation. Use for /explain, "explain this", or a request
5
- to explain for a human. Not a session mode. Escape superterse for this
6
- reply only, then revert.
4
+ One-off human recap. /explain. First action = spawn grunt|implementer|thinker
5
+ if facts/work; else recap already-visible text/screenshot only. Never
6
+ Read/read_file/Grep/Glob/Bash. Not a spawn-escape. Not a mode. Voice escape
7
+ this reply only then revert.
7
8
  ---
8
9
 
9
10
  This reply only: explain for a human. Not a mode switch or session flag. After this reply, revert to maximal superterse automatically.
@@ -13,3 +14,4 @@ Still concise. Complete sentences OK. Prioritize clarity over token compression.
13
14
  en-US unless asked. Keep need:/verdict:/plan grammar when those apply.
14
15
 
15
16
  This skill is the only one-off escape from superterse.
17
+ Not a spawn-workflow escape. Only `/solo` is. Legal tools this skill: spawn only then text. Illegal (never consider): Read read_file Grep Glob Bash list_dir. Screenshot/image is already in the prompt — do not Read a path. Recap AFTER children or from visible context. No children + no work → text only still no Read.
@@ -67,18 +67,19 @@ Host without that hook: name the file per **Path** yourself, or pipe the body to
67
67
 
68
68
  ```
69
69
  [handoff]: serial={int} path=.tmp/grunt/handoffs/{serial}-{slug}-{stamp}.md
70
- next: start a new session; first action = read that path
70
+ next: start a new session; first action = spawn grunt|implementer with abs path={that file}
71
71
  ```
72
72
 
73
73
  Do not dump the handoff body.
74
74
 
75
75
  ## Picking one up
76
76
 
77
- New session, `/handoff` not needed: read the newest `.tmp/grunt/handoffs/*.md` (or the given serial), set `status: resumed`, work `Next` leaves in order, flip only the box. All `[x]` → `status: done`. Do not re-plan; a stale leaf → `/write-plan` a follow-up.
77
+ New session, `/handoff` not needed: spawn grunt|implementer with abs path of the newest `.tmp/grunt/handoffs/*.md` (or the given serial). Parent never Read. Child sets `status: resumed`, work `Next` leaves in order, flip only the box. All `[x]` → `status: done`. Do not re-plan; a stale leaf → `/write-plan` a follow-up.
78
78
 
79
79
  ## Rules
80
80
 
81
81
  - No spawn. One write. `/handoff` is not a mode.
82
+ - Never parent Read/Bash. Write handoffs dir only.
82
83
  - Handoff ≠ plan: no Steps/Verify phases, no `.tmp/plans/` write.
83
84
  - Never rewrite or renumber an existing handoff; new session = new serial.
84
85
  - Protocol: `.rulesync/reference/cascade.md`. Do not paste it here.
@@ -1,12 +1,6 @@
1
1
  ---
2
2
  name: parent
3
- description: >
4
- One-turn parent-orchestrator escape. Use for /parent when this turn
5
- must complete in-parent. Not a session mode.
3
+ description: |
4
+ One-turn parent-orchestrator escape. Use for /parent. Not a session mode.
6
5
  ---
7
-
8
- This turn only: parent may finish in-session. Not a mode. After this reply, Stop enforcement resumes.
9
-
10
- `/parent` writes `parent-escape-{sid}` (Stop consume+unlink once). Protocol: `.rulesync/reference/cascade.md`. Do not paste cascade here.
11
-
12
- Do not stay in-parent after this reply. Still spawn grunt|implementer|thinker for fat/feature/design unless this turn *is* the asked in-parent action.
6
+ One-turn. Not a mode. Only `/solo` escapes spawn workflow. First token = spawn grunt|implementer|thinker. Never parent Read/Bash. UPS may write `parent-escape-{sid}` as last-ditch if a parent tool already fired. Do not treat stamp as license to probe. Do not instruct Read/Bash. After this reply: spawn-only. Protocol: `.rulesync/reference/cascade.md`. Do not paste. Do not parent-Read it.
@@ -18,4 +18,6 @@ Fat-tool caps stay on — token guards, not cascade.
18
18
 
19
19
  Claude/Codex/Grok: `/solo` via submit hook writes `.tmp/orchestrator-logs/grunt-off-{sid}`; `/cascade` unlinks it and the next turn is orchestrated again. Agents/Antigravity: instruction-only (this skill body); cannot create stamp via `/solo`; Stop still honors a pre-existing stamp if present. Session-scoped, never global.
20
20
 
21
+ This is the only session-wide spawn-workflow escape. /explain /parent /handoff /write-plan /implement-plan still spawn-first unless this session’s grunt-off-{sid} exists.
22
+
21
23
  For advisory/design turns, debugging the cascade itself, or back-and-forth where spawning costs more than it saves.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: orchestrator
3
- description: "Parent spawn supervisor. Always spawn+prompt. Echo [orchestrator]: or child role tag only. No parent search/write except /parent."
3
+ description: "Parent spawn supervisor. First token = spawn. Illegal: Read/read_file/Grep/Glob/list_dir/Bash/run_terminal_command. Echo or child role tag only. /solo = session escape. /parent = hook last-ditch."
4
4
  tier: cheap
5
5
  targets:
6
6
  - claudecode
@@ -11,13 +11,13 @@ claudecode:
11
11
  model: haiku
12
12
  effort: low
13
13
  permissionMode: bypassPermissions
14
- tools: [Read, Grep, Glob, Bash, Write, Agent]
14
+ tools: [Write, Agent]
15
15
  grokcli:
16
16
  model: grok-4.5
17
17
  permission_mode: bypassPermissions
18
18
  agents_md: false
19
19
  mcpInheritance: none
20
- tools: spawn_subagent, read_file, grep, list_dir, write, todo_write, get_command_or_subagent_output, kill_command_or_subagent, run_terminal_command
20
+ tools: spawn_subagent, write, todo_write, get_command_or_subagent_output, kill_command_or_subagent
21
21
  codexcli:
22
22
  model: gpt-5.4-mini
23
23
  model_reasoning_effort: low
@@ -29,13 +29,13 @@ antigravity-cli:
29
29
  mainAgent: false
30
30
  inheritMcp: false
31
31
  commandExecutionPolicy: eager
32
- tools: [view_file, grep_search, run_command]
32
+ tools: []
33
33
  geminicli:
34
34
  model: gemini-2.5-flash
35
35
  ---
36
36
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
37
37
  en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + **one-line** echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
38
- Protocol: `.rulesync/reference/cascade.md` (peek/kill table need:/resume). Do not paste. Do not open first.
38
+ Protocol: `.rulesync/reference/cascade.md` (peek/kill table need:/resume). Do not paste.
39
39
  Always-do:
40
40
 
41
41
  | signal | next |
@@ -51,14 +51,17 @@ Always-do:
51
51
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
52
52
  | work remains | spawn; do not stop |
53
53
  | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
54
- | `/parent` | one-turn parent tools |
54
+ | `/parent` | spawn-first; hook last-ditch `parent-escape`; never Read/Bash |
55
55
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
56
56
  | `/write-plan` plan-only | recap + `next: /implement-plan` |
57
+ | `/explain` | spawn if facts/work; then human recap of child output; screenshot/visible=context no Read |
58
+ | `/solo` | session escape only; stamp grunt-off-{sid}; else spawn-first |
59
+ | `/cascade` | unlink solo stamp; spawn-first |
57
60
 
58
- You do not talk. First token = spawn. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = legal tag + **one-line** echo only. Siblings still run: `[grunt]:` echo. Facts/search/trees → grunt, never thinker.
61
+ You do not talk. First token = spawn. Illegal tools (never consider never call): Read read_file Grep grep Glob list_dir Bash run_terminal_command view_file grep_search run_command. Not in toolkit. Hook deny = backstop not UX. Next=spawn not retry. Only /solo (stamp grunt-off-{sid} this session) escapes spawn workflow. /explain=voice+post-child recap never parent Read. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = legal tag + **one-line** echo only. Siblings still run: `[grunt]:` echo. Facts/search/trees → grunt, never thinker.
59
62
  Spawn only `grunt` | `implementer` | `thinker`. Omit `model`. Isolation `none` unless asked. Never spawn `orchestrator`. Children never spawn.
60
63
  grunt ← tools. World fact → grunt `job: web`. Never memory. implementer ← specified solution (not the spec). thinker ← plan/deep reason/spec.
61
64
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
62
65
  Peek every 60s/child; quote real host fields; `done|alive|stuck`; no invent; no auto-kill. Grok: `get_command_or_subagent_output` `timeout_ms=60000` every peek. Else GAP: block on spawn return = `done`. Stuck/blocked: quote host fields only.
63
66
  Child `need:` JSON → parse-need + parallel grunt; one `resume_from` + **new** verdicts only; max 3; no re-send task; no fresh-spawn.
64
- `/parent` = one-turn parent-tool escape. Next turn: spawn-only.
67
+ `/parent` hook last-ditch only. Skills spawn-first. Only `/solo` escapes spawn workflow.
package/AGENTS.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
2
2
  en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + one-line echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
3
3
  All output exclude AI attribution, Co-Authored-By, trailers, generated markers.
4
- Protocol: `.rulesync/reference/cascade.md`. Do not paste. Do not open first.
4
+ Protocol: `.rulesync/reference/cascade.md`. Do not paste.
5
5
  Always-do:
6
6
 
7
7
  | signal | next |
@@ -17,13 +17,17 @@ Always-do:
17
17
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
18
18
  | work remains | spawn; do not stop |
19
19
  | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
20
- | `/parent` | one-turn parent tools |
20
+ | `/parent` | spawn-first; hook last-ditch `parent-escape`; never Read/Bash |
21
21
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
22
22
  | `/write-plan` plan-only | recap + `next: /implement-plan` |
23
+ | `/explain` | spawn if facts/work; then human recap of child output; screenshot/visible=context no Read |
24
+ | `/solo` | session escape only; stamp grunt-off-{sid}; else spawn-first |
25
+ | `/cascade` | unlink solo stamp; spawn-first |
23
26
 
24
27
  Parent = orchestrator. You do not talk. Spawn wait echo.
25
28
  Spawn only grunt|implementer|thinker. Never spawn orchestrator. Children never spawn.
26
- First token = spawn. No try-then-spawn. No parent probe. After child: legal tag + one-line echo only. Siblings still run: `[grunt]:` echo. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
29
+ You do not talk. First token = spawn. Illegal tools (never consider never call): Read read_file Grep grep Glob list_dir Bash run_terminal_command view_file grep_search run_command. Not in toolkit. Hook deny = backstop not UX. Next=spawn not retry. Only /solo (stamp grunt-off-{sid} this session) escapes spawn workflow. /explain=voice+post-child recap never parent Read. No try-then-spawn. No parent probe. After child: legal tag + one-line echo only. Siblings still run: `[grunt]:` echo. No skip-spawn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
30
+ `/parent` hook last-ditch only. Skills spawn-first. Only `/solo` escapes spawn workflow.
27
31
  grunt ← tools (facts/search/exec/git/web/test/low-reason mechanical write; world fact: `job: web` never memory). implementer ← write defined solution. thinker ← plan/deep reason.
28
32
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
29
33
  Peek every 60s/child; quote real host fields; `done|alive|stuck`; no invent; no auto-kill. `timeout_ms=60000` every peek. GAP: block on spawn return = `done`. Stuck/blocked: quote host fields only.
package/CLAUDE.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
2
2
  en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + one-line echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
3
3
  All output exclude AI attribution, Co-Authored-By, trailers, generated markers.
4
- Protocol: `.rulesync/reference/cascade.md`. Do not paste. Do not open first.
4
+ Protocol: `.rulesync/reference/cascade.md`. Do not paste.
5
5
  Always-do:
6
6
 
7
7
  | signal | next |
@@ -17,13 +17,17 @@ Always-do:
17
17
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
18
18
  | work remains | spawn; do not stop |
19
19
  | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
20
- | `/parent` | one-turn parent tools |
20
+ | `/parent` | spawn-first; hook last-ditch `parent-escape`; never Read/Bash |
21
21
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
22
22
  | `/write-plan` plan-only | recap + `next: /implement-plan` |
23
+ | `/explain` | spawn if facts/work; then human recap of child output; screenshot/visible=context no Read |
24
+ | `/solo` | session escape only; stamp grunt-off-{sid}; else spawn-first |
25
+ | `/cascade` | unlink solo stamp; spawn-first |
23
26
 
24
27
  Parent = this file not `.claude/agents/orchestrator.md`. You do not talk. Spawn wait echo.
25
28
  Spawn only grunt|implementer|thinker. Never spawn orchestrator. Children never spawn.
26
- First token = spawn. No try-then-spawn. No parent probe. After child: legal tag + one-line echo only. Siblings still run: `[grunt]:` echo. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
29
+ You do not talk. First token = spawn. Illegal tools (never consider never call): Read read_file Grep grep Glob list_dir Bash run_terminal_command view_file grep_search run_command. Not in toolkit. Hook deny = backstop not UX. Next=spawn not retry. Only /solo (stamp grunt-off-{sid} this session) escapes spawn workflow. /explain=voice+post-child recap never parent Read. No try-then-spawn. No parent probe. After child: legal tag + one-line echo only. Siblings still run: `[grunt]:` echo. No skip-spawn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
30
+ `/parent` hook last-ditch only. Skills spawn-first. Only `/solo` escapes spawn workflow.
27
31
  grunt ← tools (facts/search/exec/git/web/test/low-reason mechanical write; world fact: `job: web` never memory). implementer ← write defined solution. thinker ← plan/deep reason.
28
32
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
29
33
  Peek every 60s/child; quote real host fields; `done|alive|stuck`; no invent; no auto-kill. `timeout_ms=60000` every peek. GAP: block on spawn return = `done`. Stuck/blocked: quote host fields only.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GRUNT
2
2
 
3
- Building Grunt - a multi agent orchestrator workflow
3
+ Building Grunt - a multi agent orchestrator workflow. OSS, general use case, merges with existing configs, drop in any repo to switch from default provider flow to Grunt's
4
4
 
5
5
  # Install
6
6
 
@@ -11,10 +11,12 @@ npm i -D @lovrozagar/grunt
11
11
  npx @lovrozagar/grunt
12
12
  ```
13
13
 
14
- Same as `npx @lovrozagar/grunt init`. Merge SoT, `npm install`, `rulesync:generate`, `sync:globals:apply`, `rulesync:check`. Do not `npm test` as a consumer.
14
+ TTY no command: menu (init default; generate, check, sync-globals, purge-mcps, doctor, help, quit). Piped/CI/`--yes`/`-y`/`--non-interactive` no command: still `init`. Same as `npx @lovrozagar/grunt init`. Merge SoT, `npm install`, `rulesync:generate`, `sync:globals:apply`, `rulesync:check`. Do not `npm test` as a consumer.
15
15
 
16
16
  `--skip-globals` skips `sync:globals:apply`. Re-init auto-skips globals when `<!-- grunt:begin -->` is in AGENTS.md/CLAUDE.md or `scripts/telemetry.mjs` exists. First init (no sentinel, no telemetry) still applies globals unless flagged.
17
17
 
18
+ `sync-globals` / `purge-mcps`: dry-run default; `--apply` writes. `--yes` is not `--apply`. `sync-globals --host <id>`. `doctor` runs `rulesync doctor`.
19
+
18
20
  Contributors to this repo only: local `npm i && npm test`.
19
21
 
20
22
  # Version bump
package/bin/grunt.js CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { start } from "../cli/grunt.mjs"
3
3
 
4
- start()
4
+ start().catch(() => {
5
+ process.exit(1)
6
+ })
package/cli/grunt.mjs CHANGED
@@ -2,38 +2,134 @@ import { execFileSync } from "node:child_process"
2
2
  import { readFileSync } from "node:fs"
3
3
  import path from "node:path"
4
4
  import { fileURLToPath } from "node:url"
5
- import { init } from "./init.mjs"
5
+ import { destAlreadyInited, init, shouldAutoSkipGlobals } from "./init.mjs"
6
+ import { confirm, isInteractive, select, spinner } from "./prompt.mjs"
6
7
 
7
8
  const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..")
8
9
 
9
10
  const USAGE = `Usage: grunt [command]
10
11
 
11
- Default (no command): init — full setup
12
+ Default (no command): TTY menu; else init — full setup
12
13
 
13
14
  Commands:
14
- init Full setup: merge SoT, npm install, rulesync:generate, sync:globals:apply, rulesync:check
15
- generate npm run rulesync:generate
16
- check npm run rulesync:check
17
- help Show this help
18
- version Print package version
15
+ init Full setup: merge SoT, npm install, rulesync:generate, sync:globals:apply, rulesync:check
16
+ generate npm run rulesync:generate
17
+ check npm run rulesync:check
18
+ sync-globals npm run sync:globals (dry-run; --apply to write)
19
+ purge-mcps npm run purge:global-mcps (dry-run; --apply to write)
20
+ doctor npm run rulesync:doctor
21
+ help Show this help
22
+ version Print package version
19
23
 
20
24
  Flags:
21
- --skip-globals Skip sync:globals:apply (auto-skipped when already initialized)
25
+ --skip-globals Skip sync:globals:apply (auto-skipped when already initialized)
26
+ --yes, -y Non-interactive (not --apply)
27
+ --non-interactive Same as --yes
28
+ --apply Write for sync-globals / purge-mcps
29
+ --host <id> sync-globals host
22
30
  `
23
31
 
32
+ const MENU_OPTIONS = [
33
+ { value: "init", label: "init" },
34
+ { value: "generate", label: "generate" },
35
+ { value: "check", label: "check" },
36
+ { value: "sync-globals", label: "sync-globals" },
37
+ { value: "purge-mcps", label: "purge-mcps" },
38
+ { value: "doctor", label: "doctor" },
39
+ { value: "help", label: "help" },
40
+ { value: "quit", label: "quit" },
41
+ ]
42
+
43
+ const YES_FLAGS = new Set(["--yes", "-y", "--non-interactive"])
44
+
24
45
  function pkgVersion() {
25
46
  const pkg = JSON.parse(readFileSync(path.join(PKG_ROOT, "package.json"), "utf8"))
26
47
  return pkg.version
27
48
  }
28
49
 
29
- function npmRun(script) {
30
- execFileSync("npm", ["run", script], { cwd: process.cwd(), stdio: "inherit" })
50
+ function npmRun(script, extra = []) {
51
+ const args = extra.length ? ["run", script, "--", ...extra] : ["run", script]
52
+ execFileSync("npm", args, { cwd: process.cwd(), stdio: "inherit" })
53
+ }
54
+
55
+ function hostValueOk(v) {
56
+ return v != null && v !== "" && !String(v).startsWith("-")
31
57
  }
32
58
 
33
- export function start() {
34
- const args = process.argv.slice(2)
35
- const skipGlobals = args.includes("--skip-globals")
36
- const [cmd] = args.filter((a) => a !== "--skip-globals")
59
+ export function parseArgv(argv) {
60
+ let skipGlobals = false
61
+ let apply = false
62
+ let host
63
+ let hostError = false
64
+ const positionals = []
65
+ for (let i = 0; i < argv.length; i++) {
66
+ const a = argv[i]
67
+ if (a === "--skip-globals") {
68
+ skipGlobals = true
69
+ continue
70
+ }
71
+ if (YES_FLAGS.has(a)) continue
72
+ if (a === "--apply") {
73
+ apply = true
74
+ continue
75
+ }
76
+ if (a === "--host") {
77
+ const v = argv[i + 1]
78
+ if (!hostValueOk(v)) {
79
+ hostError = true
80
+ continue
81
+ }
82
+ host = v
83
+ i += 1
84
+ continue
85
+ }
86
+ if (typeof a === "string" && a.startsWith("--host=")) {
87
+ host = a.slice("--host=".length)
88
+ if (!hostValueOk(host)) hostError = true
89
+ continue
90
+ }
91
+ positionals.push(a)
92
+ }
93
+ return { cmd: positionals[0], skipGlobals, apply, host, hostError }
94
+ }
95
+
96
+ function hostExtra(host) {
97
+ return host ? ["--host", host] : []
98
+ }
99
+
100
+ function bindSpinner() {
101
+ const spin = spinner()
102
+ return (name, action) => {
103
+ if (action === "start") spin.start(name)
104
+ else if (action === "stop") spin.stop()
105
+ }
106
+ }
107
+
108
+ async function runInit(cwd, { skipGlobals, interactive }) {
109
+ if (!interactive) {
110
+ init(cwd, { skipGlobals })
111
+ return
112
+ }
113
+ if (destAlreadyInited(cwd)) {
114
+ const again = await confirm({
115
+ message: "Re-init?",
116
+ initialValue: true,
117
+ })
118
+ if (!again) return
119
+ }
120
+ const autoSkip = skipGlobals || shouldAutoSkipGlobals(cwd)
121
+ const applyGlobals = await confirm({
122
+ message: "Apply globals?",
123
+ initialValue: !autoSkip,
124
+ })
125
+ init(cwd, {
126
+ skipGlobals: !applyGlobals,
127
+ applyGlobals,
128
+ onPhase: bindSpinner(),
129
+ })
130
+ }
131
+
132
+ async function dispatch(cmd, flags, interactive) {
37
133
  if (cmd === "help" || cmd === "--help" || cmd === "-h") {
38
134
  process.stdout.write(USAGE)
39
135
  return
@@ -43,7 +139,10 @@ export function start() {
43
139
  return
44
140
  }
45
141
  if (!cmd || cmd === "init") {
46
- init(process.cwd(), { skipGlobals })
142
+ await runInit(process.cwd(), {
143
+ skipGlobals: flags.skipGlobals,
144
+ interactive: interactive && (!cmd || cmd === "init"),
145
+ })
47
146
  return
48
147
  }
49
148
  if (cmd === "generate") {
@@ -54,6 +153,40 @@ export function start() {
54
153
  npmRun("rulesync:check")
55
154
  return
56
155
  }
156
+ if (cmd === "sync-globals") {
157
+ const script = flags.apply ? "sync:globals:apply" : "sync:globals"
158
+ npmRun(script, hostExtra(flags.host))
159
+ return
160
+ }
161
+ if (cmd === "purge-mcps") {
162
+ npmRun(flags.apply ? "purge:global-mcps:apply" : "purge:global-mcps")
163
+ return
164
+ }
165
+ if (cmd === "doctor") {
166
+ npmRun("rulesync:doctor")
167
+ return
168
+ }
57
169
  process.stdout.write(USAGE)
58
170
  process.exitCode = 1
59
171
  }
172
+
173
+ export async function start() {
174
+ const flags = parseArgv(process.argv.slice(2))
175
+ if (flags.hostError) {
176
+ process.stdout.write(USAGE)
177
+ process.exitCode = 1
178
+ return
179
+ }
180
+ const interactive = isInteractive()
181
+ let cmd = flags.cmd
182
+ if (!cmd && interactive) {
183
+ const choice = await select({
184
+ message: "Command",
185
+ options: MENU_OPTIONS,
186
+ initialValue: "init",
187
+ })
188
+ if (choice === "quit") return
189
+ cmd = choice
190
+ }
191
+ await dispatch(cmd, flags, interactive)
192
+ }