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 CHANGED
@@ -6,93 +6,161 @@ Stress-test product ideas with adversarial agents, synthetic users, and court-st
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/idea-gauntlet)](https://www.npmjs.com/package/idea-gauntlet)
8
8
 
9
- ## Quick start
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
- npx idea-gauntlet quick "A synthetic focus room app for remote workers" --mock
18
+ npm install -g idea-gauntlet
13
19
  ```
14
20
 
15
- ## Installation
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
- npm install idea-gauntlet
50
+ ollama serve
51
+ idea-gauntlet quick "Your idea" --ollama --model llama3
19
52
  ```
20
53
 
21
- Or run directly:
54
+ No provider is required for prompt and integration workflows:
22
55
 
23
56
  ```bash
24
- npx idea-gauntlet quick "Your product idea here"
57
+ idea-gauntlet prompt quick "Your idea"
58
+ idea-gauntlet setup --dry-run --all
25
59
  ```
26
60
 
27
- ## Usage Paths
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
- | Path | Provider? | Description |
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
- ## CLI Commands
84
+ ### `quick`
37
85
 
38
- ### quick
39
86
  Fast adversarial critique.
87
+
40
88
  ```bash
41
- idea-gauntlet quick "Your idea" [--mock] [--json] [--output report.md]
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 [--mock]
97
+ idea-gauntlet court idea.md
48
98
  ```
49
99
 
50
- ### users
51
- Generate synthetic user personas.
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 [--mock]
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 [--mock]
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 [--mock]
121
+ idea-gauntlet compare idea1.md idea2.md
66
122
  ```
67
123
 
68
- ### prompt
69
- Generate structured prompts (no API key needed).
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
- Scaffold a workspace.
135
+ ### `init`
136
+
137
+ Scaffold a local IdeaGauntlet workspace.
138
+
78
139
  ```bash
79
- idea-gauntlet init [--directory path]
140
+ idea-gauntlet init
80
141
  ```
81
142
 
82
- ### setup
83
- Generate integration files for Claude Code, Cursor, Codex.
143
+ ### `setup`
144
+
145
+ Generate integration files for Claude Code, Cursor, Codex, MCP, and GitHub Actions.
146
+
84
147
  ```bash
85
- idea-gauntlet setup [--all] [--dry-run]
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
- Start MCP server for Claude Desktop integration.
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, MockProvider } from "idea-gauntlet";
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: new MockProvider(),
182
+ provider,
110
183
  });
111
184
 
112
185
  console.log(report.markdown);
113
186
  ```
114
187
 
115
- ## Scoring Philosophy
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
- - **Prompt mode** generates structured prompts for any AI
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?.mock) return { provider: new MockProvider(), source: "mock" };
395
- if (options?.apiKey) return { provider: new OpenAICompatibleProvider({ apiKey: options.apiKey, baseUrl: options.baseUrl, model: options.model }), source: "flags" };
396
- if (hasApiKey()) return { provider: new OpenAICompatibleProvider({ apiKey: getApiKey(), baseUrl: options?.baseUrl ?? getBaseUrl(), model: options?.model ?? getModel() }), source: "env" };
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
- if (provider.kind === "mock") {
444
- transcript.push(
445
- {
446
- role: "Prosecutor",
447
- argument: "Mock: The idea assumes behavior change without sufficient incentive."
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
- if (provider.kind === "mock") {
480
- verdictText = "Mock verdict: Promising but risky. Core assumption needs real-world testing.";
481
- unresolvedQuestions = [
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
- system: "You are the Judge in IdeaGauntlet. Synthesize arguments into a conservative verdict.",
495
- temperature: 0.3,
496
- maxTokens: 512
497
- }
498
- );
499
- const parsed = JSON.parse(judgeResponse);
500
- verdictText = parsed.verdict ?? verdictText;
501
- unresolvedQuestions = parsed.unresolvedQuestions ?? [];
502
- } catch {
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
- if (provider.kind === "mock") {
533
- users = [
534
- {
535
- name: "Alex Chen",
536
- archetype: "Busy Student",
537
- goal: "Focus on exam prep",
538
- currentWorkaround: "YouTube study-with-me videos",
539
- triggerToTry: "Exam week stress",
540
- primaryObjection: "Might feel childish",
541
- willingnessToPay: "low",
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-P4FDULQC.js";
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 = new MockProvider();
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
- let output = "";
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 }, provider);
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 }, provider);
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
- provider,
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 }, provider);
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, provider);
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 Critique
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
- critique:
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 }}" --mock
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-HW6JACOL.js";
4
+ } from "../chunk-SPGNYKGN.js";
5
5
  import {
6
6
  safeWriteOutput,
7
7
  startMcpServer
8
- } from "../chunk-A6GCV4RD.js";
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-P4FDULQC.js";
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) Run mock/demo mode (deterministic output)");
79
- console.log(" 4) Configure API key now");
80
- console.log(" 5) Set up agent-native integration files (setup)\n");
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 "5":
89
+ case "4":
93
90
  return "setup_agent_native";
94
91
  default:
95
- console.log("Invalid choice. Please enter 1-5.");
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({ mock, apiKey: options.apiKey, baseUrl: options.baseUrl, model: options.model });
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-QMYBP3QE.js");
128
+ const { setupCommand: setupCommand2 } = await import("../setup-HR7PTLSY.js");
132
129
  await setupCommand2({});
133
130
  return;
134
131
  }
135
132
  case "ollama": {
136
- console.log("Ollama support coming soon. Use --mock for now.");
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. Run with --mock for demo mode.");
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 json = JSON.stringify(report, null, 2);
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
- mock: !!options.mock,
226
- model: options.model
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. Use --mock.");
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 json = JSON.stringify(report, null, 2);
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
- mock: !!options.mock,
269
- model: options.model
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. Use --mock.");
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 json = JSON.stringify(report, null, 2);
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({ mock: !!options.mock, model: options.model });
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. Use --mock.");
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({ mock: !!options.mock, model: options.model });
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. Use --mock.");
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/mock/setup still work)"
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 ? "Tools list fetched" : "Could not start (run with --verbose)",
578
- detail: verbose ? mcpOk ? "MCP server started, received tools/list response" : "MCP server failed to respond to tools/list request" : void 0
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-FLE4IK6S.js");
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
- 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("--model <model>", "LLM model override").option("--base-url <url>", "API base URL override").option("--api-key <key>", "API key override").option("--mock", "Use mock provider").option("--json", "Output JSON instead of Markdown").option("--output <file>", "Write to file").action((idea, options) => quickCommand(idea, options));
635
- cli.command("court <idea>", "Run a court-style debate").option("--stage <stage>", "Idea stage").option("--model <model>", "LLM model override").option("--mock", "Use mock provider").option("--json", "Output JSON").option("--output <file>", "Write to file").action((idea, options) => courtCommand(idea, options));
636
- cli.command("users <idea>", "Generate synthetic user personas").option("--personas <number>", "Number of personas", { default: "6" }).option("--stage <stage>", "Idea stage").option("--model <model>", "LLM model override").option("--mock", "Use mock provider").option("--json", "Output JSON").option("--output <file>", "Write to file").action((idea, options) => usersCommand(idea, options));
637
- cli.command("mvp <idea>", "Generate a validation/MVP plan").option("--stage <stage>", "Idea stage").option("--model <model>", "LLM model override").option("--mock", "Use mock provider").option("--json", "Output JSON").option("--output <file>", "Write to file").action((idea, options) => mvpCommand(idea, options));
638
- cli.command("compare <...ideas>", "Compare multiple product ideas").option("--model <model>", "LLM model override").option("--mock", "Use mock provider").option("--output <file>", "Write to file").action((ideas, options) => compareCommand(ideas, options));
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" | "mock" | "configure_key" | "setup_agent_native";
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: "mock" | "openai" | "ollama" | "custom";
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: "mock" | "flags" | "env" | "ollama" | null;
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, MockProvider, 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 };
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-P4FDULQC.js";
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 --mock or set IDEAGAUNTLET_API_KEY.");
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,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  startMcpServer
3
- } from "./chunk-A6GCV4RD.js";
4
- import "./chunk-P4FDULQC.js";
3
+ } from "./chunk-JTEPYPKW.js";
4
+ import "./chunk-CMSHOKHT.js";
5
5
  import "./chunk-VQHEJYTS.js";
6
6
  import "./chunk-FF7CULAJ.js";
7
7
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  setupCommand
3
- } from "./chunk-HW6JACOL.js";
3
+ } from "./chunk-SPGNYKGN.js";
4
4
  export {
5
5
  setupCommand
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "idea-gauntlet",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Stress-test product ideas with adversarial agents, synthetic users, and court-style critique.",
5
5
  "type": "module",
6
6
  "bin": {