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/cli.js CHANGED
@@ -1,218 +1,206 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- builder
4
- } from "./chunk-DTWFVPYU.js";
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
- var program = new Command();
11
- program.name("startup-builder").description("Business-as-Code. Define your startup in minutes.").version("0.1.0");
12
- program.argument("[idea]", "Your startup idea").option("--industry <type>", "Industry (fintech, health, devtools, etc)").option("--tone <tone>", "Tone (professional, playful, modern, traditional)").option("--deploy", "Deploy landing page after building").action(async (idea, options) => {
13
- console.log(pc.bold(pc.cyan("\n\u{1F680} Startup Builder\n")));
14
- console.log("Business-as-Code. Define your startup in minutes.\n");
15
- if (!idea) {
16
- const response = await prompts({
17
- type: "text",
18
- name: "idea",
19
- message: "What's your startup idea?",
20
- validate: (value) => value.length > 0 ? true : "Please enter an idea"
21
- });
22
- if (!response.idea) {
23
- console.log(pc.red("\n\u274C Cancelled"));
24
- process.exit(0);
25
- }
26
- idea = response.idea;
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
- const founderResponse = await prompts([
29
- {
30
- type: "text",
31
- name: "capabilities",
32
- message: "Your unique capabilities (optional):",
33
- initial: ""
34
- },
35
- {
36
- type: "text",
37
- name: "insight",
38
- message: "Your unique insight (optional):",
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
- console.log(pc.dim("\nNext steps:"));
90
- console.log(pc.dim(" \u2022 Review your foundation"));
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
- program.command("foundation").alias("sprint").description("Run foundation sprint").argument("[idea]", "Your startup idea").action(async (idea) => {
100
- if (!idea) {
101
- const response = await prompts({
102
- type: "text",
103
- name: "idea",
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
- console.log(pc.cyan("\n\u{1F680} Foundation Sprint\n"));
109
- const foundation = await builder.foundation(idea);
110
- console.log(pc.green("\n\u2705 Foundation complete!"));
111
- console.log(foundation);
112
- });
113
- program.command("canvas").description("Generate lean canvas").argument("[idea]", "Your startup idea").action(async (idea) => {
114
- if (!idea) {
115
- const response = await prompts({
116
- type: "text",
117
- name: "idea",
118
- message: "What's your startup idea?"
119
- });
120
- idea = response.idea;
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
- console.log(pc.cyan("\n\u{1F4CA} Lean Canvas\n"));
123
- const leanCanvas = await builder.canvas(idea);
124
- console.log(pc.green("\n\u2705 Canvas complete!"));
125
- console.log(leanCanvas);
126
- });
127
- program.command("icp").description("Generate ideal customer profile").argument("[idea]", "Your startup idea").action(async (idea) => {
128
- if (!idea) {
129
- const response = await prompts({
130
- type: "text",
131
- name: "idea",
132
- message: "What's your startup idea?"
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
- console.log(pc.cyan("\n\u{1F3AF} Ideal Customer Profile\n"));
137
- const customerProfile = await builder.icp(idea);
138
- console.log(pc.green("\n\u2705 ICP complete!"));
139
- console.log(customerProfile);
140
- });
141
- program.command("jtbd").description("Generate jobs to be done").argument("[idea]", "Your startup idea").action(async (idea) => {
142
- if (!idea) {
143
- const response = await prompts({
144
- type: "text",
145
- name: "idea",
146
- message: "What's your startup idea?"
147
- });
148
- idea = response.idea;
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
- console.log(pc.cyan("\n\u{1F4BC} Jobs to be Done\n"));
151
- const jobs = await builder.jtbd(idea);
152
- console.log(pc.green("\n\u2705 JTBD complete!"));
153
- console.log(jobs);
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
- console.log(pc.cyan("\n\u{1F4D6} StoryBrand Messaging\n"));
165
- const brandStory = await builder.story(idea);
166
- console.log(pc.green("\n\u2705 Story complete!"));
167
- console.log(brandStory);
168
- });
169
- program.command("name").description("Generate startup name").argument("[idea]", "Your startup idea").option("--industry <type>", "Industry").option("--tone <tone>", "Tone").option("--count <number>", "Number of names to generate", "10").action(async (idea, options) => {
170
- if (!idea) {
171
- const response = await prompts({
172
- type: "text",
173
- name: "idea",
174
- message: "What's your startup idea?"
175
- });
176
- idea = response.idea;
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
- idea = response.idea;
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();