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.
- package/README.md +30 -5
- package/dist/ai/providers.js +19 -14
- package/dist/commands/run.d.ts +1 -1
- package/dist/commands/run.js +2 -2
- package/dist/generator/templates.d.ts +1 -0
- package/dist/generator/templates.js +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +229 -41
- package/dist/repl/session-state.d.ts +2 -0
- package/dist/templates/config/ralph.config.cjs.tmpl +1 -1
- package/dist/templates/prompts/PROMPT_e2e.md.tmpl +151 -0
- package/dist/templates/prompts/PROMPT_feature.md.tmpl +23 -0
- package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +13 -42
- package/dist/templates/prompts/PROMPT_review_merge.md.tmpl +168 -0
- package/dist/templates/scripts/feature-loop-actions.test.ts +92 -0
- package/dist/templates/scripts/feature-loop.sh.tmpl +334 -40
- package/dist/tui/app.d.ts +16 -1
- package/dist/tui/app.js +31 -5
- package/dist/tui/components/ActivityFeed.d.ts +18 -0
- package/dist/tui/components/ActivityFeed.js +31 -0
- package/dist/tui/components/ChatInput.d.ts +3 -1
- package/dist/tui/components/ChatInput.js +23 -4
- package/dist/tui/components/CommandDropdown.d.ts +3 -1
- package/dist/tui/components/CommandDropdown.js +10 -7
- package/dist/tui/components/RunCompletionSummary.d.ts +27 -1
- package/dist/tui/components/RunCompletionSummary.js +97 -7
- package/dist/tui/components/SpecCompletionSummary.d.ts +3 -2
- package/dist/tui/components/SpecCompletionSummary.js +26 -9
- package/dist/tui/components/SummaryBox.d.ts +4 -3
- package/dist/tui/components/SummaryBox.js +7 -3
- package/dist/tui/hooks/useBackgroundRuns.js +1 -1
- package/dist/tui/orchestration/interview-orchestrator.js +35 -5
- package/dist/tui/screens/MainShell.js +2 -1
- package/dist/tui/screens/RunScreen.d.ts +15 -15
- package/dist/tui/screens/RunScreen.js +139 -17
- package/dist/tui/utils/action-inbox.d.ts +43 -0
- package/dist/tui/utils/action-inbox.js +109 -0
- package/dist/tui/utils/build-run-summary.js +4 -1
- package/dist/tui/utils/git-summary.d.ts +13 -0
- package/dist/tui/utils/git-summary.js +30 -0
- package/dist/tui/utils/loop-status.d.ts +54 -0
- package/dist/tui/utils/loop-status.js +213 -1
- package/dist/tui/utils/polishGoal.d.ts +37 -0
- package/dist/tui/utils/polishGoal.js +170 -0
- package/dist/utils/ci.d.ts +8 -0
- package/dist/utils/ci.js +13 -0
- package/dist/utils/config.d.ts +1 -1
- package/dist/utils/fuzzy-match.d.ts +5 -0
- package/dist/utils/fuzzy-match.js +16 -0
- package/dist/utils/spec-names.d.ts +6 -0
- package/dist/utils/spec-names.js +27 -0
- package/package.json +15 -5
- package/src/templates/config/ralph.config.cjs.tmpl +1 -1
- package/src/templates/prompts/PROMPT_e2e.md.tmpl +151 -0
- package/src/templates/prompts/PROMPT_feature.md.tmpl +23 -0
- package/src/templates/prompts/PROMPT_review_auto.md.tmpl +13 -42
- package/src/templates/prompts/PROMPT_review_merge.md.tmpl +168 -0
- package/src/templates/scripts/feature-loop-actions.test.ts +92 -0
- 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)
|
|
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 /
|
|
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
|
-
│
|
|
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:
|
|
222
|
-
| `--max-e2e-attempts <n>` | Max E2E retries (default:
|
|
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).
|
package/dist/ai/providers.js
CHANGED
|
@@ -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-
|
|
38
|
-
{ value: 'claude-sonnet-4-5-
|
|
39
|
-
{ value: 'claude-haiku-4-5-
|
|
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.
|
|
43
|
-
{ value: 'gpt-5.
|
|
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: '
|
|
50
|
-
{ value: '
|
|
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-
|
|
60
|
-
openai: 'gpt-5.
|
|
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-
|
|
68
|
-
opus: 'claude-opus-4-
|
|
69
|
-
haiku: 'claude-haiku-4-5-
|
|
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
|
package/dist/commands/run.d.ts
CHANGED
package/dist/commands/run.js
CHANGED
|
@@ -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 '
|
|
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);
|
|
@@ -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',
|
|
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
|
-
|
|
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
|
|
188
|
+
const parsed = parseCliArgs(process.argv.slice(2));
|
|
96
189
|
// Check for updates (non-blocking, fails silently)
|
|
97
190
|
await notifyIfUpdateAvailable();
|
|
98
|
-
//
|
|
99
|
-
if (
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
const featureName =
|
|
257
|
+
}
|
|
258
|
+
case 'new': {
|
|
259
|
+
const featureName = parsed.positionalArgs[0];
|
|
113
260
|
if (!featureName) {
|
|
114
|
-
|
|
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
|
-
|
|
265
|
+
// TODO: pass parsed flags to startInkTui once TUI supports new flags
|
|
266
|
+
await startInkTui('interview', { interviewFeature: featureName });
|
|
118
267
|
break;
|
|
119
|
-
|
|
120
|
-
case '
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
};
|