ship-create 1.4.0 → 1.6.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/create.mjs CHANGED
@@ -10,18 +10,6 @@
10
10
  *
11
11
  * Usage:
12
12
  * npx ship-create
13
- *
14
- * All templates (starter-kit app, agent rule files, Claude skill) are
15
- * bundled inside this package's own templates/ folder — nothing is read
16
- * from outside, so it works standalone via npx.
17
- *
18
- * What changed from v1.3.1 → v1.4.0:
19
- * - Removed the AI-tool picker (no longer needed)
20
- * - Added 4 idea questions so PROJECT.md + HUMAN_FLOW.md are pre-filled
21
- * with real content (no [bracket placeholders] in the core sections)
22
- * - Runs `npm install` automatically so the project is ready to open
23
- * - End message tells the user exactly how to start building in their
24
- * AI coding tool — no intermediate manual steps
25
13
  */
26
14
 
27
15
  import fs from "node:fs";
@@ -34,19 +22,105 @@ import { spawnSync } from "node:child_process";
34
22
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
35
23
  const TEMPLATES_DIR = path.join(__dirname, "templates");
36
24
 
25
+ // ─── ANSI helpers ────────────────────────────────────────────────────────────
26
+
27
+ const C = {
28
+ reset: "\x1b[0m",
29
+ bold: "\x1b[1m",
30
+ dim: "\x1b[2m",
31
+ cyan: "\x1b[36m",
32
+ green: "\x1b[32m",
33
+ white: "\x1b[37m",
34
+ hideCursor: "\x1b[?25l",
35
+ showCursor: "\x1b[?25h",
36
+ clearLine: "\x1b[2K",
37
+ up: (n) => `\x1b[${n}A`,
38
+ };
39
+
40
+ // ─── Data ─────────────────────────────────────────────────────────────────────
41
+
37
42
  const PRODUCT_TYPES = [
38
- { key: "SAAS", label: "SaaS product", file: "SAAS_TEMPLATE.md" },
39
- { key: "CRM", label: "CRM system", file: "CRM_TEMPLATE.md" },
40
- { key: "MEMBERSHIP", label: "Membership site", file: "MEMBERSHIP_TEMPLATE.md" },
41
- { key: "LEADGEN", label: "Lead generation website", file: "LEADGEN_TEMPLATE.md" },
42
- { key: "DIRECTORY", label: "Directory website", file: "DIRECTORY_TEMPLATE.md" },
43
- { key: "DASHBOARD", label: "Dashboard / internal analytics", file: "DASHBOARD_TEMPLATE.md" },
44
- { key: "INTERNAL_TOOL", label: "Internal tool", file: "INTERNAL_TOOL_TEMPLATE.md" },
45
- { key: "MARKETPLACE", label: "Marketplace", file: "MARKETPLACE_TEMPLATE.md" },
43
+ { key: "SAAS", name: "Web app / SaaS", desc: "users sign up and use a tool online", file: "SAAS_TEMPLATE.md" },
44
+ { key: "CRM", name: "CRM / Sales pipeline", desc: "track leads, deals, and customers", file: "CRM_TEMPLATE.md" },
45
+ { key: "MEMBERSHIP", name: "Membership / community", desc: "paid access to content or a community", file: "MEMBERSHIP_TEMPLATE.md" },
46
+ { key: "LEADGEN", name: "Landing page / lead capture", desc: "get visitors to fill a form or book a call", file: "LEADGEN_TEMPLATE.md" },
47
+ { key: "DIRECTORY", name: "Directory / listing site", desc: "browse and find providers, products, or places", file: "DIRECTORY_TEMPLATE.md" },
48
+ { key: "DASHBOARD", name: "Dashboard / analytics", desc: "visualise data and KPIs for a team or business", file: "DASHBOARD_TEMPLATE.md" },
49
+ { key: "INTERNAL_TOOL", name: "Internal tool", desc: "replaces a manual or spreadsheet-based process inside a company", file: "INTERNAL_TOOL_TEMPLATE.md" },
50
+ { key: "MARKETPLACE", name: "Marketplace", desc: "connects buyers and sellers; transactions happen on the platform", file: "MARKETPLACE_TEMPLATE.md" },
51
+ ];
52
+
53
+ const LANGUAGES = [
54
+ { code: "en", name: "English", desc: "UI text, labels, and copy in English" },
55
+ { code: "th", name: "Thai (ภาษาไทย)", desc: "ข้อความ label และ copy ในระบบเป็นภาษาไทย" },
46
56
  ];
47
57
 
48
- // Pre-seeded journey stages per product type so HUMAN_FLOW.md has real
49
- // content the moment the project is created.
58
+ const I18N = {
59
+ en: {
60
+ sectionIdea: "── About your idea ────────────────────────────",
61
+ qName: "Project name?",
62
+ qNameDefault: "My Product",
63
+ qIdea: "Describe it in 1–3 sentences (who it's for and what it does)",
64
+ qUser: "Who is your primary user? (e.g. 'freelance designers', 'gym owners')",
65
+ qUserDefault: "small business owners",
66
+ qProblem: "What is the #1 problem they face today?",
67
+ qValue: "What makes them say 'I need this'? (the aha moment)",
68
+ scaffolding: (slug) => `Scaffolding ./${slug} ...`,
69
+ installing: "Installing packages (this takes ~30 seconds)...",
70
+ installFail: (slug) => `npm install failed. Run it manually:\n cd ${slug} && npm install`,
71
+ folderExists: (slug) => `Folder ./${slug} already exists — pick a different name and run again.`,
72
+ templatesMissing:"Bundled templates missing. Try: npx ship-create@latest",
73
+ done: (slug) => `
74
+ ✔ Done! Your project is at ./${slug}/
75
+
76
+ docs/PROJECT.md — product spec (pre-filled)
77
+ docs/HUMAN_FLOW.md — UX flow (pre-filled)
78
+ docs/DESIGN_SYSTEM.md — design tokens (filled by /build)
79
+
80
+ ──────────────────────────────────────────
81
+ Open in your AI coding tool and type /build
82
+
83
+ Claude Code → claude ./${slug}
84
+ Cursor → cursor ./${slug}
85
+ Windsurf → windsurf ./${slug}
86
+
87
+ Then type: /build
88
+ `,
89
+ },
90
+ th: {
91
+ sectionIdea: "── เกี่ยวกับ idea ของคุณ ─────────────────────",
92
+ qName: "ชื่อโปรเจค?",
93
+ qNameDefault: "สินค้าของฉัน",
94
+ qIdea: "อธิบายในไม่เกิน 1–3 ประโยค (สำหรับใคร และทำอะไร)",
95
+ qUser: "ผู้ใช้หลักของคุณคือใคร? (เช่น 'ฟรีแลนซ์ดีไซเนอร์', 'เจ้าของยิม')",
96
+ qUserDefault: "เจ้าของธุรกิจขนาดเล็ก",
97
+ qProblem: "ปัญหาอันดับ 1 ที่พวกเขาเจออยู่ทุกวันคืออะไร?",
98
+ qValue: "อะไรทำให้พวกเขาบอกว่า 'ฉันต้องการสิ่งนี้!'? (จุด aha moment)",
99
+ scaffolding: (slug) => `กำลัง scaffold ./${slug} ...`,
100
+ installing: "กำลังติดตั้ง packages (ใช้เวลาประมาณ 30 วินาที)...",
101
+ installFail: (slug) => `npm install ล้มเหลว รันเองได้ที่:\n cd ${slug} && npm install`,
102
+ folderExists: (slug) => `โฟลเดอร์ ./${slug} มีอยู่แล้ว — เลือกชื่ออื่นแล้วรันใหม่`,
103
+ templatesMissing:"ไม่พบ template ที่ bundle ไว้ ลองรัน: npx ship-create@latest",
104
+ done: (slug) => `
105
+ ✔ เสร็จแล้ว! โปรเจคของคุณอยู่ที่ ./${slug}/
106
+
107
+ docs/PROJECT.md — spec สินค้า (กรอกข้อมูลแล้ว)
108
+ docs/HUMAN_FLOW.md — UX flow (กรอกข้อมูลแล้ว)
109
+ docs/DESIGN_SYSTEM.md — design tokens (เติมโดย /build)
110
+
111
+ ──────────────────────────────────────────
112
+ เปิดใน AI coding tool แล้วพิมพ์ /build
113
+
114
+ Claude Code → claude ./${slug}
115
+ Cursor → cursor ./${slug}
116
+ Windsurf → windsurf ./${slug}
117
+
118
+ จากนั้นพิมพ์: /build
119
+ `,
120
+ },
121
+ };
122
+
123
+ // Pre-seeded journey stages per product type so HUMAN_FLOW.md has real content.
50
124
  const JOURNEY_SEEDS = {
51
125
  SAAS: {
52
126
  discovery: "Googles a pain they have, or sees a post about the tool from someone they follow",
@@ -98,19 +172,87 @@ const JOURNEY_SEEDS = {
98
172
  },
99
173
  };
100
174
 
101
- // ─── helpers ────────────────────────────────────────────────────────────────
175
+ // ─── Interactive selector (arrow keys) ───────────────────────────────────────
176
+
177
+ function selectInteractive(title, items) {
178
+ return new Promise((resolve) => {
179
+ let idx = 0;
180
+
181
+ const printItems = () => {
182
+ items.forEach((item, i) => {
183
+ const selected = i === idx;
184
+ process.stdout.write(C.clearLine + "\r");
185
+ if (selected) {
186
+ process.stdout.write(
187
+ ` ${C.cyan}${C.bold}❯ ${item.name}${C.reset}` +
188
+ ` ${C.dim}${item.desc}${C.reset}\n`
189
+ );
190
+ } else {
191
+ process.stdout.write(` ${C.dim} ${item.name}${C.reset}\n`);
192
+ }
193
+ });
194
+ // hint line (no trailing newline so cursor stays here)
195
+ process.stdout.write(
196
+ C.clearLine + `\r ${C.dim}↑↓ navigate · Enter to select${C.reset}`
197
+ );
198
+ };
199
+
200
+ // initial render
201
+ process.stdout.write(`\n ${C.bold}${title}${C.reset}\n\n`);
202
+ process.stdout.write(C.hideCursor);
203
+ printItems();
204
+
205
+ process.stdin.setRawMode(true);
206
+ process.stdin.resume();
207
+ process.stdin.setEncoding("utf8");
208
+
209
+ const onData = (key) => {
210
+ if (key === "\x03") { // Ctrl+C
211
+ process.stdout.write(C.showCursor + "\n");
212
+ process.exit(0);
213
+ }
214
+ if (key === "\r" || key === "\n") { // Enter
215
+ process.stdin.removeListener("data", onData);
216
+ process.stdin.setRawMode(false);
217
+ process.stdin.pause();
218
+ // replace hint line with confirmed selection
219
+ process.stdout.write(
220
+ "\r" + C.clearLine +
221
+ ` ${C.green}${C.bold}✔ ${items[idx].name}${C.reset}\n`
222
+ );
223
+ process.stdout.write(C.showCursor);
224
+ resolve(items[idx]);
225
+ return;
226
+ }
227
+ const prev = idx;
228
+ if (key === "\x1b[A") idx = Math.max(0, idx - 1); // up
229
+ if (key === "\x1b[B") idx = Math.min(items.length - 1, idx + 1); // down
230
+ if (idx !== prev) {
231
+ process.stdout.write(C.up(items.length));
232
+ printItems();
233
+ }
234
+ };
235
+
236
+ process.stdin.on("data", onData);
237
+ });
238
+ }
102
239
 
103
- function toKebabCase(str) {
104
- return str
105
- .trim()
106
- .toLowerCase()
107
- .replace(/[^a-z0-9]+/g, "-")
108
- .replace(/(^-|-$)/g, "") || "my-product";
240
+ // Fallback for non-TTY environments (CI, piped input)
241
+ async function selectFallback(title, items, nextLine) {
242
+ console.log(`\n ${title}`);
243
+ items.forEach((item, i) =>
244
+ console.log(` ${i + 1}. ${item.name} — ${item.desc}`)
245
+ );
246
+ while (true) {
247
+ const raw = await nextLine(` Pick a number (1-${items.length}): `);
248
+ const n = parseInt(raw, 10) - 1;
249
+ if (n >= 0 && n < items.length) return items[n];
250
+ console.log(" Not a valid number, try again.");
251
+ }
109
252
  }
110
253
 
111
- // Use the readline async iterator rather than the `question()` API to avoid
112
- // a Node.js quirk where piped / non-TTY stdin stops delivering buffered
113
- // lines after the first question() call resolves.
254
+ // ─── Text question helper ─────────────────────────────────────────────────────
255
+
114
256
  function makeLineReader(rl) {
115
257
  const it = rl[Symbol.asyncIterator]();
116
258
  return async function nextLine(promptText) {
@@ -122,22 +264,12 @@ function makeLineReader(rl) {
122
264
  }
123
265
 
124
266
  async function ask(nextLine, question, defaultValue) {
125
- const suffix = defaultValue ? ` (${defaultValue})` : "";
126
- const answer = await nextLine(`${question}${suffix}: `);
267
+ const suffix = defaultValue ? ` ${C.dim}(${defaultValue})${C.reset}` : "";
268
+ output.write(` ${question}${suffix}\n ${C.cyan}›${C.reset} `);
269
+ const answer = await nextLine("");
127
270
  return answer || defaultValue || "";
128
271
  }
129
272
 
130
- async function pickFromList(nextLine, title, items, labelFn) {
131
- console.log(`\n${title}`);
132
- items.forEach((item, i) => console.log(` ${i + 1}. ${labelFn(item)}`));
133
- while (true) {
134
- const raw = await nextLine(`Pick a number (1-${items.length}): `);
135
- const idx = parseInt(raw, 10) - 1;
136
- if (idx >= 0 && idx < items.length) return items[idx];
137
- console.log(" Not a valid number, try again.");
138
- }
139
- }
140
-
141
273
  function copyRecursiveExcluding(src, dest, excludeNames) {
142
274
  fs.mkdirSync(dest, { recursive: true });
143
275
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
@@ -152,9 +284,17 @@ function copyRecursiveExcluding(src, dest, excludeNames) {
152
284
  }
153
285
  }
154
286
 
155
- // ─── doc builders ───────────────────────────────────────────────────────────
287
+ function toKebabCase(str) {
288
+ return str
289
+ .trim()
290
+ .toLowerCase()
291
+ .replace(/[^a-z0-9]+/g, "-")
292
+ .replace(/(^-|-$)/g, "") || "my-product";
293
+ }
156
294
 
157
- function buildProjectMd(projectName, productTypeLabel, { targetUser, problem, valueProp, idea }) {
295
+ // ─── Doc builders ─────────────────────────────────────────────────────────────
296
+
297
+ function buildProjectMd(projectName, productTypeName, { targetUser, problem, valueProp, idea, uiLanguage }) {
158
298
  return `# PROJECT.md
159
299
 
160
300
  **Phase:** S — Structure
@@ -164,7 +304,7 @@ function buildProjectMd(projectName, productTypeLabel, { targetUser, problem, va
164
304
 
165
305
  ## 1. Product Vision
166
306
 
167
- ${projectName} is a ${productTypeLabel.toLowerCase()} that helps **${targetUser}** ${valueProp}.
307
+ ${projectName} is a ${productTypeName.toLowerCase()} that helps **${targetUser}** ${valueProp}.
168
308
 
169
309
  > *Expand this into a fuller paragraph before moving to HUMAN_FLOW.md — what does this product become in 2 years if it succeeds?*
170
310
 
@@ -242,7 +382,15 @@ ${projectName} is a ${productTypeLabel.toLowerCase()} that helps **${targetUser}
242
382
 
243
383
  ---
244
384
 
245
- ## 9. Original Idea (keep for reference)
385
+ ## 9. Technical Decisions
386
+
387
+ - **UI language:** ${uiLanguage || "English"}
388
+ - **Auth method:** [magic link / OAuth / password — fill in]
389
+ - **Hosting:** [Vercel / Railway / other — fill in]
390
+
391
+ ---
392
+
393
+ ## 10. Original Idea (keep for reference)
246
394
 
247
395
  > *Your words from when you scaffolded this project.*
248
396
 
@@ -336,94 +484,94 @@ function buildHumanFlowMd(projectName, productType, { targetUser, problem, value
336
484
  `;
337
485
  }
338
486
 
339
- // ─── main ───────────────────────────────────────────────────────────────────
487
+ // ─── Main ──────────────────────────────────────────────────────────────────────
340
488
 
341
489
  async function main() {
490
+ const isTTY = process.stdin.isTTY;
491
+
492
+ // ── Banner ─────────────────────────────────────────────────────────────────
493
+ console.log("");
494
+ console.log(`${C.bold}${C.cyan} ███████╗██╗ ██╗██╗██████╗ ${C.reset}`);
495
+ console.log(`${C.bold}${C.cyan} ██╔════╝██║ ██║██║██╔══██╗${C.reset}`);
496
+ console.log(`${C.bold}${C.cyan} ███████╗███████║██║██████╔╝${C.reset}`);
497
+ console.log(`${C.bold}${C.cyan} ╚════██║██╔══██║██║██╔═══╝ ${C.reset}`);
498
+ console.log(`${C.bold}${C.cyan} ███████║██║ ██║██║██║ ${C.reset}`);
499
+ console.log(`${C.bold}${C.cyan} ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ${C.reset}`);
500
+ console.log("");
501
+ console.log(` ${C.bold}The SHIP Method${C.reset} ${C.dim}· scaffold your project${C.reset}`);
502
+ console.log(` ${C.dim}──────────────────────────────────────────${C.reset}`);
503
+ console.log("");
504
+
505
+ // ── Interactive selections (no readline needed) ────────────────────────────
506
+ let productType, uiLanguage;
507
+
508
+ if (isTTY) {
509
+ productType = await selectInteractive("What type of system are you building?", PRODUCT_TYPES);
510
+ uiLanguage = await selectInteractive("UI language — what will your app display to users?", LANGUAGES);
511
+ }
512
+
513
+ // ── Text questions (readline) — switch language after uiLanguage is known ──
342
514
  const rl = readline.createInterface({ input, terminal: false });
343
515
  const nextLine = makeLineReader(rl);
344
516
 
345
- console.log("===========================================");
346
- console.log(" SHIP CLI scaffold your project in 60 s");
347
- console.log("===========================================\n");
348
-
349
- // ── 1. Core questions ──────────────────────────────────────────────────
350
- const projectNameRaw = await ask(nextLine, "Project name?", "My Product");
351
- const projectSlug = toKebabCase(projectNameRaw);
517
+ if (!isTTY) {
518
+ productType = await selectFallback("What type of system are you building?", PRODUCT_TYPES, nextLine);
519
+ uiLanguage = await selectFallback("UI language — what will your app display to users?", LANGUAGES, nextLine);
520
+ }
352
521
 
353
- const productType = await pickFromList(
354
- nextLine,
355
- "What are you building?",
356
- PRODUCT_TYPES,
357
- (t) => t.label
358
- );
522
+ const t = I18N[uiLanguage.code] ?? I18N.en;
359
523
 
360
- // ── 2. Idea questions (fills the docs without needing a separate AI step)
361
- console.log("\n── About your idea ─────────────────────────");
362
- const idea = await ask(
363
- nextLine,
364
- "Describe it in 1-3 sentences (who it's for and what it does)",
365
- ""
366
- );
367
- const targetUser = await ask(
368
- nextLine,
369
- "Who is your primary user? (e.g. 'freelance designers', 'gym owners')",
370
- "small business owners"
371
- );
372
- const problem = await ask(
373
- nextLine,
374
- "What is the #1 problem they face today?",
375
- ""
376
- );
377
- const valueProp = await ask(
378
- nextLine,
379
- "What makes them say 'I need this'? (the aha moment)",
380
- ""
381
- );
524
+ console.log(`\n ${C.dim}${t.sectionIdea}${C.reset}`);
525
+ const projectNameRaw = await ask(nextLine, t.qName, t.qNameDefault);
526
+ const projectSlug = toKebabCase(projectNameRaw);
527
+ const idea = await ask(nextLine, t.qIdea, "");
528
+ const targetUser = await ask(nextLine, t.qUser, t.qUserDefault);
529
+ const problem = await ask(nextLine, t.qProblem, "");
530
+ const valueProp = await ask(nextLine, t.qValue, "");
382
531
 
383
- // Close readline before any child processes touch stdin
384
532
  rl.close();
385
533
 
386
- // ── 3. Guard: don't overwrite an existing folder ───────────────────────
534
+ // ── Guard: don't overwrite an existing folder ──────────────────────────────
387
535
  const outDir = path.join(process.cwd(), projectSlug);
388
536
  if (fs.existsSync(outDir)) {
389
- console.log(`\n Folder ./${projectSlug} already exists — pick a different name and run again.`);
537
+ console.log(`\n ${C.dim}${t.folderExists(projectSlug)}${C.reset}`);
390
538
  process.exit(1);
391
539
  }
392
540
 
393
- console.log(`\nScaffolding ./${projectSlug} ...`);
541
+ console.log(`\n ${C.dim}${t.scaffolding(projectSlug)}${C.reset}\n`);
394
542
 
395
543
  const starterKitSrc = path.join(TEMPLATES_DIR, "starter-kit");
396
544
  if (!fs.existsSync(starterKitSrc) || !fs.existsSync(path.join(TEMPLATES_DIR, "docs"))) {
397
- console.log("\nBundled templates missing. Try: npx ship-create@latest\n");
545
+ console.log(`\n ${t.templatesMissing}\n`);
398
546
  process.exit(1);
399
547
  }
400
548
 
401
- // ── 4. Copy app shell ──────────────────────────────────────────────────
549
+ // ── Copy app shell ─────────────────────────────────────────────────────────
402
550
  copyRecursiveExcluding(
403
551
  starterKitSrc,
404
552
  outDir,
405
553
  new Set(["node_modules", ".next", "package-lock.json", "next-env.d.ts", "tsconfig.tsbuildinfo"])
406
554
  );
407
555
 
408
- // ── 5. Copy agent rule files (CLAUDE.md, AGENTS.md, .cursorrules, etc.)
556
+ // ── Copy agent rule files ──────────────────────────────────────────────────
409
557
  for (const ruleFile of ["AGENTS.md", "CLAUDE.md", ".cursorrules", ".windsurfrules"]) {
410
558
  const src = path.join(TEMPLATES_DIR, ruleFile);
411
559
  if (fs.existsSync(src)) fs.copyFileSync(src, path.join(outDir, ruleFile));
412
560
  }
413
561
 
414
- // ── 6. Copy Claude Code skill so agents can invoke it as a skill ───────
562
+ // ── Copy Claude Code skill ─────────────────────────────────────────────────
415
563
  const claudeSkillSrc = path.join(TEMPLATES_DIR, ".claude");
416
564
  if (fs.existsSync(claudeSkillSrc)) {
417
565
  copyRecursiveExcluding(claudeSkillSrc, path.join(outDir, ".claude"), new Set());
418
566
  }
419
567
 
420
- // ── 7. Write pre-filled docs (no bracket placeholders in core sections)
568
+ // ── Write pre-filled docs ──────────────────────────────────────────────────
421
569
  const docsDir = path.join(outDir, "docs");
422
570
  fs.mkdirSync(docsDir, { recursive: true });
423
571
 
424
572
  fs.writeFileSync(
425
573
  path.join(docsDir, "PROJECT.md"),
426
- buildProjectMd(projectNameRaw, productType.label, { targetUser, problem, valueProp, idea })
574
+ buildProjectMd(projectNameRaw, productType.name, { targetUser, problem, valueProp, idea, uiLanguage: uiLanguage.name })
427
575
  );
428
576
 
429
577
  fs.writeFileSync(
@@ -431,59 +579,49 @@ async function main() {
431
579
  buildHumanFlowMd(projectNameRaw, productType, { targetUser, problem, valueProp })
432
580
  );
433
581
 
434
- // Product-type feature checklist
435
582
  const templateSrc = path.join(TEMPLATES_DIR, "docs", "product-types", productType.file);
436
583
  if (fs.existsSync(templateSrc)) {
437
584
  fs.copyFileSync(templateSrc, path.join(docsDir, productType.file));
438
585
  }
439
586
 
440
- // Full prompt chain (Stages 3-6) for users who want it
441
587
  const promptsSrc = path.join(TEMPLATES_DIR, "docs", "PROMPTS.md");
442
588
  if (fs.existsSync(promptsSrc)) {
443
589
  fs.copyFileSync(promptsSrc, path.join(docsDir, "PROMPTS.md"));
444
590
  }
445
591
 
446
- // Tech-stack reference — agent reads this when making stack decisions.
447
- // (Design system is NOT bundled as a static file — the /build command
448
- // invokes the ui-ux-pro-max skill to generate one specific to this project.)
449
592
  const techStackSrc = path.join(TEMPLATES_DIR, "docs", "tech-stack");
450
593
  if (fs.existsSync(techStackSrc)) {
451
594
  copyRecursiveExcluding(techStackSrc, path.join(docsDir, "tech-stack"), new Set());
452
595
  }
453
596
 
454
- // ── 8. npm install ─────────────────────────────────────────────────────
455
- console.log("\nInstalling packages (this takes ~30 seconds)...");
597
+ for (const f of ["DESIGN_SYSTEM.md", "DESIGN_SPEC.md"]) {
598
+ const src = path.join(TEMPLATES_DIR, "docs", f);
599
+ if (fs.existsSync(src)) fs.copyFileSync(src, path.join(docsDir, f));
600
+ }
601
+
602
+ // ── npm install ────────────────────────────────────────────────────────────
603
+ console.log(` ${C.dim}${t.installing}${C.reset}\n`);
456
604
  const install = spawnSync("npm", ["install"], {
457
605
  cwd: outDir,
458
606
  stdio: "inherit",
459
607
  shell: true,
460
608
  });
461
609
  if (install.status !== 0) {
462
- console.log(`\n npm install failed. Run it manually:\n cd ${projectSlug} && npm install`);
610
+ console.log(`\n ${t.installFail(projectSlug)}`);
463
611
  }
464
612
 
465
- // ── 9. Done ────────────────────────────────────────────────────────────
466
- console.log(`
467
- Done! Your project is at: ./${projectSlug}/
468
-
469
- docs/PROJECT.md — product spec (pre-filled)
470
- docs/HUMAN_FLOW.md — UX flow (pre-filled)
471
- docs/tech-stack/STACK_DECISION_MATRIX.md — stack choices reference
472
-
473
- ── Open in your AI coding tool and type /build ─────────────────
474
-
475
- Claude Code → claude ./${projectSlug}
476
- Cursor → cursor ./${projectSlug}
477
- Windsurf → windsurf ./${projectSlug}
478
-
479
- Then type: /build
480
-
481
- The agent will read your docs, create the build spec, pick a theme,
482
- and start coding the MVP — no copy-paste, no extra setup.
483
- `);
613
+ // ── Done ───────────────────────────────────────────────────────────────────
614
+ const doneLines = t.done(projectSlug).split("\n");
615
+ const styledDone = doneLines.map((line, i) => {
616
+ if (i === 1) return ` ${C.green}${C.bold}${line.trim()}${C.reset}`;
617
+ if (line.includes("/build")) return line.replace("/build", `${C.cyan}${C.bold}/build${C.reset}`);
618
+ return `${C.dim}${line}${C.reset}`;
619
+ }).join("\n");
620
+ console.log(styledDone);
484
621
  }
485
622
 
486
623
  main().catch((err) => {
487
- console.error("\nSomething went wrong:", err.message);
624
+ process.stdout.write(C.showCursor);
625
+ console.error(`\n ${C.dim}Something went wrong:${C.reset}`, err.message);
488
626
  process.exit(1);
489
627
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ship-create",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Scaffold a new project the SHIP Method way — Structure, Human Flow, Instruction, Publish. No git clone, no API key, just one command.",
5
5
  "type": "module",
6
6
  "license": "MIT",