wiggum-cli 0.14.0 → 0.16.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/README.md +30 -5
  2. package/dist/ai/providers.js +19 -14
  3. package/dist/commands/run.d.ts +1 -1
  4. package/dist/commands/run.js +2 -2
  5. package/dist/generator/templates.d.ts +1 -0
  6. package/dist/generator/templates.js +14 -1
  7. package/dist/index.d.ts +14 -1
  8. package/dist/index.js +229 -41
  9. package/dist/repl/session-state.d.ts +2 -0
  10. package/dist/templates/config/ralph.config.cjs.tmpl +1 -1
  11. package/dist/templates/prompts/PROMPT_e2e.md.tmpl +151 -0
  12. package/dist/templates/prompts/PROMPT_feature.md.tmpl +23 -0
  13. package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +13 -42
  14. package/dist/templates/prompts/PROMPT_review_merge.md.tmpl +168 -0
  15. package/dist/templates/scripts/feature-loop-actions.test.ts +92 -0
  16. package/dist/templates/scripts/feature-loop.sh.tmpl +334 -40
  17. package/dist/tui/app.d.ts +16 -1
  18. package/dist/tui/app.js +31 -5
  19. package/dist/tui/components/ActivityFeed.d.ts +18 -0
  20. package/dist/tui/components/ActivityFeed.js +31 -0
  21. package/dist/tui/components/ChatInput.d.ts +3 -1
  22. package/dist/tui/components/ChatInput.js +23 -4
  23. package/dist/tui/components/CommandDropdown.d.ts +3 -1
  24. package/dist/tui/components/CommandDropdown.js +10 -7
  25. package/dist/tui/components/RunCompletionSummary.d.ts +27 -1
  26. package/dist/tui/components/RunCompletionSummary.js +97 -7
  27. package/dist/tui/components/SpecCompletionSummary.d.ts +3 -2
  28. package/dist/tui/components/SpecCompletionSummary.js +26 -9
  29. package/dist/tui/components/SummaryBox.d.ts +4 -3
  30. package/dist/tui/components/SummaryBox.js +7 -3
  31. package/dist/tui/hooks/useBackgroundRuns.js +1 -1
  32. package/dist/tui/orchestration/interview-orchestrator.js +35 -5
  33. package/dist/tui/screens/MainShell.js +2 -1
  34. package/dist/tui/screens/RunScreen.d.ts +15 -15
  35. package/dist/tui/screens/RunScreen.js +139 -17
  36. package/dist/tui/utils/action-inbox.d.ts +43 -0
  37. package/dist/tui/utils/action-inbox.js +109 -0
  38. package/dist/tui/utils/build-run-summary.js +4 -1
  39. package/dist/tui/utils/git-summary.d.ts +13 -0
  40. package/dist/tui/utils/git-summary.js +30 -0
  41. package/dist/tui/utils/loop-status.d.ts +54 -0
  42. package/dist/tui/utils/loop-status.js +213 -1
  43. package/dist/tui/utils/polishGoal.d.ts +37 -0
  44. package/dist/tui/utils/polishGoal.js +170 -0
  45. package/dist/utils/ci.d.ts +8 -0
  46. package/dist/utils/ci.js +13 -0
  47. package/dist/utils/config.d.ts +1 -1
  48. package/dist/utils/fuzzy-match.d.ts +5 -0
  49. package/dist/utils/fuzzy-match.js +16 -0
  50. package/dist/utils/spec-names.d.ts +6 -0
  51. package/dist/utils/spec-names.js +27 -0
  52. package/package.json +15 -5
  53. package/src/templates/config/ralph.config.cjs.tmpl +1 -1
  54. package/src/templates/prompts/PROMPT_e2e.md.tmpl +151 -0
  55. package/src/templates/prompts/PROMPT_feature.md.tmpl +23 -0
  56. package/src/templates/prompts/PROMPT_review_auto.md.tmpl +13 -42
  57. package/src/templates/prompts/PROMPT_review_merge.md.tmpl +168 -0
  58. package/src/templates/scripts/feature-loop-actions.test.ts +92 -0
  59. package/src/templates/scripts/feature-loop.sh.tmpl +334 -40
package/README.md CHANGED
@@ -9,6 +9,7 @@
9
9
  <p align="center">
10
10
  <a href="https://www.npmjs.com/package/wiggum-cli"><img src="https://img.shields.io/npm/v/wiggum-cli?color=F8DB27&labelColor=1a1a1a&style=flat-square" alt="npm"></a>
11
11
  <a href="https://www.npmjs.com/package/wiggum-cli"><img src="https://img.shields.io/npm/dm/wiggum-cli?color=F8DB27&labelColor=1a1a1a&style=flat-square" alt="downloads"></a>
12
+ <a href="https://github.com/federiconeri/wiggum-cli/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/federiconeri/wiggum-cli/ci.yml?branch=main&color=F8DB27&labelColor=1a1a1a&style=flat-square&label=CI" alt="CI"></a>
12
13
  <a href="https://github.com/federiconeri/wiggum-cli/stargazers"><img src="https://img.shields.io/github/stars/federiconeri/wiggum-cli?color=F8DB27&labelColor=1a1a1a&style=flat-square" alt="stars"></a>
13
14
  <a href="https://github.com/federiconeri/wiggum-cli/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT%20+%20Commons%20Clause-F8DB27?labelColor=1a1a1a&style=flat-square" alt="license"></a>
14
15
  <img src="https://img.shields.io/node/v/wiggum-cli?color=F8DB27&labelColor=1a1a1a&style=flat-square" alt="node">
@@ -18,16 +19,22 @@
18
19
  <a href="#-quick-start">Quick Start</a> ·
19
20
  <a href="#-how-it-works">How It Works</a> ·
20
21
  <a href="https://wiggum.app">Website</a> ·
22
+ <a href="https://wiggum.app/blog">Blog</a> ·
23
+ <a href="https://wiggum.app/pricing">Pricing</a> ·
21
24
  <a href="https://github.com/federiconeri/wiggum-cli/issues">Issues</a>
22
25
  </p>
23
26
 
27
+ <p align="center">
28
+ <video src="https://github.com/user-attachments/assets/817edf0c-a7aa-418f-bf85-499be520fd94" width="800" controls></video>
29
+ </p>
30
+
24
31
  ---
25
32
 
26
33
  ## What is Wiggum?
27
34
 
28
35
  Wiggum is an **AI agent** that plugs into any codebase and makes it ready for autonomous feature development — no configuration, no boilerplate.
29
36
 
30
- It works in two phases. First, **Wiggum itself is the agent**: it scans your project, detects your stack, and runs an AI-guided interview to produce detailed specs, prompts, and scripts — all tailored to your codebase. Then it delegates the actual coding to [Claude Code](https://docs.anthropic.com/en/docs/claude-code), Codex, or any CLI-based coding agent, running an autonomous **implement → test → fix** loop until the feature ships.
37
+ It works in two phases. First, **Wiggum itself is the agent**: it scans your project, detects your stack, and runs an AI-guided interview to produce detailed specs, prompts, and scripts — all tailored to your codebase. Then it delegates the actual coding to [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or any CLI-based coding agent, running an autonomous **implement → test → fix** loop until the feature ships.
31
38
 
32
39
  Plug & play. Point it at a repo. It figures out the rest.
33
40
 
@@ -41,7 +48,7 @@ Plug & play. Point it at a repo. It figures out the rest.
41
48
  │ plug&play prompts guides until done │
42
49
  │ │ │ │
43
50
  └────────────────────────────┘ └────────────────────┘
44
- runs in your terminal Claude Code / Codex
51
+ runs in your terminal Claude Code / any agent
45
52
  ```
46
53
 
47
54
  ---
@@ -76,6 +83,12 @@ npx wiggum-cli init
76
83
 
77
84
  🔁 **Autonomous Coding Loops** — Hands specs to Claude Code (or any agent) and runs implement → test → fix cycles with git worktree isolation.
78
85
 
86
+ ✨ **Spec Autocomplete** — AI pre-fills spec names from your codebase context when running `/run`.
87
+
88
+ 📥 **Action Inbox** — Review AI decisions inline without breaking your flow. The loop pauses, you approve or redirect, it continues.
89
+
90
+ 📊 **Run Summaries** — See exactly what changed and why after each loop completes, with activity feed and diff stats.
91
+
79
92
  📋 **Tailored Prompts** — Generates prompts, guides, and scripts specific to your stack. Not generic templates — actual context about *your* project.
80
93
 
81
94
  🔌 **BYOK** — Bring your own API keys. Works with Anthropic, OpenAI, or OpenRouter. Keys stay local, never leave your machine.
@@ -163,7 +176,8 @@ $ wiggum
163
176
  │ ├── PROMPT_e2e.md # E2E testing
164
177
  │ ├── PROMPT_verify.md # Verification
165
178
  │ ├── PROMPT_review_manual.md # PR review (manual - stop at PR)
166
- └── PROMPT_review_auto.md # PR review (auto - review + merge)
179
+ ├── PROMPT_review_auto.md # PR review (auto - review, no merge)
180
+ │ └── PROMPT_review_merge.md # PR review (merge - review + auto-merge)
167
181
  ├── guides/
168
182
  │ ├── AGENTS.md # Agent instructions (CLAUDE.md)
169
183
  │ ├── FRONTEND.md # Frontend patterns
@@ -218,8 +232,9 @@ Run the autonomous development loop.
218
232
  | `--worktree` | Git worktree isolation (parallel features) |
219
233
  | `--resume` | Resume an interrupted loop |
220
234
  | `--model <model>` | Claude model (`opus`, `sonnet`) |
221
- | `--max-iterations <n>` | Max iterations (default: 50) |
222
- | `--max-e2e-attempts <n>` | Max E2E retries (default: 3) |
235
+ | `--max-iterations <n>` | Max iterations (default: 10) |
236
+ | `--max-e2e-attempts <n>` | Max E2E retries (default: 5) |
237
+ | `--review-mode <mode>` | `manual` (stop at PR), `auto` (review, no merge), or `merge` (review + merge). Default: `manual` |
223
238
 
224
239
  </details>
225
240
 
@@ -307,6 +322,16 @@ npm test
307
322
 
308
323
  ---
309
324
 
325
+ ## 📖 Learn More
326
+
327
+ - [What Is Wiggum CLI?](https://wiggum.app/blog/what-is-wiggum-cli) — Overview of the autonomous coding agent
328
+ - [What Is the Ralph Loop?](https://wiggum.app/blog/what-is-the-ralph-loop) — Deep dive into the Ralph loop methodology
329
+ - [Wiggum vs Bash Scripts](https://wiggum.app/blog/wiggum-vs-ralph-wiggum-scripts) — Why spec generation matters
330
+ - [Roadmap](https://wiggum.app/roadmap) — What's coming next
331
+ - [Changelog](https://wiggum.app/changelog) — Release history
332
+
333
+ ---
334
+
310
335
  ## 📄 License
311
336
 
312
337
  **MIT + Commons Clause** — see [LICENSE](LICENSE).
@@ -34,21 +34,25 @@ export const KNOWN_API_KEYS = [
34
34
  */
35
35
  export const AVAILABLE_MODELS = {
36
36
  anthropic: [
37
- { value: 'claude-opus-4-5-20250514', label: 'Claude Opus 4.5', hint: 'most capable' },
38
- { value: 'claude-sonnet-4-5-20250514', label: 'Claude Sonnet 4.5', hint: 'recommended' },
39
- { value: 'claude-haiku-4-5-20250514', label: 'Claude Haiku 4.5', hint: 'fastest' },
37
+ { value: 'claude-opus-4-6', label: 'Claude Opus 4.6', hint: 'most capable' },
38
+ { value: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5', hint: 'recommended' },
39
+ { value: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', hint: 'fastest' },
40
40
  ],
41
41
  openai: [
42
- { value: 'gpt-5.1', label: 'GPT-5.1', hint: 'most capable' },
43
- { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max', hint: 'best for code' },
42
+ { value: 'gpt-5.2', label: 'GPT-5.2', hint: 'most capable' },
43
+ { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex', hint: 'best for code' },
44
+ { value: 'gpt-5.1', label: 'GPT-5.1', hint: 'previous gen' },
45
+ { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max', hint: 'previous codex' },
44
46
  { value: 'gpt-5-mini', label: 'GPT-5 Mini', hint: 'fastest' },
45
47
  ],
46
48
  openrouter: [
47
- { value: 'google/gemini-3-pro-preview', label: 'Gemini 3 Pro', hint: 'Google' },
48
- { value: 'google/gemini-3-flash-preview', label: 'Gemini 3 Flash', hint: 'fast' },
49
- { value: 'deepseek/deepseek-v3.2', label: 'DeepSeek v3.2', hint: 'efficient' },
50
- { value: 'z-ai/glm-4.7', label: 'GLM 4.7', hint: 'Z-AI' },
49
+ { value: 'google/gemini-3-pro-preview', label: 'Gemini 3 Pro Preview', hint: 'Google' },
50
+ { value: 'google/gemini-3-flash-preview', label: 'Gemini 3 Flash Preview', hint: 'fast' },
51
+ { value: 'moonshotai/kimi-k2.5', label: 'Kimi K2.5', hint: 'Moonshot' },
52
+ { value: 'deepseek/deepseek-v3.2', label: 'DeepSeek V3.2', hint: 'efficient' },
51
53
  { value: 'minimax/minimax-m2.1', label: 'MiniMax M2.1', hint: 'MiniMax' },
54
+ { value: 'z-ai/glm-4.7', label: 'GLM 4.7', hint: 'Z-AI' },
55
+ { value: 'x-ai/grok-4.1-fast', label: 'Grok 4.1 Fast', hint: 'xAI' },
52
56
  ],
53
57
  };
54
58
  /**
@@ -56,17 +60,17 @@ export const AVAILABLE_MODELS = {
56
60
  * Using balanced models for good results
57
61
  */
58
62
  const DEFAULT_MODELS = {
59
- anthropic: 'claude-sonnet-4-5-20250514',
60
- openai: 'gpt-5.1',
63
+ anthropic: 'claude-sonnet-4-5-20250929',
64
+ openai: 'gpt-5.2',
61
65
  openrouter: 'google/gemini-3-pro-preview',
62
66
  };
63
67
  /**
64
68
  * Anthropic shorthand aliases for legacy configs
65
69
  */
66
70
  const ANTHROPIC_MODEL_ALIASES = {
67
- sonnet: 'claude-sonnet-4-5-20250514',
68
- opus: 'claude-opus-4-5-20250514',
69
- haiku: 'claude-haiku-4-5-20250514',
71
+ sonnet: 'claude-sonnet-4-5-20250929',
72
+ opus: 'claude-opus-4-6',
73
+ haiku: 'claude-haiku-4-5-20251001',
70
74
  };
71
75
  /**
72
76
  * Check if a model id is an Anthropic alias (sonnet/opus/haiku)
@@ -173,6 +177,7 @@ const REASONING_MODELS = [
173
177
  'o3', 'o3-mini',
174
178
  'gpt-5', 'gpt-5.1', 'gpt-5-mini',
175
179
  'gpt-5.1-codex', 'gpt-5.1-codex-max',
180
+ 'gpt-5.2', 'gpt-5.2-codex',
176
181
  ];
177
182
  /**
178
183
  * Check if a model is a reasoning model that doesn't support temperature
@@ -8,7 +8,7 @@ export interface RunOptions {
8
8
  model?: string;
9
9
  maxIterations?: number;
10
10
  maxE2eAttempts?: number;
11
- reviewMode?: 'manual' | 'auto';
11
+ reviewMode?: 'manual' | 'auto' | 'merge';
12
12
  }
13
13
  /**
14
14
  * Run the feature development loop for a specific feature
@@ -113,8 +113,8 @@ export async function runCommand(feature, options = {}) {
113
113
  }
114
114
  // Resolve and validate reviewMode
115
115
  const reviewMode = options.reviewMode ?? config.loop.reviewMode ?? 'manual';
116
- if (reviewMode !== 'manual' && reviewMode !== 'auto') {
117
- logger.error(`Invalid reviewMode '${reviewMode}'. Allowed values are 'manual' or 'auto'.`);
116
+ if (reviewMode !== 'manual' && reviewMode !== 'auto' && reviewMode !== 'merge') {
117
+ logger.error(`Invalid reviewMode '${reviewMode}'. Allowed values are 'manual', 'auto', or 'merge'.`);
118
118
  process.exit(1);
119
119
  }
120
120
  args.push('--review-mode', reviewMode);
@@ -28,6 +28,7 @@ export interface TemplateVariables {
28
28
  typecheckCommand: string;
29
29
  formatCommand: string;
30
30
  appDir: string;
31
+ isTui: string;
31
32
  aiEntryPoints: string;
32
33
  aiKeyDirectories: string;
33
34
  aiNamingConventions: string;
@@ -3,7 +3,7 @@
3
3
  * Reads template files with {{variable}} placeholders and substitutes values
4
4
  */
5
5
  import { readFile, readdir } from 'node:fs/promises';
6
- import { existsSync } from 'node:fs';
6
+ import { existsSync, readFileSync } from 'node:fs';
7
7
  import { join, dirname } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  const __filename = fileURLToPath(import.meta.url);
@@ -150,6 +150,18 @@ export function extractVariables(scanResult, customVars = {}) {
150
150
  existsSync(join(projectRoot, 'src', 'main.ts'))) {
151
151
  appDir = 'src';
152
152
  }
153
+ // Detect TUI (Ink) projects
154
+ let isTui = '';
155
+ try {
156
+ const pkgPath = join(projectRoot, 'package.json');
157
+ if (existsSync(pkgPath)) {
158
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
159
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
160
+ if (allDeps['ink'])
161
+ isTui = 'true';
162
+ }
163
+ }
164
+ catch { /* ignore */ }
153
165
  return {
154
166
  projectName,
155
167
  projectRoot,
@@ -166,6 +178,7 @@ export function extractVariables(scanResult, customVars = {}) {
166
178
  stylingVersion,
167
179
  stylingVariant,
168
180
  appDir,
181
+ isTui,
169
182
  ...commands,
170
183
  ...aiData,
171
184
  ...customVars,
package/dist/index.d.ts CHANGED
@@ -1,6 +1,19 @@
1
+ /**
2
+ * Parsed CLI arguments
3
+ */
4
+ export interface ParsedArgs {
5
+ command: string | undefined;
6
+ positionalArgs: string[];
7
+ flags: Record<string, string | boolean>;
8
+ }
9
+ /**
10
+ * Parse CLI arguments into command, positional args, and flags.
11
+ * Supports: --flag value, --flag=value, boolean flags, short flags (-i, -y, -e, -f, -h, -v).
12
+ */
13
+ export declare function parseCliArgs(argv: string[]): ParsedArgs;
1
14
  /**
2
15
  * Main entry point for the Wiggum CLI
3
- * TUI-first: routes args to appropriate TUI screens
16
+ * TUI-first: routes args to appropriate TUI screens or CLI commands
4
17
  */
5
18
  export declare function main(): Promise<void>;
6
19
  export { displayHeader } from './utils/header.js';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createSessionState } from './repl/session-state.js';
2
2
  import { hasConfig, loadConfigWithDefaults } from './utils/config.js';
3
+ import { listSpecNames } from './utils/spec-names.js';
3
4
  import { AVAILABLE_MODELS, getAvailableProvider, isAnthropicAlias } from './ai/providers.js';
4
5
  import { notifyIfUpdateAvailable } from './utils/update-check.js';
5
6
  import { renderApp } from './tui/app.js';
@@ -8,6 +9,87 @@ import { loadApiKeysFromEnvLocal } from './utils/env.js';
8
9
  import { readFileSync } from 'fs';
9
10
  import { fileURLToPath } from 'url';
10
11
  import { dirname, join } from 'path';
12
+ import { runCommand } from './commands/run.js';
13
+ import { monitorCommand } from './commands/monitor.js';
14
+ import { handleConfigCommand } from './commands/config.js';
15
+ import { isCI } from './utils/ci.js';
16
+ /**
17
+ * Normalize a flag name: strip '--' prefix and convert kebab-case to camelCase
18
+ */
19
+ function normalizeFlagName(flag) {
20
+ return flag.replace(/^--/, '').replace(/-([a-z])/g, (_, c) => c.toUpperCase());
21
+ }
22
+ /**
23
+ * Parse CLI arguments into command, positional args, and flags.
24
+ * Supports: --flag value, --flag=value, boolean flags, short flags (-i, -y, -e, -f, -h, -v).
25
+ */
26
+ export function parseCliArgs(argv) {
27
+ const positionalArgs = [];
28
+ const flags = {};
29
+ let command;
30
+ const shortFlags = {
31
+ '-i': 'interactive',
32
+ '-y': 'yes',
33
+ '-e': 'edit',
34
+ '-f': 'force',
35
+ '-h': 'help',
36
+ '-v': 'version',
37
+ };
38
+ // Flags that consume the next argument as their value
39
+ const valueFlagSet = new Set([
40
+ '--model',
41
+ '--max-iterations',
42
+ '--max-e2e-attempts',
43
+ '--interval',
44
+ '--provider',
45
+ '--review-mode',
46
+ ]);
47
+ let i = 0;
48
+ while (i < argv.length) {
49
+ const arg = argv[i];
50
+ if (arg in shortFlags) {
51
+ flags[shortFlags[arg]] = true;
52
+ i++;
53
+ continue;
54
+ }
55
+ if (arg.startsWith('--') && arg.includes('=')) {
56
+ const eqIdx = arg.indexOf('=');
57
+ const key = normalizeFlagName(arg.slice(0, eqIdx));
58
+ const value = arg.slice(eqIdx + 1);
59
+ flags[key] = value;
60
+ i++;
61
+ continue;
62
+ }
63
+ if (arg.startsWith('--')) {
64
+ const normalized = normalizeFlagName(arg);
65
+ if (valueFlagSet.has(arg) && i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
66
+ flags[normalized] = argv[i + 1];
67
+ i += 2;
68
+ }
69
+ else {
70
+ flags[normalized] = true;
71
+ i++;
72
+ }
73
+ continue;
74
+ }
75
+ if (command === undefined) {
76
+ command = arg;
77
+ }
78
+ else {
79
+ positionalArgs.push(arg);
80
+ }
81
+ i++;
82
+ }
83
+ return { command, positionalArgs, flags };
84
+ }
85
+ function parseIntFlag(value, flagName) {
86
+ const n = parseInt(value, 10);
87
+ if (Number.isNaN(n)) {
88
+ console.error(`Error: ${flagName} must be a number, got "${value}"`);
89
+ process.exit(1);
90
+ }
91
+ return n;
92
+ }
11
93
  /**
12
94
  * Get version from package.json
13
95
  */
@@ -28,7 +110,8 @@ function getVersion() {
28
110
  * Start Ink TUI mode
29
111
  * Called when wiggum is invoked with no arguments or with screen-routing args
30
112
  */
31
- async function startInkTui(initialScreen = 'shell', interviewFeature) {
113
+ async function startInkTui(initialScreen = 'shell', options) {
114
+ const interviewFeature = options?.interviewFeature;
32
115
  const projectRoot = process.cwd();
33
116
  const version = getVersion();
34
117
  /**
@@ -54,9 +137,14 @@ async function startInkTui(initialScreen = 'shell', interviewFeature) {
54
137
  model = configuredModel;
55
138
  }
56
139
  }
57
- return createSessionState(projectRoot, provider, // May be null if no API key
140
+ const specsDir = config
141
+ ? join(projectRoot, config.paths.specs)
142
+ : join(projectRoot, '.ralph/specs');
143
+ const specNames = await listSpecNames(specsDir);
144
+ const state = createSessionState(projectRoot, provider, // May be null if no API key
58
145
  model, undefined, // No scan result yet
59
146
  config, isInitialized);
147
+ return { ...state, specNames };
60
148
  }
61
149
  const initialState = await createCurrentSessionState();
62
150
  // Build interview props if starting on interview screen
@@ -69,11 +157,16 @@ async function startInkTui(initialScreen = 'shell', interviewFeature) {
69
157
  scanResult: initialState.scanResult,
70
158
  }
71
159
  : undefined;
160
+ // Build run props if starting on run/monitor screen
161
+ const runProps = options?.runFeature
162
+ ? { featureName: options.runFeature, monitorOnly: options.monitorOnly }
163
+ : undefined;
72
164
  const instance = renderApp({
73
165
  screen: initialScreen,
74
166
  initialSessionState: initialState,
75
167
  version,
76
168
  interviewProps,
169
+ runProps,
77
170
  onComplete: (specPath) => {
78
171
  // Spec was saved to disk by app.tsx (avoid stdout noise during TUI)
79
172
  logger.debug(`Created spec: ${specPath}`);
@@ -87,62 +180,157 @@ async function startInkTui(initialScreen = 'shell', interviewFeature) {
87
180
  }
88
181
  /**
89
182
  * Main entry point for the Wiggum CLI
90
- * TUI-first: routes args to appropriate TUI screens
183
+ * TUI-first: routes args to appropriate TUI screens or CLI commands
91
184
  */
92
185
  export async function main() {
93
186
  // Load API keys from .ralph/.env.local before any provider detection
94
187
  loadApiKeysFromEnvLocal();
95
- const args = process.argv.slice(2);
188
+ const parsed = parseCliArgs(process.argv.slice(2));
96
189
  // Check for updates (non-blocking, fails silently)
97
190
  await notifyIfUpdateAvailable();
98
- // No args = start with shell
99
- if (args.length === 0) {
191
+ // Handle --help / -h
192
+ if (parsed.flags.help) {
193
+ console.log(`
194
+ Wiggum CLI - AI-powered feature development assistant
195
+
196
+ Usage:
197
+ wiggum Start interactive TUI
198
+ wiggum init Initialize project (TUI)
199
+ wiggum new <name> Create new feature spec (TUI)
200
+ wiggum run <feature> Run feature development loop
201
+ wiggum monitor <feature> Monitor a running feature loop
202
+ wiggum config [args...] Manage API keys and settings
203
+
204
+ Options for run:
205
+ --worktree Use git worktree isolation
206
+ --resume Resume from last checkpoint
207
+ --model <model> AI model to use
208
+ --max-iterations <n> Maximum loop iterations
209
+ --max-e2e-attempts <n> Maximum E2E test attempts
210
+ --review-mode <mode> Review mode: manual, auto, merge
211
+
212
+ Options for monitor:
213
+ --interval <seconds> Refresh interval (default: 5)
214
+ --bash Use bash monitor script
215
+ --stream Force headless streaming output (skip TUI)
216
+
217
+ Options for init:
218
+ --provider <name> AI provider (anthropic, openai, openrouter)
219
+ -i, --interactive Interactive mode
220
+ -y, --yes Accept all defaults
221
+
222
+ Options for new:
223
+ --provider <name> AI provider
224
+ --model <model> AI model
225
+ -e, --edit Open in editor after creation
226
+ -f, --force Overwrite existing spec
227
+
228
+ In the TUI:
229
+ /init Initialize or reconfigure project
230
+ /new <name> Create a new feature specification
231
+ /run <name> Run the feature development loop
232
+ /monitor <name> Monitor a running feature loop
233
+ /sync Sync context from git history
234
+ /config [set <svc> <key>] Manage API keys and settings
235
+ /help Show available commands
236
+ /exit Exit the application
237
+
238
+ Press Esc to cancel any operation.
239
+ `);
240
+ return;
241
+ }
242
+ // Handle --version / -v
243
+ if (parsed.flags.version) {
244
+ console.log(getVersion());
245
+ return;
246
+ }
247
+ // No command = start with shell
248
+ if (!parsed.command) {
100
249
  await startInkTui('shell');
101
250
  return;
102
251
  }
103
- // Route commands to TUI screens
104
- const command = args[0];
105
- switch (command) {
106
- case 'init':
107
- // Start TUI at init screen
252
+ switch (parsed.command) {
253
+ case 'init': {
254
+ // TODO: pass parsed flags to startInkTui once TUI supports init flags
108
255
  await startInkTui('init');
109
256
  break;
110
- case 'new':
111
- // Start TUI at interview screen with feature name
112
- const featureName = args[1];
257
+ }
258
+ case 'new': {
259
+ const featureName = parsed.positionalArgs[0];
113
260
  if (!featureName) {
114
- logger.error('Feature name required. Usage: wiggum new <feature-name>');
261
+ console.error('Error: <name> is required for "new"');
262
+ console.error('Usage: wiggum new <name> [--provider <name>] [--model <model>] [-e] [-f]');
115
263
  process.exit(1);
116
264
  }
117
- await startInkTui('interview', featureName);
265
+ // TODO: pass parsed flags to startInkTui once TUI supports new flags
266
+ await startInkTui('interview', { interviewFeature: featureName });
118
267
  break;
119
- case '--help':
120
- case '-h':
121
- // Show help
122
- console.log(`
123
- Wiggum CLI - AI-powered feature development assistant
124
-
125
- Usage:
126
- wiggum Start interactive TUI
127
- wiggum init Initialize project (TUI)
128
- wiggum new <name> Create new feature spec (TUI)
129
-
130
- In the TUI:
131
- /init Initialize or reconfigure project
132
- /new <name> Create a new feature specification
133
- /help Show available commands
134
- /exit Exit the application
135
-
136
- Press Esc to cancel any operation.
137
- `);
138
- return;
139
- case '--version':
140
- case '-v':
141
- console.log(getVersion());
142
- return;
268
+ }
269
+ case 'run': {
270
+ const feature = parsed.positionalArgs[0];
271
+ if (!feature) {
272
+ console.error('Error: <feature> is required for "run"');
273
+ console.error('Usage: wiggum run <feature> [--worktree] [--resume] [--model <model>] [--max-iterations <n>] [--max-e2e-attempts <n>]');
274
+ process.exit(1);
275
+ }
276
+ const runOptions = {
277
+ worktree: parsed.flags.worktree === true,
278
+ resume: parsed.flags.resume === true,
279
+ model: typeof parsed.flags.model === 'string' ? parsed.flags.model : undefined,
280
+ maxIterations: typeof parsed.flags.maxIterations === 'string' ? parseIntFlag(parsed.flags.maxIterations, '--max-iterations') : undefined,
281
+ maxE2eAttempts: typeof parsed.flags.maxE2eAttempts === 'string' ? parseIntFlag(parsed.flags.maxE2eAttempts, '--max-e2e-attempts') : undefined,
282
+ reviewMode: typeof parsed.flags.reviewMode === 'string' ? parsed.flags.reviewMode : undefined,
283
+ };
284
+ await runCommand(feature, runOptions);
285
+ break;
286
+ }
287
+ case 'monitor': {
288
+ const feature = parsed.positionalArgs[0];
289
+ if (!feature) {
290
+ console.error('Error: <feature> is required for "monitor"');
291
+ console.error('Usage: wiggum monitor <feature> [--interval <seconds>] [--bash] [--stream]');
292
+ process.exit(1);
293
+ }
294
+ const interval = typeof parsed.flags.interval === 'string'
295
+ ? parseIntFlag(parsed.flags.interval, '--interval')
296
+ : undefined;
297
+ // Routing order per spec:
298
+ // 1. --bash → always use bash script path
299
+ // 2. --stream → force headless streaming
300
+ // 3. TTY and not CI → start Ink TUI in monitor-only mode
301
+ // 4. default → headless streaming monitor
302
+ if (parsed.flags.bash === true) {
303
+ await monitorCommand(feature, { bash: true, interval });
304
+ }
305
+ else if (parsed.flags.stream === true) {
306
+ await monitorCommand(feature, { interval });
307
+ }
308
+ else if (process.stdout.isTTY && !isCI()) {
309
+ try {
310
+ await startInkTui('run', { runFeature: feature, monitorOnly: true });
311
+ }
312
+ catch (err) {
313
+ logger.error(`TUI failed to start: ${err instanceof Error ? err.message : String(err)}. Falling back to headless monitor.`);
314
+ await monitorCommand(feature, { interval });
315
+ }
316
+ }
317
+ else {
318
+ await monitorCommand(feature, { interval });
319
+ }
320
+ break;
321
+ }
322
+ case 'config': {
323
+ const provider = getAvailableProvider();
324
+ const model = provider
325
+ ? (AVAILABLE_MODELS[provider].find((m) => m.hint?.includes('recommended'))?.value ?? AVAILABLE_MODELS[provider][0].value)
326
+ : 'sonnet';
327
+ const state = createSessionState(process.cwd(), provider, model);
328
+ await handleConfigCommand(parsed.positionalArgs, state);
329
+ break;
330
+ }
143
331
  default:
144
332
  // Unknown command - start TUI at shell
145
- logger.warn(`Unknown command: ${command}. Starting TUI...`);
333
+ logger.warn(`Unknown command: ${parsed.command}. Starting TUI...`);
146
334
  await startInkTui('shell');
147
335
  }
148
336
  }
@@ -25,6 +25,8 @@ export interface SessionState {
25
25
  conversationContext?: string;
26
26
  /** Whether /init has been run in this session */
27
27
  initialized: boolean;
28
+ /** Cached spec names from the configured specs directory */
29
+ specNames?: string[];
28
30
  }
29
31
  /**
30
32
  * Create a new session state
@@ -34,6 +34,6 @@ module.exports = {
34
34
  maxE2eAttempts: 5,
35
35
  defaultModel: 'sonnet',
36
36
  planningModel: 'opus',
37
- reviewMode: 'manual', // 'manual' = stop at PR, 'auto' = review + auto-merge
37
+ reviewMode: 'manual', // 'manual' = stop at PR, 'auto' = review (no merge), 'merge' = review + auto-merge
38
38
  },
39
39
  };