idea-gauntlet 0.1.0 → 0.2.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 +131 -49
- package/dist/{chunk-P4FDULQC.js → chunk-CMSHOKHT.js} +103 -131
- package/dist/{chunk-A6GCV4RD.js → chunk-JTEPYPKW.js} +110 -117
- package/dist/{chunk-HW6JACOL.js → chunk-SPGNYKGN.js} +8 -4
- package/dist/cli/index.js +65 -49
- package/dist/index.d.ts +5 -11
- package/dist/index.js +3 -37
- package/dist/{server-FLE4IK6S.js → server-VKTKEXIE.js} +2 -2
- package/dist/{setup-QMYBP3QE.js → setup-HR7PTLSY.js} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,93 +6,161 @@ Stress-test product ideas with adversarial agents, synthetic users, and court-st
|
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/idea-gauntlet)
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## What is IdeaGauntlet?
|
|
10
|
+
|
|
11
|
+
IdeaGauntlet is a TypeScript CLI and library for product discovery. It turns a product idea into structured critique: risks, assumptions, synthetic user objections, court-style debate, MVP validation plans, and side-by-side idea comparisons.
|
|
12
|
+
|
|
13
|
+
It is not a startup idea generator, chatbot, or replacement for real user research. It is a pre-validation tool for sharper thinking before you build.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
10
16
|
|
|
11
17
|
```bash
|
|
12
|
-
|
|
18
|
+
npm install -g idea-gauntlet
|
|
13
19
|
```
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
Or run without installing:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx idea-gauntlet prompt quick "Your product idea here"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Provider setup
|
|
28
|
+
|
|
29
|
+
Standalone generation requires an LLM provider. IdeaGauntlet supports OpenAI-compatible APIs and local Ollama.
|
|
30
|
+
|
|
31
|
+
Set these environment variables for an OpenAI-compatible provider:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
export IDEAGAUNTLET_API_KEY="<your-provider-key>"
|
|
35
|
+
export IDEAGAUNTLET_BASE_URL="https://api.openai.com/v1"
|
|
36
|
+
export IDEAGAUNTLET_MODEL="gpt-4o-mini"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
PowerShell:
|
|
40
|
+
|
|
41
|
+
```powershell
|
|
42
|
+
$env:IDEAGAUNTLET_API_KEY="<your-provider-key>"
|
|
43
|
+
$env:IDEAGAUNTLET_BASE_URL="https://api.openai.com/v1"
|
|
44
|
+
$env:IDEAGAUNTLET_MODEL="gpt-4o-mini"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Use Ollama locally:
|
|
16
48
|
|
|
17
49
|
```bash
|
|
18
|
-
|
|
50
|
+
ollama serve
|
|
51
|
+
idea-gauntlet quick "Your idea" --ollama --model llama3
|
|
19
52
|
```
|
|
20
53
|
|
|
21
|
-
|
|
54
|
+
No provider is required for prompt and integration workflows:
|
|
22
55
|
|
|
23
56
|
```bash
|
|
24
|
-
|
|
57
|
+
idea-gauntlet prompt quick "Your idea"
|
|
58
|
+
idea-gauntlet setup --dry-run --all
|
|
25
59
|
```
|
|
26
60
|
|
|
27
|
-
##
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
idea-gauntlet quick "A synthetic focus room app for remote workers"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If you do not want IdeaGauntlet to call a model provider, generate a structured prompt instead:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
idea-gauntlet prompt quick "A synthetic focus room app for remote workers"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Usage paths
|
|
74
|
+
|
|
75
|
+
| Path | Provider required? | Description |
|
|
76
|
+
|---|---:|---|
|
|
77
|
+
| Standalone CLI | Yes | Runs critique directly through an OpenAI-compatible API or Ollama |
|
|
78
|
+
| Prompt mode | No | Generates structured prompts for Claude, ChatGPT, Cursor, Codex, or any LLM |
|
|
79
|
+
| Agent-native setup | No | Generates Claude Code, Cursor, Codex, and GitHub Action integration files |
|
|
80
|
+
| MCP server | Partial | Generation tools require a provider; `create_prompt` does not |
|
|
28
81
|
|
|
29
|
-
|
|
30
|
-
|---|---|---|
|
|
31
|
-
| **Standalone CLI** | API key or Ollama | `idea-gauntlet quick "idea"` |
|
|
32
|
-
| **Mock mode** | None needed | `idea-gauntlet quick "idea" --mock` |
|
|
33
|
-
| **Prompt mode** | None needed | `idea-gauntlet prompt quick "idea"` |
|
|
34
|
-
| **Agent-native** | None needed | `idea-gauntlet setup` |
|
|
82
|
+
## CLI commands
|
|
35
83
|
|
|
36
|
-
|
|
84
|
+
### `quick`
|
|
37
85
|
|
|
38
|
-
### quick
|
|
39
86
|
Fast adversarial critique.
|
|
87
|
+
|
|
40
88
|
```bash
|
|
41
|
-
idea-gauntlet quick "Your idea" [--
|
|
89
|
+
idea-gauntlet quick "Your idea" [--json] [--output report.md]
|
|
42
90
|
```
|
|
43
91
|
|
|
44
|
-
### court
|
|
92
|
+
### `court`
|
|
93
|
+
|
|
45
94
|
Multi-role structured debate.
|
|
95
|
+
|
|
46
96
|
```bash
|
|
47
|
-
idea-gauntlet court idea.md
|
|
97
|
+
idea-gauntlet court idea.md
|
|
48
98
|
```
|
|
49
99
|
|
|
50
|
-
### users
|
|
51
|
-
|
|
100
|
+
### `users`
|
|
101
|
+
|
|
102
|
+
Generate synthetic user personas for hypothesis discovery. These are fictional archetypes, not validation.
|
|
103
|
+
|
|
52
104
|
```bash
|
|
53
|
-
idea-gauntlet users idea.md --personas 6
|
|
105
|
+
idea-gauntlet users idea.md --personas 6
|
|
54
106
|
```
|
|
55
107
|
|
|
56
|
-
### mvp
|
|
108
|
+
### `mvp`
|
|
109
|
+
|
|
57
110
|
Generate a 14-day validation plan.
|
|
111
|
+
|
|
58
112
|
```bash
|
|
59
|
-
idea-gauntlet mvp idea.md
|
|
113
|
+
idea-gauntlet mvp idea.md
|
|
60
114
|
```
|
|
61
115
|
|
|
62
|
-
### compare
|
|
116
|
+
### `compare`
|
|
117
|
+
|
|
63
118
|
Compare multiple ideas.
|
|
119
|
+
|
|
64
120
|
```bash
|
|
65
|
-
idea-gauntlet compare idea1.md idea2.md
|
|
121
|
+
idea-gauntlet compare idea1.md idea2.md
|
|
66
122
|
```
|
|
67
123
|
|
|
68
|
-
### prompt
|
|
69
|
-
|
|
124
|
+
### `prompt`
|
|
125
|
+
|
|
126
|
+
Generate structured prompts without making an LLM call.
|
|
127
|
+
|
|
70
128
|
```bash
|
|
71
129
|
idea-gauntlet prompt quick "Your idea"
|
|
72
130
|
idea-gauntlet prompt court idea.md
|
|
73
131
|
idea-gauntlet prompt users idea.md --personas 8
|
|
132
|
+
idea-gauntlet prompt mvp idea.md
|
|
74
133
|
```
|
|
75
134
|
|
|
76
|
-
### init
|
|
77
|
-
|
|
135
|
+
### `init`
|
|
136
|
+
|
|
137
|
+
Scaffold a local IdeaGauntlet workspace.
|
|
138
|
+
|
|
78
139
|
```bash
|
|
79
|
-
idea-gauntlet init
|
|
140
|
+
idea-gauntlet init
|
|
80
141
|
```
|
|
81
142
|
|
|
82
|
-
### setup
|
|
83
|
-
|
|
143
|
+
### `setup`
|
|
144
|
+
|
|
145
|
+
Generate integration files for Claude Code, Cursor, Codex, MCP, and GitHub Actions.
|
|
146
|
+
|
|
84
147
|
```bash
|
|
85
|
-
idea-gauntlet setup
|
|
148
|
+
idea-gauntlet setup --dry-run --all
|
|
149
|
+
idea-gauntlet setup --targets claude-skills,codex,cursor
|
|
86
150
|
```
|
|
87
151
|
|
|
88
|
-
### doctor
|
|
152
|
+
### `doctor`
|
|
153
|
+
|
|
89
154
|
Check environment and configuration.
|
|
155
|
+
|
|
90
156
|
```bash
|
|
91
|
-
idea-gauntlet doctor
|
|
157
|
+
idea-gauntlet doctor --verbose
|
|
92
158
|
```
|
|
93
159
|
|
|
94
|
-
### mcp
|
|
95
|
-
|
|
160
|
+
### `mcp`
|
|
161
|
+
|
|
162
|
+
Start an MCP server for compatible clients.
|
|
163
|
+
|
|
96
164
|
```bash
|
|
97
165
|
idea-gauntlet mcp
|
|
98
166
|
```
|
|
@@ -100,19 +168,37 @@ idea-gauntlet mcp
|
|
|
100
168
|
## TypeScript API
|
|
101
169
|
|
|
102
170
|
```typescript
|
|
103
|
-
import { runGauntlet,
|
|
171
|
+
import { runGauntlet, OpenAICompatibleProvider } from "idea-gauntlet";
|
|
172
|
+
|
|
173
|
+
const provider = new OpenAICompatibleProvider({
|
|
174
|
+
apiKey: process.env.IDEAGAUNTLET_API_KEY!,
|
|
175
|
+
model: "gpt-4o-mini",
|
|
176
|
+
});
|
|
104
177
|
|
|
105
178
|
const report = await runGauntlet({
|
|
106
179
|
idea: "A synthetic focus room app for remote workers",
|
|
107
180
|
targetUsers: ["remote workers", "students"],
|
|
108
181
|
mode: "quick",
|
|
109
|
-
provider
|
|
182
|
+
provider,
|
|
110
183
|
});
|
|
111
184
|
|
|
112
185
|
console.log(report.markdown);
|
|
113
186
|
```
|
|
114
187
|
|
|
115
|
-
|
|
188
|
+
Custom providers can implement the `LLMProvider` interface:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import type { LLMProvider } from "idea-gauntlet";
|
|
192
|
+
|
|
193
|
+
const provider: LLMProvider = {
|
|
194
|
+
kind: "custom",
|
|
195
|
+
async complete(prompt, options) {
|
|
196
|
+
return "{}";
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Scoring philosophy
|
|
116
202
|
|
|
117
203
|
Scores are diagnostic signals, not predictions.
|
|
118
204
|
|
|
@@ -126,24 +212,20 @@ Scores are diagnostic signals, not predictions.
|
|
|
126
212
|
| Monetization | Is there a clear revenue model? |
|
|
127
213
|
| Evidence | What real evidence supports the idea? |
|
|
128
214
|
|
|
129
|
-
Evidence score defaults low unless you provide real user evidence.
|
|
130
|
-
|
|
131
|
-
## No API Key? No Problem.
|
|
215
|
+
Evidence score defaults low unless you provide real user evidence. Synthetic users are fictional archetypes for hypothesis generation only.
|
|
132
216
|
|
|
133
|
-
|
|
134
|
-
- **Mock mode** runs deterministically for demos
|
|
135
|
-
- **Agent-native mode** generates Claude/Cursor/Codex integration files
|
|
136
|
-
- **Setup** never requires an API key
|
|
137
|
-
|
|
138
|
-
## Product Philosophy
|
|
217
|
+
## Product philosophy
|
|
139
218
|
|
|
140
219
|
IdeaGauntlet is:
|
|
220
|
+
|
|
141
221
|
- adversarial but useful
|
|
142
222
|
- skeptical but not cynical
|
|
143
223
|
- evidence-aware
|
|
144
224
|
- honest about uncertainty
|
|
225
|
+
- designed to produce validation work, not founder comfort
|
|
145
226
|
|
|
146
227
|
IdeaGauntlet is not:
|
|
228
|
+
|
|
147
229
|
- a startup idea generator
|
|
148
230
|
- a replacement for real user research
|
|
149
231
|
- a market research oracle
|
|
@@ -295,39 +295,6 @@ function buildComparison(comparison) {
|
|
|
295
295
|
return parts.join("\n");
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
// src/providers/mockProvider.ts
|
|
299
|
-
var MockProvider = class {
|
|
300
|
-
kind = "mock";
|
|
301
|
-
async complete(prompt, options) {
|
|
302
|
-
const combined = options?.system ? `${options.system} ${prompt}` : prompt;
|
|
303
|
-
if (combined.includes("Skeptic") || combined.includes("immune")) {
|
|
304
|
-
return JSON.stringify({
|
|
305
|
-
coreInsight: "The idea targets a real pain point but assumes behavior change without sufficient incentive.",
|
|
306
|
-
strongestCase: "Users who already use existing workarounds may adopt if friction is low enough.",
|
|
307
|
-
weakestAssumption: "Users will pay for this before experiencing the full value.",
|
|
308
|
-
scores: { clarity: 6, pain: 5, differentiation: 5, buildability: 6, distribution: 3, monetization: 3, evidence: 2 },
|
|
309
|
-
risks: [
|
|
310
|
-
{ title: "Low retention after novelty wears off", severity: "high", explanation: "Users may try once and never return.", mitigation: "Build a single-session MVP first and measure Day 7 return rate." },
|
|
311
|
-
{ title: "Hard to reach target users without paid acquisition", severity: "medium", explanation: "No organic distribution channel.", mitigation: "Focus on one community (e.g., indie hackers on Twitter)." },
|
|
312
|
-
{ title: "Substitute solutions are free and habit-forming", severity: "critical", explanation: "Existing free tools already solve part of the problem.", mitigation: "Differentiate on synthetic companion angle, not features." }
|
|
313
|
-
],
|
|
314
|
-
assumptions: [
|
|
315
|
-
{ title: "Users want synthetic social presence", whyItMatters: "Core value prop depends on this", howToTest: "Fake door test with 100 visitors", confidence: "low" },
|
|
316
|
-
{ title: "Users will complete more work with companions", whyItMatters: "Retention depends on measurable productivity gain", howToTest: "A/B test with 20 users", confidence: "medium" }
|
|
317
|
-
],
|
|
318
|
-
killTests: [
|
|
319
|
-
{ title: "Fake room test", method: "Static page with timer + companion avatars", timeframe: "2 days", successSignal: "40%+ complete a session", killSignal: "<20% engagement" }
|
|
320
|
-
],
|
|
321
|
-
nextActions: ["Build static prototype this week", "Run manual sessions with 10 users"]
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
return JSON.stringify({
|
|
325
|
-
verdict: "promising_but_risky",
|
|
326
|
-
summary: "Mock verdict. The idea has a plausible wedge if positioned carefully."
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
|
|
331
298
|
// src/providers/openaiCompatibleProvider.ts
|
|
332
299
|
var OpenAICompatibleProvider = class {
|
|
333
300
|
kind = "openai";
|
|
@@ -367,6 +334,39 @@ var OpenAICompatibleProvider = class {
|
|
|
367
334
|
}
|
|
368
335
|
};
|
|
369
336
|
|
|
337
|
+
// src/providers/ollamaProvider.ts
|
|
338
|
+
var OllamaProvider = class {
|
|
339
|
+
kind = "ollama";
|
|
340
|
+
baseUrl;
|
|
341
|
+
model;
|
|
342
|
+
constructor(model, baseUrl) {
|
|
343
|
+
this.baseUrl = baseUrl ?? "http://localhost:11434";
|
|
344
|
+
this.model = model ?? "llama3";
|
|
345
|
+
}
|
|
346
|
+
async complete(prompt, options) {
|
|
347
|
+
const url = `${this.baseUrl}/api/chat`;
|
|
348
|
+
const messages = [];
|
|
349
|
+
if (options?.system) messages.push({ role: "system", content: options.system });
|
|
350
|
+
messages.push({ role: "user", content: prompt });
|
|
351
|
+
const response = await fetch(url, {
|
|
352
|
+
method: "POST",
|
|
353
|
+
headers: { "Content-Type": "application/json" },
|
|
354
|
+
body: JSON.stringify({
|
|
355
|
+
model: this.model,
|
|
356
|
+
messages,
|
|
357
|
+
stream: false,
|
|
358
|
+
options: { temperature: options?.temperature ?? 0.4, num_predict: options?.maxTokens ?? 2048 }
|
|
359
|
+
})
|
|
360
|
+
});
|
|
361
|
+
if (!response.ok) {
|
|
362
|
+
const text = await response.text();
|
|
363
|
+
throw new Error(`Ollama returned ${response.status}: ${text}`);
|
|
364
|
+
}
|
|
365
|
+
const data = await response.json();
|
|
366
|
+
return data.message?.content ?? "";
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
370
|
// src/utils/env.ts
|
|
371
371
|
function getEnv(key, defaultValue) {
|
|
372
372
|
return process.env[key] ?? defaultValue;
|
|
@@ -391,9 +391,32 @@ function isNodeGte(minMajor) {
|
|
|
391
391
|
|
|
392
392
|
// src/providers/providerUtils.ts
|
|
393
393
|
function resolveProvider(options) {
|
|
394
|
-
if (options?.
|
|
395
|
-
|
|
396
|
-
|
|
394
|
+
if (options?.apiKey) {
|
|
395
|
+
return {
|
|
396
|
+
provider: new OpenAICompatibleProvider({
|
|
397
|
+
apiKey: options.apiKey,
|
|
398
|
+
baseUrl: options.baseUrl,
|
|
399
|
+
model: options.model
|
|
400
|
+
}),
|
|
401
|
+
source: "flags"
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if (hasApiKey()) {
|
|
405
|
+
return {
|
|
406
|
+
provider: new OpenAICompatibleProvider({
|
|
407
|
+
apiKey: getApiKey(),
|
|
408
|
+
baseUrl: options?.baseUrl ?? getBaseUrl(),
|
|
409
|
+
model: options?.model ?? getModel()
|
|
410
|
+
}),
|
|
411
|
+
source: "env"
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
if (options?.ollama) {
|
|
415
|
+
return {
|
|
416
|
+
provider: new OllamaProvider(options.model),
|
|
417
|
+
source: "ollama"
|
|
418
|
+
};
|
|
419
|
+
}
|
|
397
420
|
return null;
|
|
398
421
|
}
|
|
399
422
|
function getProvider(options) {
|
|
@@ -403,7 +426,7 @@ function getProvider(options) {
|
|
|
403
426
|
}
|
|
404
427
|
var NoProviderError = class extends Error {
|
|
405
428
|
constructor() {
|
|
406
|
-
super("No LLM provider configured");
|
|
429
|
+
super("No LLM provider configured. Set IDEAGAUNTLET_API_KEY, pass --api-key, use --ollama, or use prompt/setup mode.");
|
|
407
430
|
this.name = "NoProviderError";
|
|
408
431
|
}
|
|
409
432
|
};
|
|
@@ -440,67 +463,34 @@ async function runCourtEngine(idea, provider) {
|
|
|
440
463
|
const userMessage = `Product idea: ${idea.idea}
|
|
441
464
|
|
|
442
465
|
Provide your argument in 2-3 paragraphs. Be specific and direct.`;
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
role: "Defender",
|
|
451
|
-
argument: "Mock: The synthetic companion angle is novel for early adopters."
|
|
452
|
-
},
|
|
453
|
-
{
|
|
454
|
-
role: "User Advocate",
|
|
455
|
-
argument: "Mock: Users want better focus but may not want AI companionship."
|
|
456
|
-
},
|
|
457
|
-
{
|
|
458
|
-
role: "Investor",
|
|
459
|
-
argument: "Mock: Niche market with unclear defensibility."
|
|
460
|
-
},
|
|
461
|
-
{
|
|
462
|
-
role: "Competitor",
|
|
463
|
-
argument: "Mock: Existing focus apps could add companion features."
|
|
464
|
-
}
|
|
465
|
-
);
|
|
466
|
-
} else {
|
|
467
|
-
for (const role of COURT_ROLES) {
|
|
468
|
-
const response = await provider.complete(userMessage, {
|
|
469
|
-
system: role.system,
|
|
470
|
-
temperature: 0.4,
|
|
471
|
-
maxTokens: 512
|
|
472
|
-
});
|
|
473
|
-
transcript.push({ role: role.name, argument: response.trim() });
|
|
474
|
-
}
|
|
466
|
+
for (const role of COURT_ROLES) {
|
|
467
|
+
const response = await provider.complete(userMessage, {
|
|
468
|
+
system: role.system,
|
|
469
|
+
temperature: 0.4,
|
|
470
|
+
maxTokens: 512
|
|
471
|
+
});
|
|
472
|
+
transcript.push({ role: role.name, argument: response.trim() });
|
|
475
473
|
}
|
|
476
474
|
let verdictText = "Court debate completed. See transcript for details.";
|
|
477
475
|
let unresolvedQuestions = [];
|
|
478
476
|
let reportVerdict = "unclear";
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
"Do users feel motivated after one week?",
|
|
483
|
-
"Will users pay?"
|
|
484
|
-
];
|
|
485
|
-
reportVerdict = "promising_but_risky";
|
|
486
|
-
} else {
|
|
487
|
-
try {
|
|
488
|
-
const judgeResponse = await provider.complete(
|
|
489
|
-
`Court transcript:
|
|
477
|
+
try {
|
|
478
|
+
const judgeResponse = await provider.complete(
|
|
479
|
+
`Court transcript:
|
|
490
480
|
${transcript.map((t) => `${t.role}: ${t.argument}`).join("\n\n")}
|
|
491
481
|
|
|
492
482
|
Synthesize a verdict as JSON with verdict and unresolvedQuestions.`,
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
483
|
+
{
|
|
484
|
+
system: "You are the Judge in IdeaGauntlet. Synthesize arguments into a conservative verdict.",
|
|
485
|
+
temperature: 0.3,
|
|
486
|
+
maxTokens: 512
|
|
487
|
+
}
|
|
488
|
+
);
|
|
489
|
+
const parsed = JSON.parse(judgeResponse);
|
|
490
|
+
verdictText = parsed.verdict ?? verdictText;
|
|
491
|
+
unresolvedQuestions = parsed.unresolvedQuestions ?? [];
|
|
492
|
+
reportVerdict = normalizeVerdict(parsed.reportVerdict ?? parsed.status ?? parsed.verdictType, reportVerdict);
|
|
493
|
+
} catch {
|
|
504
494
|
}
|
|
505
495
|
const report = {
|
|
506
496
|
id,
|
|
@@ -518,6 +508,17 @@ Synthesize a verdict as JSON with verdict and unresolvedQuestions.`,
|
|
|
518
508
|
report.markdown = buildReport(report);
|
|
519
509
|
return report;
|
|
520
510
|
}
|
|
511
|
+
function normalizeVerdict(value, fallback) {
|
|
512
|
+
const allowed = [
|
|
513
|
+
"strong",
|
|
514
|
+
"promising_but_risky",
|
|
515
|
+
"unclear",
|
|
516
|
+
"weak",
|
|
517
|
+
"needs_real_evidence",
|
|
518
|
+
"pivot_recommended"
|
|
519
|
+
];
|
|
520
|
+
return typeof value === "string" && allowed.includes(value) ? value : fallback;
|
|
521
|
+
}
|
|
521
522
|
|
|
522
523
|
// src/engines/syntheticUserLab.ts
|
|
523
524
|
async function runUserLab(idea, provider, count = 6) {
|
|
@@ -529,45 +530,16 @@ Target users: ${idea.targetUsers.join(", ")}` : ""}
|
|
|
529
530
|
|
|
530
531
|
Generate ${count} fictional user archetypes as JSON.`;
|
|
531
532
|
let users = [];
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
likelyChurnReason: "Novelty fades after one week",
|
|
543
|
-
quote: "I'll try anything during finals.",
|
|
544
|
-
interviewQuestion: "What do you do when you can't focus?"
|
|
545
|
-
},
|
|
546
|
-
{
|
|
547
|
-
name: "Maya Rodriguez",
|
|
548
|
-
archetype: "Remote Designer",
|
|
549
|
-
goal: "Deep work without isolation",
|
|
550
|
-
currentWorkaround: "Coworking cafes",
|
|
551
|
-
triggerToTry: "Bad weather days",
|
|
552
|
-
primaryObjection: "AI companions can't replace real presence",
|
|
553
|
-
willingnessToPay: "medium",
|
|
554
|
-
likelyChurnReason: "Not enough social feedback",
|
|
555
|
-
quote: "A fake coworker might feel worse than silence.",
|
|
556
|
-
interviewQuestion: "What aspect of coworking is hardest to replicate alone?"
|
|
557
|
-
}
|
|
558
|
-
];
|
|
559
|
-
} else {
|
|
560
|
-
try {
|
|
561
|
-
const response = await provider.complete(userMessage, {
|
|
562
|
-
system: systemPrompt,
|
|
563
|
-
temperature: 0.5,
|
|
564
|
-
maxTokens: 2048
|
|
565
|
-
});
|
|
566
|
-
const parsed = JSON.parse(response);
|
|
567
|
-
users = (parsed.users ?? []).slice(0, count);
|
|
568
|
-
} catch {
|
|
569
|
-
users = [];
|
|
570
|
-
}
|
|
533
|
+
try {
|
|
534
|
+
const response = await provider.complete(userMessage, {
|
|
535
|
+
system: systemPrompt,
|
|
536
|
+
temperature: 0.5,
|
|
537
|
+
maxTokens: 2048
|
|
538
|
+
});
|
|
539
|
+
const parsed = JSON.parse(response);
|
|
540
|
+
users = (parsed.users ?? []).slice(0, count);
|
|
541
|
+
} catch {
|
|
542
|
+
users = [];
|
|
571
543
|
}
|
|
572
544
|
const report = {
|
|
573
545
|
id,
|
|
@@ -693,8 +665,8 @@ export {
|
|
|
693
665
|
runUserLab,
|
|
694
666
|
runMvpPlanner,
|
|
695
667
|
runCompareEngine,
|
|
696
|
-
MockProvider,
|
|
697
668
|
OpenAICompatibleProvider,
|
|
669
|
+
OllamaProvider,
|
|
698
670
|
getApiKey,
|
|
699
671
|
isNodeGte,
|
|
700
672
|
resolveProvider,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
MockProvider,
|
|
3
2
|
buildReport,
|
|
4
3
|
resolveProvider,
|
|
5
4
|
runCompareEngine,
|
|
@@ -7,7 +6,7 @@ import {
|
|
|
7
6
|
runImmuneEngine,
|
|
8
7
|
runMvpPlanner,
|
|
9
8
|
runUserLab
|
|
10
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-CMSHOKHT.js";
|
|
11
10
|
import {
|
|
12
11
|
skeptic
|
|
13
12
|
} from "./chunk-VQHEJYTS.js";
|
|
@@ -65,136 +64,38 @@ function safeWriteReport(reportId, content, workspaceDir) {
|
|
|
65
64
|
|
|
66
65
|
// src/mcp/tools.ts
|
|
67
66
|
var reports = /* @__PURE__ */ new Map();
|
|
68
|
-
var provider =
|
|
67
|
+
var provider = null;
|
|
69
68
|
var resolved = resolveProvider({});
|
|
70
69
|
if (resolved) provider = resolved.provider;
|
|
71
70
|
function getReportIds() {
|
|
72
71
|
return Array.from(reports.keys());
|
|
73
72
|
}
|
|
73
|
+
function requireProvider() {
|
|
74
|
+
if (!provider) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
"No LLM provider configured. Set IDEAGAUNTLET_API_KEY, configure an OpenAI-compatible provider, or use create_prompt for no-provider workflows."
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return provider;
|
|
80
|
+
}
|
|
74
81
|
async function handleToolCall(name, args) {
|
|
75
82
|
switch (name) {
|
|
76
83
|
case "create_prompt": {
|
|
77
84
|
const mode = args.mode ?? "quick";
|
|
78
85
|
const ideaText = args.idea ?? "";
|
|
79
86
|
if (!ideaText) throw new Error("idea parameter required");
|
|
80
|
-
|
|
81
|
-
const idea = { idea: ideaText };
|
|
82
|
-
switch (mode) {
|
|
83
|
-
case "quick": {
|
|
84
|
-
const sk = skeptic(idea);
|
|
85
|
-
const df = defender(idea);
|
|
86
|
-
output = [
|
|
87
|
-
"# IdeaGauntlet \u2014 Quick Critique Prompt",
|
|
88
|
-
"",
|
|
89
|
-
"Copy the prompts below into your AI assistant.",
|
|
90
|
-
"",
|
|
91
|
-
"---",
|
|
92
|
-
"",
|
|
93
|
-
"## Prompt 1: Skeptic",
|
|
94
|
-
`**System:** ${sk.system}`,
|
|
95
|
-
"",
|
|
96
|
-
sk.userMessage,
|
|
97
|
-
"",
|
|
98
|
-
"## Prompt 2: Defender",
|
|
99
|
-
`**System:** ${df.system}`,
|
|
100
|
-
"",
|
|
101
|
-
df.userMessage,
|
|
102
|
-
"",
|
|
103
|
-
"## Instructions for the AI",
|
|
104
|
-
"Respond to both prompts, then synthesize a verdict with: core insight, strongest case, weakest assumption, top failure modes, dangerous assumptions, kill tests, and next actions."
|
|
105
|
-
].join("\n");
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
case "court": {
|
|
109
|
-
const roles = [
|
|
110
|
-
"Prosecutor",
|
|
111
|
-
"Defender",
|
|
112
|
-
"User Advocate",
|
|
113
|
-
"Investor",
|
|
114
|
-
"Competitor",
|
|
115
|
-
"Judge"
|
|
116
|
-
];
|
|
117
|
-
const roleDescriptions = [
|
|
118
|
-
"attacks the idea",
|
|
119
|
-
"argues why it could work",
|
|
120
|
-
"argues from the user's perspective",
|
|
121
|
-
"evaluates market, scale, defensibility",
|
|
122
|
-
"explains how the idea could be copied or crushed",
|
|
123
|
-
"summarizes the verdict"
|
|
124
|
-
];
|
|
125
|
-
output = [
|
|
126
|
-
"# IdeaGauntlet \u2014 Court Debate Prompt",
|
|
127
|
-
"",
|
|
128
|
-
`Product idea: ${ideaText}`,
|
|
129
|
-
"",
|
|
130
|
-
"Run a structured debate with these roles:",
|
|
131
|
-
...roles.map(
|
|
132
|
-
(r, i) => `${i + 1}. **${r}** \u2014 ${roleDescriptions[i]}`
|
|
133
|
-
),
|
|
134
|
-
"",
|
|
135
|
-
"Each role speaks once, max 300 words. End with a judge verdict and unresolved questions."
|
|
136
|
-
].join("\n");
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
case "users": {
|
|
140
|
-
const count = args.personas ?? "6";
|
|
141
|
-
output = [
|
|
142
|
-
"# IdeaGauntlet \u2014 Synthetic User Prompt",
|
|
143
|
-
"",
|
|
144
|
-
`Product idea: ${ideaText}`,
|
|
145
|
-
"",
|
|
146
|
-
`Generate ${count} fictional user archetypes who would encounter this product.`,
|
|
147
|
-
"",
|
|
148
|
-
"For each persona, include:",
|
|
149
|
-
"- Name and archetype label",
|
|
150
|
-
"- Goal they are trying to accomplish",
|
|
151
|
-
"- Current workaround (what they do today)",
|
|
152
|
-
"- Trigger that would make them try this product",
|
|
153
|
-
"- Primary objection (why they would hesitate)",
|
|
154
|
-
"- Willingness to pay (none/low/medium/high)",
|
|
155
|
-
"- Likely reason they would churn",
|
|
156
|
-
"- A quote expressing their skepticism",
|
|
157
|
-
"- One question a founder should ask a real user like this",
|
|
158
|
-
"",
|
|
159
|
-
"**IMPORTANT:** These are fictional archetypes for hypothesis generation, not real validation."
|
|
160
|
-
].join("\n");
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
case "mvp": {
|
|
164
|
-
output = [
|
|
165
|
-
"# IdeaGauntlet \u2014 MVP Validation Plan Prompt",
|
|
166
|
-
"",
|
|
167
|
-
`Product idea: ${ideaText}`,
|
|
168
|
-
"",
|
|
169
|
-
"Generate an aggressive MVP validation plan with:",
|
|
170
|
-
"- 14-day MVP plan (max 3 things to build)",
|
|
171
|
-
"- Fake-door test design (what would the landing page say?)",
|
|
172
|
-
"- User interview script (5 questions to ask)",
|
|
173
|
-
"- Success metrics (what numbers justify continuing?)",
|
|
174
|
-
"- Kill criteria (what results mean pivot or stop?)",
|
|
175
|
-
"- Pivot options (adjacent directions if the core doesn't work)",
|
|
176
|
-
"",
|
|
177
|
-
"Be aggressive about reducing scope."
|
|
178
|
-
].join("\n");
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
default:
|
|
182
|
-
throw new Error(
|
|
183
|
-
`Unknown prompt mode: ${mode}. Use: quick, court, users, mvp`
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
return { type: "text", text: output };
|
|
87
|
+
return { type: "text", text: buildPrompt(mode, ideaText, args.personas) };
|
|
187
88
|
}
|
|
188
89
|
case "quick_critique": {
|
|
189
90
|
if (!args.idea) throw new Error("idea required");
|
|
190
|
-
const report = await runImmuneEngine({ idea: args.idea },
|
|
91
|
+
const report = await runImmuneEngine({ idea: args.idea }, requireProvider());
|
|
191
92
|
report.markdown = buildReport(report);
|
|
192
93
|
reports.set(report.id, report);
|
|
193
94
|
return { type: "text", text: report.markdown };
|
|
194
95
|
}
|
|
195
96
|
case "run_court": {
|
|
196
97
|
if (!args.idea) throw new Error("idea required");
|
|
197
|
-
const report = await runCourtEngine({ idea: args.idea },
|
|
98
|
+
const report = await runCourtEngine({ idea: args.idea }, requireProvider());
|
|
198
99
|
reports.set(report.id, report);
|
|
199
100
|
return { type: "text", text: report.markdown };
|
|
200
101
|
}
|
|
@@ -202,7 +103,7 @@ async function handleToolCall(name, args) {
|
|
|
202
103
|
if (!args.idea) throw new Error("idea required");
|
|
203
104
|
const report = await runUserLab(
|
|
204
105
|
{ idea: args.idea },
|
|
205
|
-
|
|
106
|
+
requireProvider(),
|
|
206
107
|
args.personas ?? 6
|
|
207
108
|
);
|
|
208
109
|
reports.set(report.id, report);
|
|
@@ -210,7 +111,7 @@ async function handleToolCall(name, args) {
|
|
|
210
111
|
}
|
|
211
112
|
case "plan_mvp": {
|
|
212
113
|
if (!args.idea) throw new Error("idea required");
|
|
213
|
-
const report = await runMvpPlanner({ idea: args.idea },
|
|
114
|
+
const report = await runMvpPlanner({ idea: args.idea }, requireProvider());
|
|
214
115
|
reports.set(report.id, report);
|
|
215
116
|
return { type: "text", text: report.markdown };
|
|
216
117
|
}
|
|
@@ -218,7 +119,7 @@ async function handleToolCall(name, args) {
|
|
|
218
119
|
if (!args.ideas || !Array.isArray(args.ideas))
|
|
219
120
|
throw new Error("ideas array required");
|
|
220
121
|
const ideas = args.ideas.map((i) => ({ idea: i }));
|
|
221
|
-
const report = await runCompareEngine(ideas,
|
|
122
|
+
const report = await runCompareEngine(ideas, requireProvider());
|
|
222
123
|
reports.set(report.id, report);
|
|
223
124
|
return { type: "text", text: report.markdown };
|
|
224
125
|
}
|
|
@@ -234,6 +135,96 @@ async function handleToolCall(name, args) {
|
|
|
234
135
|
throw new Error(`Unknown tool: ${name}`);
|
|
235
136
|
}
|
|
236
137
|
}
|
|
138
|
+
function buildPrompt(mode, ideaText, personas) {
|
|
139
|
+
const idea = { idea: ideaText };
|
|
140
|
+
switch (mode) {
|
|
141
|
+
case "quick": {
|
|
142
|
+
const sk = skeptic(idea);
|
|
143
|
+
const df = defender(idea);
|
|
144
|
+
return [
|
|
145
|
+
"# IdeaGauntlet \u2014 Quick Critique Prompt",
|
|
146
|
+
"",
|
|
147
|
+
"Copy the prompts below into your AI assistant.",
|
|
148
|
+
"",
|
|
149
|
+
"---",
|
|
150
|
+
"",
|
|
151
|
+
"## Prompt 1: Skeptic",
|
|
152
|
+
`**System:** ${sk.system}`,
|
|
153
|
+
"",
|
|
154
|
+
sk.userMessage,
|
|
155
|
+
"",
|
|
156
|
+
"## Prompt 2: Defender",
|
|
157
|
+
`**System:** ${df.system}`,
|
|
158
|
+
"",
|
|
159
|
+
df.userMessage,
|
|
160
|
+
"",
|
|
161
|
+
"## Instructions for the AI",
|
|
162
|
+
"Respond to both prompts, then synthesize a verdict with: core insight, strongest case, weakest assumption, top failure modes, dangerous assumptions, kill tests, and next actions."
|
|
163
|
+
].join("\n");
|
|
164
|
+
}
|
|
165
|
+
case "court": {
|
|
166
|
+
const roles = [
|
|
167
|
+
"Prosecutor \u2014 attacks the idea",
|
|
168
|
+
"Defender \u2014 argues why it could work",
|
|
169
|
+
"User Advocate \u2014 argues from the user's perspective",
|
|
170
|
+
"Investor \u2014 evaluates market, scale, defensibility",
|
|
171
|
+
"Competitor \u2014 explains how the idea could be copied or crushed",
|
|
172
|
+
"Judge \u2014 summarizes the verdict"
|
|
173
|
+
];
|
|
174
|
+
return [
|
|
175
|
+
"# IdeaGauntlet \u2014 Court Debate Prompt",
|
|
176
|
+
"",
|
|
177
|
+
`Product idea: ${ideaText}`,
|
|
178
|
+
"",
|
|
179
|
+
"Run a structured debate with these roles:",
|
|
180
|
+
...roles.map((r, i) => `${i + 1}. **${r.split(" \u2014 ")[0]}** \u2014 ${r.split(" \u2014 ")[1]}`),
|
|
181
|
+
"",
|
|
182
|
+
"Each role speaks once, max 300 words. End with a judge verdict and unresolved questions."
|
|
183
|
+
].join("\n");
|
|
184
|
+
}
|
|
185
|
+
case "users": {
|
|
186
|
+
const count = personas ?? 6;
|
|
187
|
+
return [
|
|
188
|
+
"# IdeaGauntlet \u2014 Synthetic User Prompt",
|
|
189
|
+
"",
|
|
190
|
+
`Product idea: ${ideaText}`,
|
|
191
|
+
"",
|
|
192
|
+
`Generate ${count} fictional user archetypes who would encounter this product.`,
|
|
193
|
+
"",
|
|
194
|
+
"For each persona, include:",
|
|
195
|
+
"- Name and archetype label",
|
|
196
|
+
"- Goal they are trying to accomplish",
|
|
197
|
+
"- Current workaround (what they do today)",
|
|
198
|
+
"- Trigger that would make them try this product",
|
|
199
|
+
"- Primary objection (why they would hesitate)",
|
|
200
|
+
"- Willingness to pay (none/low/medium/high)",
|
|
201
|
+
"- Likely reason they would churn",
|
|
202
|
+
"- A quote expressing their skepticism",
|
|
203
|
+
"- One question a founder should ask a real user like this",
|
|
204
|
+
"",
|
|
205
|
+
"**IMPORTANT:** These are fictional archetypes for hypothesis generation, not real validation."
|
|
206
|
+
].join("\n");
|
|
207
|
+
}
|
|
208
|
+
case "mvp":
|
|
209
|
+
return [
|
|
210
|
+
"# IdeaGauntlet \u2014 MVP Validation Plan Prompt",
|
|
211
|
+
"",
|
|
212
|
+
`Product idea: ${ideaText}`,
|
|
213
|
+
"",
|
|
214
|
+
"Generate an aggressive MVP validation plan with:",
|
|
215
|
+
"- 14-day MVP plan (max 3 things to build)",
|
|
216
|
+
"- Fake-door test design (what would the landing page say?)",
|
|
217
|
+
"- User interview script (5 questions to ask)",
|
|
218
|
+
"- Success metrics (what numbers justify continuing?)",
|
|
219
|
+
"- Kill criteria (what results mean pivot or stop?)",
|
|
220
|
+
"- Pivot options (adjacent directions if the core doesn't work)",
|
|
221
|
+
"",
|
|
222
|
+
"Be aggressive about reducing scope."
|
|
223
|
+
].join("\n");
|
|
224
|
+
default:
|
|
225
|
+
throw new Error(`Unknown prompt mode: ${mode}. Use: quick, court, users, mvp`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
237
228
|
var toolDefinitions = [
|
|
238
229
|
{
|
|
239
230
|
name: "create_prompt",
|
|
@@ -242,8 +233,10 @@ var toolDefinitions = [
|
|
|
242
233
|
type: "object",
|
|
243
234
|
properties: {
|
|
244
235
|
mode: { type: "string" },
|
|
245
|
-
idea: { type: "string" }
|
|
246
|
-
|
|
236
|
+
idea: { type: "string" },
|
|
237
|
+
personas: { type: "number" }
|
|
238
|
+
},
|
|
239
|
+
required: ["idea"]
|
|
247
240
|
}
|
|
248
241
|
},
|
|
249
242
|
{
|
|
@@ -211,17 +211,21 @@ function generateGitHubAction() {
|
|
|
211
211
|
return [
|
|
212
212
|
{
|
|
213
213
|
path: ".github/workflows/idea-gauntlet.yml",
|
|
214
|
-
description: "GitHub Action for PR idea critique",
|
|
215
|
-
content: `name: IdeaGauntlet
|
|
214
|
+
description: "GitHub Action for PR idea critique prompt generation",
|
|
215
|
+
content: `name: IdeaGauntlet Prompt
|
|
216
216
|
on:
|
|
217
217
|
issues:
|
|
218
218
|
types: [opened]
|
|
219
219
|
jobs:
|
|
220
|
-
|
|
220
|
+
prompt:
|
|
221
221
|
runs-on: ubuntu-latest
|
|
222
222
|
steps:
|
|
223
223
|
- uses: actions/checkout@v4
|
|
224
|
-
- run: npx idea-gauntlet quick "\${{ github.event.issue.title }}" --
|
|
224
|
+
- run: npx idea-gauntlet prompt quick "\${{ github.event.issue.title }}" --output idea-gauntlet-prompt.md
|
|
225
|
+
- uses: actions/upload-artifact@v4
|
|
226
|
+
with:
|
|
227
|
+
name: idea-gauntlet-prompt
|
|
228
|
+
path: idea-gauntlet-prompt.md
|
|
225
229
|
`
|
|
226
230
|
}
|
|
227
231
|
];
|
package/dist/cli/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
setupCommand
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-SPGNYKGN.js";
|
|
5
5
|
import {
|
|
6
6
|
safeWriteOutput,
|
|
7
7
|
startMcpServer
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-JTEPYPKW.js";
|
|
9
9
|
import {
|
|
10
10
|
buildReport,
|
|
11
11
|
getApiKey,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
runImmuneEngine,
|
|
17
17
|
runMvpPlanner,
|
|
18
18
|
runUserLab
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-CMSHOKHT.js";
|
|
20
20
|
import {
|
|
21
21
|
skeptic
|
|
22
22
|
} from "../chunk-VQHEJYTS.js";
|
|
@@ -75,10 +75,9 @@ async function showOnboardingMenu() {
|
|
|
75
75
|
console.log("Choose how to proceed:\n");
|
|
76
76
|
console.log(" 1) Use Ollama/local model (if installed)");
|
|
77
77
|
console.log(" 2) Generate structured prompt for Claude/Cursor/Codex (no API key needed)");
|
|
78
|
-
console.log(" 3)
|
|
79
|
-
console.log(" 4)
|
|
80
|
-
|
|
81
|
-
const answer = await rl.question("Enter choice (1-5): ");
|
|
78
|
+
console.log(" 3) Configure API key now");
|
|
79
|
+
console.log(" 4) Set up agent-native integration files (setup)\n");
|
|
80
|
+
const answer = await rl.question("Enter choice (1-4): ");
|
|
82
81
|
rl.close();
|
|
83
82
|
switch (answer.trim()) {
|
|
84
83
|
case "1":
|
|
@@ -86,14 +85,11 @@ async function showOnboardingMenu() {
|
|
|
86
85
|
case "2":
|
|
87
86
|
return "generate_prompt";
|
|
88
87
|
case "3":
|
|
89
|
-
return "mock";
|
|
90
|
-
case "4":
|
|
91
88
|
return "configure_key";
|
|
92
|
-
case "
|
|
89
|
+
case "4":
|
|
93
90
|
return "setup_agent_native";
|
|
94
91
|
default:
|
|
95
|
-
console.log("Invalid choice. Please enter 1-
|
|
96
|
-
rl.close();
|
|
92
|
+
console.log("Invalid choice. Please enter 1-4.");
|
|
97
93
|
return showOnboardingMenu();
|
|
98
94
|
}
|
|
99
95
|
}
|
|
@@ -102,7 +98,6 @@ async function showOnboardingMenu() {
|
|
|
102
98
|
async function quickCommand(ideaArg, rawOptions) {
|
|
103
99
|
const options = normalizeOptions(rawOptions);
|
|
104
100
|
const ideaText = loadIdeaInput(ideaArg);
|
|
105
|
-
const mock = !!options.mock;
|
|
106
101
|
const idea = parseIdeaInput({
|
|
107
102
|
idea: ideaText,
|
|
108
103
|
mode: "quick",
|
|
@@ -110,13 +105,15 @@ async function quickCommand(ideaArg, rawOptions) {
|
|
|
110
105
|
market: options.market,
|
|
111
106
|
targetUsers: options.targetUsers
|
|
112
107
|
});
|
|
113
|
-
let providerRes = resolveProvider({
|
|
108
|
+
let providerRes = resolveProvider({
|
|
109
|
+
apiKey: options.apiKey,
|
|
110
|
+
baseUrl: options.baseUrl,
|
|
111
|
+
model: options.model,
|
|
112
|
+
ollama: !!options.ollama
|
|
113
|
+
});
|
|
114
114
|
if (!providerRes) {
|
|
115
115
|
const choice = await showOnboardingMenu();
|
|
116
116
|
switch (choice) {
|
|
117
|
-
case "mock":
|
|
118
|
-
providerRes = resolveProvider({ mock: true });
|
|
119
|
-
break;
|
|
120
117
|
case "generate_prompt":
|
|
121
118
|
await promptQuickCommand(ideaText, options);
|
|
122
119
|
return;
|
|
@@ -128,19 +125,18 @@ async function quickCommand(ideaArg, rawOptions) {
|
|
|
128
125
|
break;
|
|
129
126
|
}
|
|
130
127
|
case "setup_agent_native": {
|
|
131
|
-
const { setupCommand: setupCommand2 } = await import("../setup-
|
|
128
|
+
const { setupCommand: setupCommand2 } = await import("../setup-HR7PTLSY.js");
|
|
132
129
|
await setupCommand2({});
|
|
133
130
|
return;
|
|
134
131
|
}
|
|
135
132
|
case "ollama": {
|
|
136
|
-
|
|
137
|
-
providerRes = resolveProvider({ mock: true });
|
|
133
|
+
providerRes = resolveProvider({ ollama: true, model: options.model });
|
|
138
134
|
break;
|
|
139
135
|
}
|
|
140
136
|
}
|
|
141
137
|
}
|
|
142
138
|
if (!providerRes) {
|
|
143
|
-
console.error("No provider available.
|
|
139
|
+
console.error("No provider available. Set IDEAGAUNTLET_API_KEY, pass --api-key, use --ollama, or use `idea-gauntlet prompt quick`.");
|
|
144
140
|
process.exit(2);
|
|
145
141
|
}
|
|
146
142
|
try {
|
|
@@ -149,8 +145,7 @@ async function quickCommand(ideaArg, rawOptions) {
|
|
|
149
145
|
const isJson = !!options.json;
|
|
150
146
|
const output = options.output;
|
|
151
147
|
if (isJson) {
|
|
152
|
-
const
|
|
153
|
-
const r = safeWriteOutput(output, json, "Report");
|
|
148
|
+
const r = safeWriteOutput(output, JSON.stringify(report, null, 2), "Report");
|
|
154
149
|
if (!r.ok) {
|
|
155
150
|
console.error(r.message);
|
|
156
151
|
process.exit(2);
|
|
@@ -222,11 +217,13 @@ async function courtCommand(ideaArg, rawOptions) {
|
|
|
222
217
|
stage: options.stage
|
|
223
218
|
});
|
|
224
219
|
const providerRes = resolveProvider({
|
|
225
|
-
|
|
226
|
-
|
|
220
|
+
apiKey: options.apiKey,
|
|
221
|
+
baseUrl: options.baseUrl,
|
|
222
|
+
model: options.model,
|
|
223
|
+
ollama: !!options.ollama
|
|
227
224
|
});
|
|
228
225
|
if (!providerRes) {
|
|
229
|
-
console.error("No provider available.
|
|
226
|
+
console.error("No provider available. Set IDEAGAUNTLET_API_KEY, pass --api-key, use --ollama, or use `idea-gauntlet prompt court`.");
|
|
230
227
|
process.exit(2);
|
|
231
228
|
}
|
|
232
229
|
try {
|
|
@@ -235,8 +232,7 @@ async function courtCommand(ideaArg, rawOptions) {
|
|
|
235
232
|
const isJson = !!options.json;
|
|
236
233
|
const output = options.output;
|
|
237
234
|
if (isJson) {
|
|
238
|
-
const
|
|
239
|
-
const r = safeWriteOutput(output, json, "Report");
|
|
235
|
+
const r = safeWriteOutput(output, JSON.stringify(report, null, 2), "Report");
|
|
240
236
|
if (!r.ok) {
|
|
241
237
|
console.error(r.message);
|
|
242
238
|
process.exit(2);
|
|
@@ -265,11 +261,13 @@ async function usersCommand(ideaArg, rawOptions) {
|
|
|
265
261
|
});
|
|
266
262
|
const count = parseInt(String(options.personas ?? "6"), 10);
|
|
267
263
|
const providerRes = resolveProvider({
|
|
268
|
-
|
|
269
|
-
|
|
264
|
+
apiKey: options.apiKey,
|
|
265
|
+
baseUrl: options.baseUrl,
|
|
266
|
+
model: options.model,
|
|
267
|
+
ollama: !!options.ollama
|
|
270
268
|
});
|
|
271
269
|
if (!providerRes) {
|
|
272
|
-
console.error("No provider available.
|
|
270
|
+
console.error("No provider available. Set IDEAGAUNTLET_API_KEY, pass --api-key, use --ollama, or use `idea-gauntlet prompt users`.");
|
|
273
271
|
process.exit(2);
|
|
274
272
|
}
|
|
275
273
|
try {
|
|
@@ -278,8 +276,7 @@ async function usersCommand(ideaArg, rawOptions) {
|
|
|
278
276
|
const isJson = !!options.json;
|
|
279
277
|
const output = options.output;
|
|
280
278
|
if (isJson) {
|
|
281
|
-
const
|
|
282
|
-
const r = safeWriteOutput(output, json, "Report");
|
|
279
|
+
const r = safeWriteOutput(output, JSON.stringify(report, null, 2), "Report");
|
|
283
280
|
if (!r.ok) {
|
|
284
281
|
console.error(r.message);
|
|
285
282
|
process.exit(2);
|
|
@@ -300,9 +297,14 @@ async function usersCommand(ideaArg, rawOptions) {
|
|
|
300
297
|
// src/cli/commands/mvp.ts
|
|
301
298
|
async function mvpCommand(ideaArg, rawOptions) {
|
|
302
299
|
const options = normalizeOptions(rawOptions);
|
|
303
|
-
const providerRes = resolveProvider({
|
|
300
|
+
const providerRes = resolveProvider({
|
|
301
|
+
apiKey: options.apiKey,
|
|
302
|
+
baseUrl: options.baseUrl,
|
|
303
|
+
model: options.model,
|
|
304
|
+
ollama: !!options.ollama
|
|
305
|
+
});
|
|
304
306
|
if (!providerRes) {
|
|
305
|
-
console.error("No provider.
|
|
307
|
+
console.error("No provider available. Set IDEAGAUNTLET_API_KEY, pass --api-key, use --ollama, or use `idea-gauntlet prompt mvp`.");
|
|
306
308
|
process.exit(2);
|
|
307
309
|
}
|
|
308
310
|
const idea = parseIdeaInput({ idea: loadIdeaInput(ideaArg), mode: "mvp", stage: options.stage });
|
|
@@ -328,9 +330,14 @@ async function mvpCommand(ideaArg, rawOptions) {
|
|
|
328
330
|
// src/cli/commands/compare.ts
|
|
329
331
|
async function compareCommand(ideas, rawOptions) {
|
|
330
332
|
const options = normalizeOptions(rawOptions);
|
|
331
|
-
const providerRes = resolveProvider({
|
|
333
|
+
const providerRes = resolveProvider({
|
|
334
|
+
apiKey: options.apiKey,
|
|
335
|
+
baseUrl: options.baseUrl,
|
|
336
|
+
model: options.model,
|
|
337
|
+
ollama: !!options.ollama
|
|
338
|
+
});
|
|
332
339
|
if (!providerRes) {
|
|
333
|
-
console.error("No provider.
|
|
340
|
+
console.error("No provider available. Set IDEAGAUNTLET_API_KEY, pass --api-key, use --ollama, or use prompt mode for individual ideas.");
|
|
334
341
|
process.exit(2);
|
|
335
342
|
}
|
|
336
343
|
const parsed = ideas.map((i) => parseIdeaInput({ idea: loadIdeaInput(i), mode: "compare" }));
|
|
@@ -547,7 +554,7 @@ async function doctorCommand(options) {
|
|
|
547
554
|
results.push({
|
|
548
555
|
label: "API key (IDEAGAUNTLET_API_KEY)",
|
|
549
556
|
status: hasKey ? "pass" : "warn",
|
|
550
|
-
message: hasKey ? "Set" : "Not set (prompt/
|
|
557
|
+
message: hasKey ? "Set" : "Not set (prompt/setup/init/doctor still work)"
|
|
551
558
|
});
|
|
552
559
|
const executableOk = checkExecutable();
|
|
553
560
|
results.push({
|
|
@@ -574,17 +581,15 @@ async function doctorCommand(options) {
|
|
|
574
581
|
results.push({
|
|
575
582
|
label: "MCP server startup",
|
|
576
583
|
status: mcpOk ? "pass" : "warn",
|
|
577
|
-
message: mcpOk ? "
|
|
578
|
-
detail: verbose ? mcpOk ? "MCP server
|
|
584
|
+
message: mcpOk ? "Module loaded" : "Could not load MCP server module",
|
|
585
|
+
detail: verbose ? mcpOk ? "MCP server module exports startMcpServer" : "MCP server module failed to import" : void 0
|
|
579
586
|
});
|
|
580
587
|
console.log("\nIdeaGauntlet Doctor");
|
|
581
588
|
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
582
589
|
for (const r of results) {
|
|
583
590
|
const icon = r.status === "pass" ? "\u2713" : r.status === "warn" ? "\u26A0" : "\u2717";
|
|
584
591
|
console.log(`${icon} ${r.label}: ${r.message}`);
|
|
585
|
-
if (r.detail && verbose) {
|
|
586
|
-
console.log(` ${r.detail}`);
|
|
587
|
-
}
|
|
592
|
+
if (r.detail && verbose) console.log(` ${r.detail}`);
|
|
588
593
|
}
|
|
589
594
|
const failures = results.filter((r) => r.status === "fail").length;
|
|
590
595
|
if (failures > 0) console.log(`
|
|
@@ -611,7 +616,7 @@ async function checkOllama() {
|
|
|
611
616
|
}
|
|
612
617
|
async function checkMcpServer() {
|
|
613
618
|
try {
|
|
614
|
-
const { startMcpServer: startMcpServer2 } = await import("../server-
|
|
619
|
+
const { startMcpServer: startMcpServer2 } = await import("../server-VKTKEXIE.js");
|
|
615
620
|
return typeof startMcpServer2 === "function";
|
|
616
621
|
} catch {
|
|
617
622
|
return false;
|
|
@@ -631,11 +636,22 @@ var pkg = JSON.parse(
|
|
|
631
636
|
var cli = cac("idea-gauntlet");
|
|
632
637
|
cli.version(pkg.version);
|
|
633
638
|
cli.help();
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
cli.command("
|
|
637
|
-
|
|
638
|
-
|
|
639
|
+
var providerOptions = (cmd) => cmd.option("--model <model>", "LLM model override").option("--base-url <url>", "OpenAI-compatible API base URL override").option("--api-key <key>", "API key override").option("--ollama", "Use local Ollama provider");
|
|
640
|
+
providerOptions(
|
|
641
|
+
cli.command("quick <idea>", "Run a fast adversarial critique").option("--stage <stage>", "Idea stage: napkin, pre-mvp, mvp, growth").option("--market <market>", "Target market description").option("--target-users <users>", "Comma-separated target users").option("--json", "Output JSON instead of Markdown").option("--output <file>", "Write to file")
|
|
642
|
+
).action((idea, options) => quickCommand(idea, options));
|
|
643
|
+
providerOptions(
|
|
644
|
+
cli.command("court <idea>", "Run a court-style debate").option("--stage <stage>", "Idea stage").option("--json", "Output JSON").option("--output <file>", "Write to file")
|
|
645
|
+
).action((idea, options) => courtCommand(idea, options));
|
|
646
|
+
providerOptions(
|
|
647
|
+
cli.command("users <idea>", "Generate synthetic user personas").option("--personas <number>", "Number of personas", { default: "6" }).option("--stage <stage>", "Idea stage").option("--json", "Output JSON").option("--output <file>", "Write to file")
|
|
648
|
+
).action((idea, options) => usersCommand(idea, options));
|
|
649
|
+
providerOptions(
|
|
650
|
+
cli.command("mvp <idea>", "Generate a validation/MVP plan").option("--stage <stage>", "Idea stage").option("--json", "Output JSON").option("--output <file>", "Write to file")
|
|
651
|
+
).action((idea, options) => mvpCommand(idea, options));
|
|
652
|
+
providerOptions(
|
|
653
|
+
cli.command("compare <...ideas>", "Compare multiple product ideas").option("--output <file>", "Write to file")
|
|
654
|
+
).action((ideas, options) => compareCommand(ideas, options));
|
|
639
655
|
cli.command("prompt <mode> <idea>", "Generate structured prompt for any AI (no API key)").option("--stage <stage>", "Idea stage").option("--market <market>", "Target market").option("--target-users <users>", "Target users").option("--personas <number>", "Number of personas (for users mode)").option("--output <file>", "Write to file").action((mode, idea, options) => promptCommand(mode, idea, options));
|
|
640
656
|
cli.command("init [directory]", "Scaffold an IdeaGauntlet workspace template").option("--name <name>", "Project name").option("--force", "Overwrite existing files").action((directory, options) => initCommand(directory, options));
|
|
641
657
|
cli.command("setup", "Generate integration files for Claude/Cursor/Codex/MCP").option("--all", "Install all integrations non-interactively").option("--dry-run", "Show what would be written without writing").option("--force", "Overwrite existing files without confirmation").option("--targets <targets>", "Comma-separated targets to install").action((options) => setupCommand(options));
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ type Verdict = "strong" | "promising_but_risky" | "unclear" | "weak" | "needs_re
|
|
|
6
6
|
type Severity = "low" | "medium" | "high" | "critical";
|
|
7
7
|
type Confidence = "low" | "medium" | "high";
|
|
8
8
|
type WillingnessToPay = "none" | "low" | "medium" | "high";
|
|
9
|
-
type OnboardingChoice = "ollama" | "generate_prompt" | "
|
|
9
|
+
type OnboardingChoice = "ollama" | "generate_prompt" | "configure_key" | "setup_agent_native";
|
|
10
10
|
type IntegrationTarget = "claude-skills" | "claude-agents" | "claude-commands" | "codex" | "cursor" | "mcp" | "github-action";
|
|
11
11
|
declare const IdeaStageSchema: z.ZodEnum<["napkin", "pre-mvp", "mvp", "growth"]>;
|
|
12
12
|
declare const GauntletModeSchema: z.ZodEnum<["quick", "court", "users", "mvp", "compare"]>;
|
|
@@ -136,7 +136,7 @@ interface CompletionOptions {
|
|
|
136
136
|
maxTokens?: number;
|
|
137
137
|
}
|
|
138
138
|
interface LLMProvider {
|
|
139
|
-
kind: "
|
|
139
|
+
kind: "openai" | "ollama" | "custom";
|
|
140
140
|
complete(prompt: string, options?: CompletionOptions): Promise<string>;
|
|
141
141
|
}
|
|
142
142
|
|
|
@@ -161,11 +161,6 @@ declare function calculateScores(params: {
|
|
|
161
161
|
}): Scorecard;
|
|
162
162
|
declare function medianScore(scores: Scorecard): number;
|
|
163
163
|
|
|
164
|
-
declare class MockProvider implements LLMProvider {
|
|
165
|
-
kind: "mock";
|
|
166
|
-
complete(prompt: string, options?: CompletionOptions): Promise<string>;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
164
|
type OpenAICompatibleConfig = {
|
|
170
165
|
apiKey: string;
|
|
171
166
|
baseUrl?: string;
|
|
@@ -188,23 +183,22 @@ declare class OllamaProvider implements LLMProvider {
|
|
|
188
183
|
|
|
189
184
|
type ProviderResolution = {
|
|
190
185
|
provider: LLMProvider;
|
|
191
|
-
source: "
|
|
186
|
+
source: "flags" | "env" | "ollama";
|
|
192
187
|
};
|
|
193
188
|
declare function resolveProvider(options?: {
|
|
194
|
-
mock?: boolean;
|
|
195
189
|
apiKey?: string;
|
|
196
190
|
baseUrl?: string;
|
|
197
191
|
model?: string;
|
|
198
192
|
ollama?: boolean;
|
|
199
193
|
}): ProviderResolution | null;
|
|
200
194
|
declare function getProvider(options?: {
|
|
201
|
-
mock?: boolean;
|
|
202
195
|
apiKey?: string;
|
|
203
196
|
baseUrl?: string;
|
|
204
197
|
model?: string;
|
|
198
|
+
ollama?: boolean;
|
|
205
199
|
}): LLMProvider;
|
|
206
200
|
declare class NoProviderError extends Error {
|
|
207
201
|
constructor();
|
|
208
202
|
}
|
|
209
203
|
|
|
210
|
-
export { type Assumption, type ComparedIdea, type ComparisonResult, type CompletionOptions, type Confidence, ConfidenceSchema, type CourtSession, type CourtTurn, type GauntletMode, GauntletModeSchema, type GauntletReport, type IdeaInput, IdeaInputSchema, type IdeaStage, IdeaStageSchema, type IntegrationTarget, type KillTest, type LLMProvider, type MVPPlan,
|
|
204
|
+
export { type Assumption, type ComparedIdea, type ComparisonResult, type CompletionOptions, type Confidence, ConfidenceSchema, type CourtSession, type CourtTurn, type GauntletMode, GauntletModeSchema, type GauntletReport, type IdeaInput, IdeaInputSchema, type IdeaStage, IdeaStageSchema, type IntegrationTarget, type KillTest, type LLMProvider, type MVPPlan, NoProviderError, OllamaProvider, type OnboardingChoice, OpenAICompatibleProvider, type Risk, type Scorecard, type Severity, SeveritySchema, type SyntheticPersona, type Verdict, VerdictSchema, type WillingnessToPay, WillingnessToPaySchema, buildReport, calculateScores, getProvider, medianScore, resolveProvider, runGauntlet, runImmuneEngine };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
MockProvider,
|
|
3
2
|
NoProviderError,
|
|
3
|
+
OllamaProvider,
|
|
4
4
|
OpenAICompatibleProvider,
|
|
5
5
|
buildReport,
|
|
6
6
|
calculateScores,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
runImmuneEngine,
|
|
13
13
|
runMvpPlanner,
|
|
14
14
|
runUserLab
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-CMSHOKHT.js";
|
|
16
16
|
import "./chunk-VQHEJYTS.js";
|
|
17
17
|
|
|
18
18
|
// src/core/types.ts
|
|
@@ -49,7 +49,7 @@ var IdeaInputSchema = z.object({
|
|
|
49
49
|
// src/core/runGauntlet.ts
|
|
50
50
|
async function runGauntlet(params) {
|
|
51
51
|
if (!params.provider) {
|
|
52
|
-
throw new Error("LLM provider is required. Pass
|
|
52
|
+
throw new Error("LLM provider is required. Pass an OpenAICompatibleProvider, OllamaProvider, or a custom LLMProvider.");
|
|
53
53
|
}
|
|
54
54
|
const input = {
|
|
55
55
|
idea: params.idea,
|
|
@@ -84,45 +84,11 @@ async function runGauntlet(params) {
|
|
|
84
84
|
report.markdown = buildReport(report);
|
|
85
85
|
return report;
|
|
86
86
|
}
|
|
87
|
-
|
|
88
|
-
// src/providers/ollamaProvider.ts
|
|
89
|
-
var OllamaProvider = class {
|
|
90
|
-
kind = "ollama";
|
|
91
|
-
baseUrl;
|
|
92
|
-
model;
|
|
93
|
-
constructor(model, baseUrl) {
|
|
94
|
-
this.baseUrl = baseUrl ?? "http://localhost:11434";
|
|
95
|
-
this.model = model ?? "llama3";
|
|
96
|
-
}
|
|
97
|
-
async complete(prompt, options) {
|
|
98
|
-
const url = `${this.baseUrl}/api/chat`;
|
|
99
|
-
const messages = [];
|
|
100
|
-
if (options?.system) messages.push({ role: "system", content: options.system });
|
|
101
|
-
messages.push({ role: "user", content: prompt });
|
|
102
|
-
const response = await fetch(url, {
|
|
103
|
-
method: "POST",
|
|
104
|
-
headers: { "Content-Type": "application/json" },
|
|
105
|
-
body: JSON.stringify({
|
|
106
|
-
model: this.model,
|
|
107
|
-
messages,
|
|
108
|
-
stream: false,
|
|
109
|
-
options: { temperature: options?.temperature ?? 0.4, num_predict: options?.maxTokens ?? 2048 }
|
|
110
|
-
})
|
|
111
|
-
});
|
|
112
|
-
if (!response.ok) {
|
|
113
|
-
const text = await response.text();
|
|
114
|
-
throw new Error(`Ollama returned ${response.status}: ${text}`);
|
|
115
|
-
}
|
|
116
|
-
const data = await response.json();
|
|
117
|
-
return data.message?.content ?? "";
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
87
|
export {
|
|
121
88
|
ConfidenceSchema,
|
|
122
89
|
GauntletModeSchema,
|
|
123
90
|
IdeaInputSchema,
|
|
124
91
|
IdeaStageSchema,
|
|
125
|
-
MockProvider,
|
|
126
92
|
NoProviderError,
|
|
127
93
|
OllamaProvider,
|
|
128
94
|
OpenAICompatibleProvider,
|