idea-gauntlet 0.1.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 +154 -0
- package/dist/chunk-A6GCV4RD.js +379 -0
- package/dist/chunk-FF7CULAJ.js +11 -0
- package/dist/chunk-HW6JACOL.js +324 -0
- package/dist/chunk-P4FDULQC.js +703 -0
- package/dist/chunk-VQHEJYTS.js +32 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +644 -0
- package/dist/defender-IQOS3YI7.js +6 -0
- package/dist/index.d.ts +210 -0
- package/dist/index.js +139 -0
- package/dist/server-FLE4IK6S.js +9 -0
- package/dist/setup-QMYBP3QE.js +6 -0
- package/dist/skeptic-KU7EW27C.js +6 -0
- package/package.json +53 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// src/prompts/immunePrompt.ts
|
|
2
|
+
function buildImmuneSystemPrompt(role) {
|
|
3
|
+
return `You are the ${role} in IdeaGauntlet.
|
|
4
|
+
Your job is to find the strongest reasons this product idea may fail.
|
|
5
|
+
Do not be polite for its own sake.
|
|
6
|
+
Do not give generic startup advice.
|
|
7
|
+
Focus on hidden assumptions, user apathy, behavior-change cost, substitutes, distribution risk, retention risk, and monetization weakness.
|
|
8
|
+
Return specific, testable objections.`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/agents/skeptic.ts
|
|
12
|
+
var skeptic = (idea) => ({
|
|
13
|
+
system: buildImmuneSystemPrompt("Skeptic"),
|
|
14
|
+
userMessage: `Product idea: ${idea.idea}${idea.targetUsers ? `
|
|
15
|
+
Target users: ${idea.targetUsers.join(", ")}` : ""}${idea.market ? `
|
|
16
|
+
Market: ${idea.market}` : ""}
|
|
17
|
+
|
|
18
|
+
Return your critique as a JSON object with these fields:
|
|
19
|
+
- coreInsight (string)
|
|
20
|
+
- strongestCase (string)
|
|
21
|
+
- weakestAssumption (string)
|
|
22
|
+
- risks (array of {title, severity: "low"|"medium"|"high"|"critical", explanation, mitigation?})
|
|
23
|
+
- assumptions (array of {title, whyItMatters, howToTest, confidence: "low"|"medium"|"high"})
|
|
24
|
+
- killTests (array of {title, method, timeframe, successSignal, killSignal})
|
|
25
|
+
- nextActions (array of string)
|
|
26
|
+
|
|
27
|
+
Only return valid JSON. No markdown fences.`
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
skeptic
|
|
32
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
setupCommand
|
|
4
|
+
} from "../chunk-HW6JACOL.js";
|
|
5
|
+
import {
|
|
6
|
+
safeWriteOutput,
|
|
7
|
+
startMcpServer
|
|
8
|
+
} from "../chunk-A6GCV4RD.js";
|
|
9
|
+
import {
|
|
10
|
+
buildReport,
|
|
11
|
+
getApiKey,
|
|
12
|
+
isNodeGte,
|
|
13
|
+
resolveProvider,
|
|
14
|
+
runCompareEngine,
|
|
15
|
+
runCourtEngine,
|
|
16
|
+
runImmuneEngine,
|
|
17
|
+
runMvpPlanner,
|
|
18
|
+
runUserLab
|
|
19
|
+
} from "../chunk-P4FDULQC.js";
|
|
20
|
+
import {
|
|
21
|
+
skeptic
|
|
22
|
+
} from "../chunk-VQHEJYTS.js";
|
|
23
|
+
import {
|
|
24
|
+
defender
|
|
25
|
+
} from "../chunk-FF7CULAJ.js";
|
|
26
|
+
|
|
27
|
+
// src/cli/index.ts
|
|
28
|
+
import { cac } from "cac";
|
|
29
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
30
|
+
import { resolve as resolve4, dirname } from "path";
|
|
31
|
+
import { fileURLToPath } from "url";
|
|
32
|
+
|
|
33
|
+
// src/utils/loadIdeaFile.ts
|
|
34
|
+
import { readFileSync } from "fs";
|
|
35
|
+
import { isAbsolute, resolve } from "path";
|
|
36
|
+
function loadIdeaInput(ideaArg) {
|
|
37
|
+
if (ideaArg.endsWith(".md") || ideaArg.includes("/") || ideaArg.includes("\\")) {
|
|
38
|
+
try {
|
|
39
|
+
const filePath = isAbsolute(ideaArg) ? ideaArg : resolve(process.cwd(), ideaArg);
|
|
40
|
+
const content = readFileSync(filePath, "utf-8").trim();
|
|
41
|
+
if (!content) throw new Error(`File is empty: ${ideaArg}`);
|
|
42
|
+
return content;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
if (err.code === "ENOENT") return ideaArg;
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return ideaArg;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/utils/parseInput.ts
|
|
52
|
+
function parseIdeaInput(params) {
|
|
53
|
+
return {
|
|
54
|
+
idea: params.idea,
|
|
55
|
+
mode: params.mode,
|
|
56
|
+
stage: params.stage ?? void 0,
|
|
57
|
+
market: params.market,
|
|
58
|
+
targetUsers: params.targetUsers?.split(",").map((s) => s.trim()).filter(Boolean)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function normalizeOptions(opts) {
|
|
62
|
+
const result = {};
|
|
63
|
+
for (const [key, value] of Object.entries(opts)) {
|
|
64
|
+
const camelKey = key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
65
|
+
result[camelKey] = value;
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/cli/onboarding.ts
|
|
71
|
+
import { createInterface } from "readline/promises";
|
|
72
|
+
async function showOnboardingMenu() {
|
|
73
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
74
|
+
console.log("\n\u26A0 No API key or provider configured.");
|
|
75
|
+
console.log("Choose how to proceed:\n");
|
|
76
|
+
console.log(" 1) Use Ollama/local model (if installed)");
|
|
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): ");
|
|
82
|
+
rl.close();
|
|
83
|
+
switch (answer.trim()) {
|
|
84
|
+
case "1":
|
|
85
|
+
return "ollama";
|
|
86
|
+
case "2":
|
|
87
|
+
return "generate_prompt";
|
|
88
|
+
case "3":
|
|
89
|
+
return "mock";
|
|
90
|
+
case "4":
|
|
91
|
+
return "configure_key";
|
|
92
|
+
case "5":
|
|
93
|
+
return "setup_agent_native";
|
|
94
|
+
default:
|
|
95
|
+
console.log("Invalid choice. Please enter 1-5.");
|
|
96
|
+
rl.close();
|
|
97
|
+
return showOnboardingMenu();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/cli/commands/quick.ts
|
|
102
|
+
async function quickCommand(ideaArg, rawOptions) {
|
|
103
|
+
const options = normalizeOptions(rawOptions);
|
|
104
|
+
const ideaText = loadIdeaInput(ideaArg);
|
|
105
|
+
const mock = !!options.mock;
|
|
106
|
+
const idea = parseIdeaInput({
|
|
107
|
+
idea: ideaText,
|
|
108
|
+
mode: "quick",
|
|
109
|
+
stage: options.stage,
|
|
110
|
+
market: options.market,
|
|
111
|
+
targetUsers: options.targetUsers
|
|
112
|
+
});
|
|
113
|
+
let providerRes = resolveProvider({ mock, apiKey: options.apiKey, baseUrl: options.baseUrl, model: options.model });
|
|
114
|
+
if (!providerRes) {
|
|
115
|
+
const choice = await showOnboardingMenu();
|
|
116
|
+
switch (choice) {
|
|
117
|
+
case "mock":
|
|
118
|
+
providerRes = resolveProvider({ mock: true });
|
|
119
|
+
break;
|
|
120
|
+
case "generate_prompt":
|
|
121
|
+
await promptQuickCommand(ideaText, options);
|
|
122
|
+
return;
|
|
123
|
+
case "configure_key": {
|
|
124
|
+
const rl = (await import("readline/promises")).createInterface({ input: process.stdin, output: process.stdout });
|
|
125
|
+
const key = await rl.question("Enter API key: ");
|
|
126
|
+
rl.close();
|
|
127
|
+
if (key.trim()) providerRes = resolveProvider({ apiKey: key.trim() });
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "setup_agent_native": {
|
|
131
|
+
const { setupCommand: setupCommand2 } = await import("../setup-QMYBP3QE.js");
|
|
132
|
+
await setupCommand2({});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
case "ollama": {
|
|
136
|
+
console.log("Ollama support coming soon. Use --mock for now.");
|
|
137
|
+
providerRes = resolveProvider({ mock: true });
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (!providerRes) {
|
|
143
|
+
console.error("No provider available. Run with --mock for demo mode.");
|
|
144
|
+
process.exit(2);
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const report = await runImmuneEngine(idea, providerRes.provider);
|
|
148
|
+
report.markdown = buildReport(report);
|
|
149
|
+
const isJson = !!options.json;
|
|
150
|
+
const output = options.output;
|
|
151
|
+
if (isJson) {
|
|
152
|
+
const json = JSON.stringify(report, null, 2);
|
|
153
|
+
const r = safeWriteOutput(output, json, "Report");
|
|
154
|
+
if (!r.ok) {
|
|
155
|
+
console.error(r.message);
|
|
156
|
+
process.exit(2);
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
const r = safeWriteOutput(output, report.markdown, "Report");
|
|
160
|
+
if (!r.ok) {
|
|
161
|
+
console.error(r.message);
|
|
162
|
+
process.exit(2);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch (err) {
|
|
166
|
+
console.error(`Error: ${err.message}`);
|
|
167
|
+
process.exit(3);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function promptQuickCommand(ideaText, options) {
|
|
171
|
+
const { skeptic: skeptic2 } = await import("../skeptic-KU7EW27C.js");
|
|
172
|
+
const { defender: defender2 } = await import("../defender-IQOS3YI7.js");
|
|
173
|
+
const idea = parseIdeaInput({
|
|
174
|
+
idea: ideaText,
|
|
175
|
+
mode: "quick",
|
|
176
|
+
stage: options.stage,
|
|
177
|
+
market: options.market,
|
|
178
|
+
targetUsers: options.targetUsers
|
|
179
|
+
});
|
|
180
|
+
const skepticPrompt = skeptic2(idea);
|
|
181
|
+
const defenderPrompt = defender2(idea);
|
|
182
|
+
const output = [
|
|
183
|
+
"# IdeaGauntlet \u2014 Quick Critique Prompt",
|
|
184
|
+
"",
|
|
185
|
+
"Copy the prompts below into your preferred AI assistant.",
|
|
186
|
+
"",
|
|
187
|
+
"---",
|
|
188
|
+
"",
|
|
189
|
+
"## Skeptic Prompt",
|
|
190
|
+
"",
|
|
191
|
+
`**System:** ${skepticPrompt.system}`,
|
|
192
|
+
"",
|
|
193
|
+
skepticPrompt.userMessage,
|
|
194
|
+
"",
|
|
195
|
+
"---",
|
|
196
|
+
"",
|
|
197
|
+
"## Defender Prompt",
|
|
198
|
+
"",
|
|
199
|
+
`**System:** ${defenderPrompt.system}`,
|
|
200
|
+
"",
|
|
201
|
+
defenderPrompt.userMessage,
|
|
202
|
+
"",
|
|
203
|
+
"---",
|
|
204
|
+
"",
|
|
205
|
+
"Ask your AI to 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."
|
|
206
|
+
].join("\n");
|
|
207
|
+
const outFile = options.output;
|
|
208
|
+
const r = safeWriteOutput(outFile, output, "Prompt");
|
|
209
|
+
if (!r.ok) {
|
|
210
|
+
console.error(r.message);
|
|
211
|
+
process.exit(2);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/cli/commands/court.ts
|
|
216
|
+
async function courtCommand(ideaArg, rawOptions) {
|
|
217
|
+
const options = normalizeOptions(rawOptions);
|
|
218
|
+
const ideaText = loadIdeaInput(ideaArg);
|
|
219
|
+
const idea = parseIdeaInput({
|
|
220
|
+
idea: ideaText,
|
|
221
|
+
mode: "court",
|
|
222
|
+
stage: options.stage
|
|
223
|
+
});
|
|
224
|
+
const providerRes = resolveProvider({
|
|
225
|
+
mock: !!options.mock,
|
|
226
|
+
model: options.model
|
|
227
|
+
});
|
|
228
|
+
if (!providerRes) {
|
|
229
|
+
console.error("No provider available. Use --mock.");
|
|
230
|
+
process.exit(2);
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const report = await runCourtEngine(idea, providerRes.provider);
|
|
234
|
+
report.markdown = buildReport(report);
|
|
235
|
+
const isJson = !!options.json;
|
|
236
|
+
const output = options.output;
|
|
237
|
+
if (isJson) {
|
|
238
|
+
const json = JSON.stringify(report, null, 2);
|
|
239
|
+
const r = safeWriteOutput(output, json, "Report");
|
|
240
|
+
if (!r.ok) {
|
|
241
|
+
console.error(r.message);
|
|
242
|
+
process.exit(2);
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
const r = safeWriteOutput(output, report.markdown, "Report");
|
|
246
|
+
if (!r.ok) {
|
|
247
|
+
console.error(r.message);
|
|
248
|
+
process.exit(2);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} catch (err) {
|
|
252
|
+
console.error(`Error: ${err.message}`);
|
|
253
|
+
process.exit(3);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/cli/commands/users.ts
|
|
258
|
+
async function usersCommand(ideaArg, rawOptions) {
|
|
259
|
+
const options = normalizeOptions(rawOptions);
|
|
260
|
+
const ideaText = loadIdeaInput(ideaArg);
|
|
261
|
+
const idea = parseIdeaInput({
|
|
262
|
+
idea: ideaText,
|
|
263
|
+
mode: "users",
|
|
264
|
+
stage: options.stage
|
|
265
|
+
});
|
|
266
|
+
const count = parseInt(String(options.personas ?? "6"), 10);
|
|
267
|
+
const providerRes = resolveProvider({
|
|
268
|
+
mock: !!options.mock,
|
|
269
|
+
model: options.model
|
|
270
|
+
});
|
|
271
|
+
if (!providerRes) {
|
|
272
|
+
console.error("No provider available. Use --mock.");
|
|
273
|
+
process.exit(2);
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
const report = await runUserLab(idea, providerRes.provider, count);
|
|
277
|
+
report.markdown = buildReport(report);
|
|
278
|
+
const isJson = !!options.json;
|
|
279
|
+
const output = options.output;
|
|
280
|
+
if (isJson) {
|
|
281
|
+
const json = JSON.stringify(report, null, 2);
|
|
282
|
+
const r = safeWriteOutput(output, json, "Report");
|
|
283
|
+
if (!r.ok) {
|
|
284
|
+
console.error(r.message);
|
|
285
|
+
process.exit(2);
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
const r = safeWriteOutput(output, report.markdown, "Report");
|
|
289
|
+
if (!r.ok) {
|
|
290
|
+
console.error(r.message);
|
|
291
|
+
process.exit(2);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} catch (err) {
|
|
295
|
+
console.error(`Error: ${err.message}`);
|
|
296
|
+
process.exit(3);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/cli/commands/mvp.ts
|
|
301
|
+
async function mvpCommand(ideaArg, rawOptions) {
|
|
302
|
+
const options = normalizeOptions(rawOptions);
|
|
303
|
+
const providerRes = resolveProvider({ mock: !!options.mock, model: options.model });
|
|
304
|
+
if (!providerRes) {
|
|
305
|
+
console.error("No provider. Use --mock.");
|
|
306
|
+
process.exit(2);
|
|
307
|
+
}
|
|
308
|
+
const idea = parseIdeaInput({ idea: loadIdeaInput(ideaArg), mode: "mvp", stage: options.stage });
|
|
309
|
+
const report = await runMvpPlanner(idea, providerRes.provider);
|
|
310
|
+
report.markdown = buildReport(report);
|
|
311
|
+
const isJson = !!options.json;
|
|
312
|
+
const output = options.output;
|
|
313
|
+
if (isJson) {
|
|
314
|
+
const r = safeWriteOutput(output, JSON.stringify(report, null, 2), "Report");
|
|
315
|
+
if (!r.ok) {
|
|
316
|
+
console.error(r.message);
|
|
317
|
+
process.exit(2);
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
const r = safeWriteOutput(output, report.markdown, "Report");
|
|
321
|
+
if (!r.ok) {
|
|
322
|
+
console.error(r.message);
|
|
323
|
+
process.exit(2);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/cli/commands/compare.ts
|
|
329
|
+
async function compareCommand(ideas, rawOptions) {
|
|
330
|
+
const options = normalizeOptions(rawOptions);
|
|
331
|
+
const providerRes = resolveProvider({ mock: !!options.mock, model: options.model });
|
|
332
|
+
if (!providerRes) {
|
|
333
|
+
console.error("No provider. Use --mock.");
|
|
334
|
+
process.exit(2);
|
|
335
|
+
}
|
|
336
|
+
const parsed = ideas.map((i) => parseIdeaInput({ idea: loadIdeaInput(i), mode: "compare" }));
|
|
337
|
+
const report = await runCompareEngine(parsed, providerRes.provider);
|
|
338
|
+
report.markdown = buildReport(report);
|
|
339
|
+
const output = options.output;
|
|
340
|
+
const r = safeWriteOutput(output, report.markdown, "Report");
|
|
341
|
+
if (!r.ok) {
|
|
342
|
+
console.error(r.message);
|
|
343
|
+
process.exit(2);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/cli/commands/prompt.ts
|
|
348
|
+
async function promptCommand(mode, ideaArg, rawOptions) {
|
|
349
|
+
const options = normalizeOptions(rawOptions);
|
|
350
|
+
const ideaText = loadIdeaInput(ideaArg);
|
|
351
|
+
const idea = parseIdeaInput({
|
|
352
|
+
idea: ideaText,
|
|
353
|
+
mode,
|
|
354
|
+
stage: options.stage,
|
|
355
|
+
market: options.market,
|
|
356
|
+
targetUsers: options.targetUsers
|
|
357
|
+
});
|
|
358
|
+
let output = "";
|
|
359
|
+
switch (mode) {
|
|
360
|
+
case "quick":
|
|
361
|
+
output = buildQuickPrompt(ideaText);
|
|
362
|
+
break;
|
|
363
|
+
case "court":
|
|
364
|
+
output = buildCourtPrompt(ideaText);
|
|
365
|
+
break;
|
|
366
|
+
case "users":
|
|
367
|
+
output = buildUsersPrompt(ideaText, options.personas);
|
|
368
|
+
break;
|
|
369
|
+
case "mvp":
|
|
370
|
+
output = buildMvpPrompt(ideaText);
|
|
371
|
+
break;
|
|
372
|
+
default:
|
|
373
|
+
console.error(`Unknown prompt mode: ${mode}. Use: quick, court, users, mvp`);
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
const outFile = options.output;
|
|
377
|
+
const r = safeWriteOutput(outFile, output, "Prompt");
|
|
378
|
+
if (!r.ok) {
|
|
379
|
+
console.error(r.message);
|
|
380
|
+
process.exit(2);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function buildQuickPrompt(ideaText) {
|
|
384
|
+
const sk = skeptic({ idea: ideaText });
|
|
385
|
+
const df = defender({ idea: ideaText });
|
|
386
|
+
return [
|
|
387
|
+
"# IdeaGauntlet \u2014 Quick Critique Prompt",
|
|
388
|
+
"",
|
|
389
|
+
"Copy the prompts below into your AI assistant.",
|
|
390
|
+
"",
|
|
391
|
+
"---",
|
|
392
|
+
"",
|
|
393
|
+
"## Prompt 1: Skeptic",
|
|
394
|
+
`**System:** ${sk.system}`,
|
|
395
|
+
"",
|
|
396
|
+
sk.userMessage,
|
|
397
|
+
"",
|
|
398
|
+
"## Prompt 2: Defender",
|
|
399
|
+
`**System:** ${df.system}`,
|
|
400
|
+
"",
|
|
401
|
+
df.userMessage,
|
|
402
|
+
"",
|
|
403
|
+
"## Instructions for the AI",
|
|
404
|
+
"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."
|
|
405
|
+
].join("\n");
|
|
406
|
+
}
|
|
407
|
+
function buildCourtPrompt(ideaText) {
|
|
408
|
+
return [
|
|
409
|
+
"# IdeaGauntlet \u2014 Court Debate Prompt",
|
|
410
|
+
"",
|
|
411
|
+
`Product idea: ${ideaText}`,
|
|
412
|
+
"",
|
|
413
|
+
"Run a structured debate with these roles:",
|
|
414
|
+
"1. **Prosecutor** \u2014 attacks the idea",
|
|
415
|
+
"2. **Defender** \u2014 argues why it could work",
|
|
416
|
+
"3. **User Advocate** \u2014 argues from the user's perspective",
|
|
417
|
+
"4. **Investor** \u2014 evaluates market, scale, defensibility",
|
|
418
|
+
"5. **Competitor** \u2014 explains how the idea could be copied or crushed",
|
|
419
|
+
"6. **Judge** \u2014 summarizes the verdict",
|
|
420
|
+
"",
|
|
421
|
+
"Each role speaks once, max 300 words. End with a judge verdict and unresolved questions."
|
|
422
|
+
].join("\n");
|
|
423
|
+
}
|
|
424
|
+
function buildUsersPrompt(ideaText, personas = "6") {
|
|
425
|
+
return [
|
|
426
|
+
"# IdeaGauntlet \u2014 Synthetic User Prompt",
|
|
427
|
+
"",
|
|
428
|
+
`Product idea: ${ideaText}`,
|
|
429
|
+
"",
|
|
430
|
+
`Generate ${personas} fictional user archetypes who would encounter this product.`,
|
|
431
|
+
"",
|
|
432
|
+
"For each persona, include:",
|
|
433
|
+
"- Name and archetype label",
|
|
434
|
+
"- Goal they are trying to accomplish",
|
|
435
|
+
"- Current workaround (what they do today)",
|
|
436
|
+
"- Trigger that would make them try this product",
|
|
437
|
+
"- Primary objection (why they would hesitate)",
|
|
438
|
+
"- Willingness to pay (none/low/medium/high)",
|
|
439
|
+
"- Likely reason they would churn",
|
|
440
|
+
"- A quote expressing their skepticism",
|
|
441
|
+
"- One question a founder should ask a real user like this",
|
|
442
|
+
"",
|
|
443
|
+
"**IMPORTANT:** These are fictional archetypes for hypothesis generation, not real validation."
|
|
444
|
+
].join("\n");
|
|
445
|
+
}
|
|
446
|
+
function buildMvpPrompt(ideaText) {
|
|
447
|
+
return [
|
|
448
|
+
"# IdeaGauntlet \u2014 MVP Validation Plan Prompt",
|
|
449
|
+
"",
|
|
450
|
+
`Product idea: ${ideaText}`,
|
|
451
|
+
"",
|
|
452
|
+
"Generate an aggressive MVP validation plan with:",
|
|
453
|
+
"- 14-day MVP plan (max 3 things to build)",
|
|
454
|
+
"- Fake-door test design (what would the landing page say?)",
|
|
455
|
+
"- User interview script (5 questions to ask)",
|
|
456
|
+
"- Success metrics (what numbers justify continuing?)",
|
|
457
|
+
"- Kill criteria (what results mean pivot or stop?)",
|
|
458
|
+
"- Pivot options (adjacent directions if the core doesn't work)",
|
|
459
|
+
"",
|
|
460
|
+
"Be aggressive about reducing scope."
|
|
461
|
+
].join("\n");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/cli/commands/init.ts
|
|
465
|
+
import { mkdirSync, writeFileSync, existsSync } from "fs";
|
|
466
|
+
import { resolve as resolve2 } from "path";
|
|
467
|
+
var IDEA_TEMPLATE = `# My Product Idea
|
|
468
|
+
|
|
469
|
+
## Title
|
|
470
|
+
[Your product name]
|
|
471
|
+
|
|
472
|
+
## Description
|
|
473
|
+
[Describe your product idea in 2-3 sentences. What does it do? Who is it for?]
|
|
474
|
+
|
|
475
|
+
## Target Users
|
|
476
|
+
- [User type 1]
|
|
477
|
+
- [User type 2]
|
|
478
|
+
|
|
479
|
+
## Market
|
|
480
|
+
[What market does this serve?]
|
|
481
|
+
|
|
482
|
+
## Stage
|
|
483
|
+
napkin / pre-mvp / mvp / growth
|
|
484
|
+
|
|
485
|
+
## Constraints
|
|
486
|
+
- Team size:
|
|
487
|
+
- Budget:
|
|
488
|
+
- Timeline:
|
|
489
|
+
|
|
490
|
+
## Known Competitors
|
|
491
|
+
- [Competitor 1]
|
|
492
|
+
- [Competitor 2]
|
|
493
|
+
|
|
494
|
+
## Evidence So Far
|
|
495
|
+
- [Any user interviews, signups, or validation data]
|
|
496
|
+
`;
|
|
497
|
+
var CONFIG_TEMPLATE = `{
|
|
498
|
+
"name": "my-idea-gauntlet",
|
|
499
|
+
"defaultMode": "quick",
|
|
500
|
+
"model": "gpt-4o-mini"
|
|
501
|
+
}
|
|
502
|
+
`;
|
|
503
|
+
async function initCommand(directory, options) {
|
|
504
|
+
const dir = directory ? resolve2(process.cwd(), directory) : resolve2(process.cwd(), ".idea-gauntlet");
|
|
505
|
+
const force = !!options?.force;
|
|
506
|
+
if (existsSync(dir)) {
|
|
507
|
+
const ideasDir = resolve2(dir, "ideas");
|
|
508
|
+
const reportsDir = resolve2(dir, "reports");
|
|
509
|
+
const configFile = resolve2(dir, "config.json");
|
|
510
|
+
const ideaFile = resolve2(ideasDir, "my-idea.md");
|
|
511
|
+
if (!force) {
|
|
512
|
+
console.log(`Directory exists: ${dir}`);
|
|
513
|
+
if (!existsSync(ideasDir)) mkdirSync(ideasDir, { recursive: true });
|
|
514
|
+
if (!existsSync(reportsDir)) mkdirSync(reportsDir, { recursive: true });
|
|
515
|
+
if (!existsSync(ideaFile)) writeFileSync(ideaFile, IDEA_TEMPLATE, "utf-8");
|
|
516
|
+
if (!existsSync(configFile)) writeFileSync(configFile, CONFIG_TEMPLATE, "utf-8");
|
|
517
|
+
console.log("\u2713 Workspace exists. Missing template files created.");
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
} else {
|
|
521
|
+
mkdirSync(dir, { recursive: true });
|
|
522
|
+
}
|
|
523
|
+
mkdirSync(resolve2(dir, "ideas"), { recursive: true });
|
|
524
|
+
mkdirSync(resolve2(dir, "reports"), { recursive: true });
|
|
525
|
+
writeFileSync(resolve2(dir, "ideas", "my-idea.md"), IDEA_TEMPLATE, "utf-8");
|
|
526
|
+
writeFileSync(resolve2(dir, "config.json"), CONFIG_TEMPLATE, "utf-8");
|
|
527
|
+
console.log(`\u2713 IdeaGauntlet workspace created at ${dir}`);
|
|
528
|
+
console.log(` ${dir}/ideas/my-idea.md`);
|
|
529
|
+
console.log(` ${dir}/reports/`);
|
|
530
|
+
console.log(` ${dir}/config.json`);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// src/cli/commands/doctor.ts
|
|
534
|
+
import { existsSync as existsSync2 } from "fs";
|
|
535
|
+
import { resolve as resolve3 } from "path";
|
|
536
|
+
async function doctorCommand(options) {
|
|
537
|
+
const verbose = !!options?.verbose;
|
|
538
|
+
const results = [];
|
|
539
|
+
const nodeOk = isNodeGte(18);
|
|
540
|
+
results.push({
|
|
541
|
+
label: "Node version (>=18)",
|
|
542
|
+
status: nodeOk ? "pass" : "fail",
|
|
543
|
+
message: process.version,
|
|
544
|
+
detail: verbose ? `Detected: ${process.version} at ${process.execPath}` : void 0
|
|
545
|
+
});
|
|
546
|
+
const hasKey = !!getApiKey();
|
|
547
|
+
results.push({
|
|
548
|
+
label: "API key (IDEAGAUNTLET_API_KEY)",
|
|
549
|
+
status: hasKey ? "pass" : "warn",
|
|
550
|
+
message: hasKey ? "Set" : "Not set (prompt/mock/setup still work)"
|
|
551
|
+
});
|
|
552
|
+
const executableOk = checkExecutable();
|
|
553
|
+
results.push({
|
|
554
|
+
label: "CLI executable",
|
|
555
|
+
status: executableOk ? "pass" : "warn",
|
|
556
|
+
message: executableOk ? "Found" : "Not found (run build first)",
|
|
557
|
+
detail: verbose ? `Checked: ${resolve3(process.cwd(), "dist/cli/index.js")}` : void 0
|
|
558
|
+
});
|
|
559
|
+
const codexConfigOk = checkPathExists(".codex/config.toml");
|
|
560
|
+
results.push({
|
|
561
|
+
label: ".codex/config.toml",
|
|
562
|
+
status: codexConfigOk ? "pass" : "warn",
|
|
563
|
+
message: codexConfigOk ? "Found" : "Not found (used by Codex integration)",
|
|
564
|
+
detail: verbose ? `Checked: ${resolve3(process.cwd(), ".codex/config.toml")}` : void 0
|
|
565
|
+
});
|
|
566
|
+
const ollamaOk = await checkOllama();
|
|
567
|
+
results.push({
|
|
568
|
+
label: "Ollama availability",
|
|
569
|
+
status: ollamaOk ? "pass" : "warn",
|
|
570
|
+
message: ollamaOk ? "Running at localhost:11434" : "Not detected",
|
|
571
|
+
detail: verbose && ollamaOk ? "API responded to /api/tags" : void 0
|
|
572
|
+
});
|
|
573
|
+
const mcpOk = await checkMcpServer();
|
|
574
|
+
results.push({
|
|
575
|
+
label: "MCP server startup",
|
|
576
|
+
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
|
|
579
|
+
});
|
|
580
|
+
console.log("\nIdeaGauntlet Doctor");
|
|
581
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
582
|
+
for (const r of results) {
|
|
583
|
+
const icon = r.status === "pass" ? "\u2713" : r.status === "warn" ? "\u26A0" : "\u2717";
|
|
584
|
+
console.log(`${icon} ${r.label}: ${r.message}`);
|
|
585
|
+
if (r.detail && verbose) {
|
|
586
|
+
console.log(` ${r.detail}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const failures = results.filter((r) => r.status === "fail").length;
|
|
590
|
+
if (failures > 0) console.log(`
|
|
591
|
+
${failures} failure(s) found.`);
|
|
592
|
+
else console.log("\nAll critical checks passed.");
|
|
593
|
+
}
|
|
594
|
+
function checkExecutable() {
|
|
595
|
+
const paths = [
|
|
596
|
+
resolve3(process.cwd(), "dist/cli/index.js"),
|
|
597
|
+
resolve3(process.cwd(), "src/cli/index.ts")
|
|
598
|
+
];
|
|
599
|
+
return paths.some((p) => existsSync2(p));
|
|
600
|
+
}
|
|
601
|
+
function checkPathExists(relativePath) {
|
|
602
|
+
return existsSync2(resolve3(process.cwd(), relativePath));
|
|
603
|
+
}
|
|
604
|
+
async function checkOllama() {
|
|
605
|
+
try {
|
|
606
|
+
const res = await fetch("http://localhost:11434/api/tags");
|
|
607
|
+
return res.ok;
|
|
608
|
+
} catch {
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
async function checkMcpServer() {
|
|
613
|
+
try {
|
|
614
|
+
const { startMcpServer: startMcpServer2 } = await import("../server-FLE4IK6S.js");
|
|
615
|
+
return typeof startMcpServer2 === "function";
|
|
616
|
+
} catch {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/cli/commands/mcp.ts
|
|
622
|
+
async function mcpCommand(options) {
|
|
623
|
+
startMcpServer();
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// src/cli/index.ts
|
|
627
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
628
|
+
var pkg = JSON.parse(
|
|
629
|
+
readFileSync2(resolve4(__dirname, "../../package.json"), "utf-8")
|
|
630
|
+
);
|
|
631
|
+
var cli = cac("idea-gauntlet");
|
|
632
|
+
cli.version(pkg.version);
|
|
633
|
+
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
|
+
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
|
+
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
|
+
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));
|
|
642
|
+
cli.command("doctor", "Check environment and configuration").option("--verbose", "Detailed output").action((options) => doctorCommand(options));
|
|
643
|
+
cli.command("mcp", "Start the MCP stdio server").option("--port <port>", "HTTP port (for HTTP transport)").option("--http", "Use HTTP transport instead of stdio").action((options) => mcpCommand(options));
|
|
644
|
+
cli.parse();
|