startup-builder 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/dist/chunk-NWM7TYW6.js +1004 -0
- package/dist/cli.js +188 -200
- package/dist/index.d.ts +434 -108
- package/dist/index.js +17 -19
- package/package.json +24 -36
- package/README.md +0 -179
- package/dist/chunk-DTWFVPYU.js +0 -190
package/dist/cli.js
CHANGED
|
@@ -1,218 +1,206 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
getNextCommands,
|
|
4
|
+
getProgress,
|
|
5
|
+
getRequiredArtifacts,
|
|
6
|
+
reconcileState
|
|
7
|
+
} from "./chunk-NWM7TYW6.js";
|
|
5
8
|
|
|
6
9
|
// src/cli.ts
|
|
7
10
|
import { Command } from "commander";
|
|
8
|
-
import prompts from "prompts";
|
|
9
11
|
import pc from "picocolors";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
import {
|
|
13
|
+
apiRequest,
|
|
14
|
+
ensureAuth,
|
|
15
|
+
output,
|
|
16
|
+
ok,
|
|
17
|
+
err,
|
|
18
|
+
detectOutputFormat,
|
|
19
|
+
getApiBaseUrl,
|
|
20
|
+
resolveContext,
|
|
21
|
+
handleError,
|
|
22
|
+
parseJsonInput
|
|
23
|
+
} from "startups.studio";
|
|
24
|
+
function renderProgressBar(progress) {
|
|
25
|
+
const total = progress.length;
|
|
26
|
+
const completeCount = progress.filter((p) => p.status === "complete").length;
|
|
27
|
+
const currentIdx = progress.findIndex((p) => p.status === "current");
|
|
28
|
+
const pct = Math.round((completeCount + (currentIdx >= 0 ? 0.5 : 0)) / total * 100);
|
|
29
|
+
const barWidth = 30;
|
|
30
|
+
const filled = Math.round(pct / 100 * barWidth);
|
|
31
|
+
const bar = pc.green("\u2588".repeat(filled)) + pc.dim("\u2591".repeat(barWidth - filled));
|
|
32
|
+
const lines = [];
|
|
33
|
+
lines.push("");
|
|
34
|
+
lines.push(` ${bar} ${pc.bold(`${pct}%`)}`);
|
|
35
|
+
lines.push("");
|
|
36
|
+
for (const entry of progress) {
|
|
37
|
+
const icon = entry.status === "complete" ? pc.green("\u2713") : entry.status === "current" ? pc.yellow("\u25B6") : pc.dim("\u25CB");
|
|
38
|
+
const label = entry.status === "current" ? pc.yellow(pc.bold(entry.label)) : entry.status === "complete" ? pc.dim(entry.label) : pc.dim(entry.label);
|
|
39
|
+
lines.push(` ${icon} ${label}`);
|
|
27
40
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
initial: ""
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
type: "text",
|
|
43
|
-
name: "motivation",
|
|
44
|
-
message: "Your motivation (optional):",
|
|
45
|
-
initial: ""
|
|
46
|
-
}
|
|
47
|
-
]);
|
|
48
|
-
const founder = {
|
|
49
|
-
capabilities: founderResponse.capabilities ? [founderResponse.capabilities] : void 0,
|
|
50
|
-
insight: founderResponse.insight || void 0,
|
|
51
|
-
motivation: founderResponse.motivation || void 0
|
|
52
|
-
};
|
|
53
|
-
console.log(pc.dim("\nBuilding your startup foundation...\n"));
|
|
54
|
-
try {
|
|
55
|
-
const startup = await builder.create({
|
|
56
|
-
idea,
|
|
57
|
-
founder,
|
|
58
|
-
industry: options.industry,
|
|
59
|
-
tone: options.tone
|
|
60
|
-
});
|
|
61
|
-
console.log(pc.bold(pc.green("\n\u2705 Complete!\n")));
|
|
62
|
-
console.log(pc.bold("Your Startup Foundation:"));
|
|
63
|
-
console.log(` Name: ${pc.cyan(startup.name.name || "Generated")}`);
|
|
64
|
-
console.log(` Domain: ${pc.cyan(startup.name.domains?.free || "Available")}`);
|
|
65
|
-
console.log("");
|
|
66
|
-
if (options.deploy) {
|
|
67
|
-
const deployment = await builder.deploy(startup);
|
|
68
|
-
console.log(pc.bold(pc.green("\n\u{1F680} Deployed!")));
|
|
69
|
-
console.log(` Landing: ${pc.cyan(deployment.landing)}`);
|
|
70
|
-
if (deployment.dashboard) {
|
|
71
|
-
console.log(` Dashboard: ${pc.cyan(deployment.dashboard)}`);
|
|
72
|
-
}
|
|
73
|
-
} else {
|
|
74
|
-
const deployResponse = await prompts({
|
|
75
|
-
type: "confirm",
|
|
76
|
-
name: "deploy",
|
|
77
|
-
message: "Deploy landing page now?",
|
|
78
|
-
initial: true
|
|
79
|
-
});
|
|
80
|
-
if (deployResponse.deploy) {
|
|
81
|
-
const deployment = await builder.deploy(startup);
|
|
82
|
-
console.log(pc.bold(pc.green("\n\u{1F680} Deployed!")));
|
|
83
|
-
console.log(` Landing: ${pc.cyan(deployment.landing)}`);
|
|
84
|
-
if (deployment.dashboard) {
|
|
85
|
-
console.log(` Dashboard: ${pc.cyan(deployment.dashboard)}`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
41
|
+
return lines.join("\n");
|
|
42
|
+
}
|
|
43
|
+
function formatSchemaValue(value, indent) {
|
|
44
|
+
const pad = " ".repeat(indent);
|
|
45
|
+
if (typeof value === "string") {
|
|
46
|
+
return `"${value}"`;
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
if (value.length === 0) return "[]";
|
|
50
|
+
if (typeof value[0] === "string") {
|
|
51
|
+
return `["${value[0]}"]`;
|
|
88
52
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
console.log(pc.dim(" \u2022 Run validation experiments"));
|
|
92
|
-
console.log(pc.dim(" \u2022 Start building your MVP"));
|
|
93
|
-
console.log("");
|
|
94
|
-
} catch (error) {
|
|
95
|
-
console.error(pc.red("\n\u274C Error:"), error instanceof Error ? error.message : error);
|
|
96
|
-
process.exit(1);
|
|
53
|
+
const inner = value.map((item) => formatSchemaValue(item, indent + 2)).join(", ");
|
|
54
|
+
return `[${inner}]`;
|
|
97
55
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
message: "What's your startup idea?"
|
|
105
|
-
});
|
|
106
|
-
idea = response.idea;
|
|
56
|
+
if (typeof value === "object" && value !== null) {
|
|
57
|
+
const entries = Object.entries(value);
|
|
58
|
+
const parts = entries.map(([k, v]) => `${pad} "${k}": ${formatSchemaValue(v, indent + 2)}`);
|
|
59
|
+
return `{
|
|
60
|
+
${parts.join(",\n")}
|
|
61
|
+
${pad}}`;
|
|
107
62
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
});
|
|
120
|
-
|
|
63
|
+
return String(value);
|
|
64
|
+
}
|
|
65
|
+
function renderCommandSuggestions(commands, _startupId) {
|
|
66
|
+
const lines = [];
|
|
67
|
+
lines.push("");
|
|
68
|
+
lines.push(pc.bold(" Next steps:"));
|
|
69
|
+
lines.push("");
|
|
70
|
+
for (const cmd of commands) {
|
|
71
|
+
lines.push(` ${pc.cyan(pc.bold(cmd.name))}`);
|
|
72
|
+
const schemaStr = formatSchemaValue(cmd.schema, 4);
|
|
73
|
+
const fullCommand = `${cmd.command} --json '${schemaStr}'`;
|
|
74
|
+
lines.push(` ${pc.dim("$")} ${pc.white(fullCommand)}`);
|
|
75
|
+
lines.push("");
|
|
121
76
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
});
|
|
134
|
-
idea = response.idea;
|
|
77
|
+
return lines.join("\n");
|
|
78
|
+
}
|
|
79
|
+
function renderArtifactStatus(artifacts) {
|
|
80
|
+
if (artifacts.length === 0) return "";
|
|
81
|
+
const lines = [];
|
|
82
|
+
lines.push("");
|
|
83
|
+
lines.push(pc.bold(" Required artifacts:"));
|
|
84
|
+
for (const a of artifacts) {
|
|
85
|
+
const met = a.current >= a.required;
|
|
86
|
+
const icon = met ? pc.green("\u2713") : pc.red("\u2717");
|
|
87
|
+
const count = met ? pc.green(`${a.current}/${a.required}`) : pc.yellow(`${a.current}/${a.required}`);
|
|
88
|
+
lines.push(` ${icon} ${a.description} (${a.collection}) ${count}`);
|
|
135
89
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
90
|
+
return lines.join("\n");
|
|
91
|
+
}
|
|
92
|
+
function renderNextSteps(currentState, startupId, counts, format) {
|
|
93
|
+
const progress = getProgress(currentState);
|
|
94
|
+
const commands = getNextCommands(currentState, startupId);
|
|
95
|
+
const artifacts = getRequiredArtifacts(currentState, counts);
|
|
96
|
+
if (format === "json") {
|
|
97
|
+
const result = {
|
|
98
|
+
state: currentState,
|
|
99
|
+
progress: progress.map((p) => ({ state: p.state, label: p.label, status: p.status })),
|
|
100
|
+
requiredArtifacts: artifacts,
|
|
101
|
+
commands: commands.map((c) => ({
|
|
102
|
+
name: c.name,
|
|
103
|
+
collection: c.collection,
|
|
104
|
+
operation: c.operation,
|
|
105
|
+
command: c.command,
|
|
106
|
+
schema: c.schema
|
|
107
|
+
}))
|
|
108
|
+
};
|
|
109
|
+
output(ok(result), "json");
|
|
110
|
+
return;
|
|
149
111
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
program.command("story").description("Generate StoryBrand messaging").argument("[idea]", "Your startup idea").action(async (idea) => {
|
|
156
|
-
if (!idea) {
|
|
157
|
-
const response = await prompts({
|
|
158
|
-
type: "text",
|
|
159
|
-
name: "idea",
|
|
160
|
-
message: "What's your startup idea?"
|
|
161
|
-
});
|
|
162
|
-
idea = response.idea;
|
|
112
|
+
const stateNode = progress.find((p) => p.status === "current");
|
|
113
|
+
if (stateNode) {
|
|
114
|
+
process.stdout.write(`
|
|
115
|
+
${pc.bold("Startup Builder")} ${pc.dim("|")} ${pc.yellow(stateNode.label)}
|
|
116
|
+
`);
|
|
163
117
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
program.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
console.log(pc.cyan("\n\u2728 Name Generation\n"));
|
|
179
|
-
const name = await builder.name(idea, {
|
|
180
|
-
industry: options.industry,
|
|
181
|
-
tone: options.tone,
|
|
182
|
-
count: parseInt(options.count)
|
|
183
|
-
});
|
|
184
|
-
console.log(pc.green("\n\u2705 Names generated!"));
|
|
185
|
-
console.log(name);
|
|
186
|
-
});
|
|
187
|
-
program.command("landing").description("Generate landing page").argument("[idea]", "Your startup idea").action(async (idea) => {
|
|
188
|
-
if (!idea) {
|
|
189
|
-
const response = await prompts({
|
|
190
|
-
type: "text",
|
|
191
|
-
name: "idea",
|
|
192
|
-
message: "What's your startup idea?"
|
|
118
|
+
process.stdout.write(renderProgressBar(progress) + "\n");
|
|
119
|
+
process.stdout.write(renderArtifactStatus(artifacts) + "\n");
|
|
120
|
+
process.stdout.write(renderCommandSuggestions(commands, startupId) + "\n");
|
|
121
|
+
}
|
|
122
|
+
var program = new Command();
|
|
123
|
+
program.name("startup-builder").description("Orchestrate a Foundation Sprint \u2014 build a startup step by step").version("0.1.0").argument("[description]", "Short description of the startup idea").option("--idea <id>", "Existing idea ID to build from").option("--name <name>", "Name for the new startup").option("--json <payload>", "JSON input with idea/startup details").option("--startup <id>", "Existing startup ID (for re-entry / status check)").option("--output <format>", "Output format: json or text").option("--api-url <url>", "API base URL").option("--studio <slug>", "Studio (organization) slug").action(async (description, opts) => {
|
|
124
|
+
const format = detectOutputFormat(opts.output ?? (process.stdout.isTTY ? void 0 : "json"));
|
|
125
|
+
const apiBaseUrl = opts.apiUrl || getApiBaseUrl();
|
|
126
|
+
try {
|
|
127
|
+
const ctx = resolveContext({
|
|
128
|
+
output: opts.output,
|
|
129
|
+
apiUrl: opts.apiUrl,
|
|
130
|
+
studio: opts.studio
|
|
193
131
|
});
|
|
194
|
-
|
|
132
|
+
const auth = await ensureAuth(ctx);
|
|
133
|
+
let startupId = opts.startup;
|
|
134
|
+
let ideaId = opts.idea;
|
|
135
|
+
let sprintId;
|
|
136
|
+
let jsonInput = {};
|
|
137
|
+
if (opts.json) {
|
|
138
|
+
jsonInput = parseJsonInput(opts.json);
|
|
139
|
+
startupId = startupId || jsonInput.startup;
|
|
140
|
+
ideaId = ideaId || jsonInput.idea;
|
|
141
|
+
description = description || jsonInput.description;
|
|
142
|
+
}
|
|
143
|
+
if (!startupId) {
|
|
144
|
+
const startupName = opts.name || jsonInput.name || description || "Untitled Startup";
|
|
145
|
+
const startupResult = await apiRequest(apiBaseUrl, "/api/startups", {
|
|
146
|
+
method: "POST",
|
|
147
|
+
body: {
|
|
148
|
+
name: startupName,
|
|
149
|
+
...ctx.studioSlug ? { studio: ctx.studioSlug } : {}
|
|
150
|
+
},
|
|
151
|
+
apiKey: auth.apiKey
|
|
152
|
+
});
|
|
153
|
+
if (!startupResult.ok) {
|
|
154
|
+
output(err(startupResult.error.code, `Failed to create startup: ${startupResult.error.message}`), format);
|
|
155
|
+
process.exitCode = 1;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
startupId = startupResult.data.id;
|
|
159
|
+
if (format === "text") {
|
|
160
|
+
process.stdout.write(`
|
|
161
|
+
${pc.green("\u2713")} Created startup ${pc.bold(startupResult.data.name)} ${pc.dim(`(${startupId})`)}
|
|
162
|
+
`);
|
|
163
|
+
}
|
|
164
|
+
if (!ideaId) {
|
|
165
|
+
const ideaResult = await apiRequest(apiBaseUrl, "/api/ideas", {
|
|
166
|
+
method: "POST",
|
|
167
|
+
body: {
|
|
168
|
+
name: description || startupName,
|
|
169
|
+
startup: startupId,
|
|
170
|
+
description: description || void 0
|
|
171
|
+
},
|
|
172
|
+
apiKey: auth.apiKey
|
|
173
|
+
});
|
|
174
|
+
if (ideaResult.ok) {
|
|
175
|
+
ideaId = ideaResult.data.id;
|
|
176
|
+
if (format === "text") {
|
|
177
|
+
process.stdout.write(` ${pc.green("\u2713")} Created idea ${pc.dim(`(${ideaId})`)}
|
|
178
|
+
`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const sprintResult = await apiRequest(apiBaseUrl, "/api/sprints", {
|
|
183
|
+
method: "POST",
|
|
184
|
+
body: {
|
|
185
|
+
name: "Foundation Sprint",
|
|
186
|
+
startup: startupId,
|
|
187
|
+
idea: ideaId || void 0,
|
|
188
|
+
type: "foundation"
|
|
189
|
+
},
|
|
190
|
+
apiKey: auth.apiKey
|
|
191
|
+
});
|
|
192
|
+
if (sprintResult.ok) {
|
|
193
|
+
sprintId = sprintResult.data.id;
|
|
194
|
+
if (format === "text") {
|
|
195
|
+
process.stdout.write(` ${pc.green("\u2713")} Created Foundation Sprint ${pc.dim(`(${sprintId})`)}
|
|
196
|
+
`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const { currentState, counts } = await reconcileState(startupId, apiBaseUrl, auth.apiKey);
|
|
201
|
+
renderNextSteps(currentState, startupId, counts, format);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
handleError(e, format);
|
|
195
204
|
}
|
|
196
|
-
console.log(pc.cyan("\n\u{1F3A8} Landing Page\n"));
|
|
197
|
-
const name = await builder.name(idea);
|
|
198
|
-
const brandStory = await builder.story(idea);
|
|
199
|
-
const page = await builder.landing(name, brandStory);
|
|
200
|
-
console.log(pc.green("\n\u2705 Landing page complete!"));
|
|
201
|
-
console.log(page);
|
|
202
|
-
});
|
|
203
|
-
program.command("build").description("Build complete startup foundation").argument("[idea]", "Your startup idea").option("--industry <type>", "Industry").option("--tone <tone>", "Tone").option("--deploy", "Deploy landing page").action(async (idea, options) => {
|
|
204
|
-
await program.parseAsync(["node", "cli", idea, ...process.argv.slice(3)].filter(Boolean));
|
|
205
|
-
});
|
|
206
|
-
program.command("export").description("Export startup definition").option("--format <type>", "Export format (md, json)", "md").action(async (options) => {
|
|
207
|
-
console.log(pc.cyan(`
|
|
208
|
-
Exporting as ${options.format}...
|
|
209
|
-
`));
|
|
210
|
-
console.log(pc.yellow("\u26A0\uFE0F Export not yet implemented"));
|
|
211
|
-
console.log(pc.dim(" Coming soon: Export to markdown and JSON"));
|
|
212
|
-
});
|
|
213
|
-
program.command("mcp").description("Start MCP server").action(async () => {
|
|
214
|
-
console.log(pc.cyan("\n\u{1F50C} MCP Server\n"));
|
|
215
|
-
console.log(pc.yellow("\u26A0\uFE0F MCP server not yet implemented"));
|
|
216
|
-
console.log(pc.dim(" Coming soon: Model Context Protocol server"));
|
|
217
205
|
});
|
|
218
206
|
program.parse();
|