sproutkit-code 0.12.0 → 0.14.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/index.js CHANGED
@@ -79756,7 +79756,8 @@ var require_builder = __commonJS({
79756
79756
  }
79757
79757
  }
79758
79758
  async function buildAndDeploySite(args) {
79759
- const { lead, anthropicKey, vercelToken, placesKey, customInstructions, onProgress, includeCms, blobToken, reuseAdminPassword, skipContentExtraction } = args;
79759
+ const { lead, anthropicKey, vercelToken, placesKey, customInstructions, onProgress, includeCms, blobToken, reuseAdminPassword, skipContentExtraction, multipage, pages } = args;
79760
+ const pageList = multipage && (!pages || pages.length === 0) ? ["Home", "Services", "About", "Contact"] : pages ?? [];
79760
79761
  if (includeCms && !blobToken) {
79761
79762
  throw new Error("CMS is enabled but no Vercel Blob token is set. Add it in Settings \u2192 Vercel Blob token.");
79762
79763
  }
@@ -79913,11 +79914,24 @@ SECTIONS (in this order)
79913
79914
  6. REVIEWS \u2014 display the real reviews above as testimonial cards with the customer's name and stars
79914
79915
  7. CONTACT \u2014 address, phone, hours in a clear card. Map embed NOT required.
79915
79916
  8. FOOTER \u2014 simple, business name + address + a final "Built with Sproutkit \xB7 sproutkit.app" line in muted text
79917
+ ${multipage ? `
79918
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
79919
+ MULTIPAGE (REQUIRED) \u2014 real separate pages, not one long scroll
79920
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
79921
+ Build a MULTI-PAGE site using react-router-dom's HashRouter (import { HashRouter, Routes, Route, Link, useNavigate } from "react-router-dom"). HashRouter is REQUIRED (not BrowserRouter) so routes work on static hosting without server config.
79916
79922
 
79923
+ Pages (one <Route> each), with a persistent sticky NAV linking between them via <Link>:
79924
+ ${pageList.map((p, i) => ` ${i + 1}. ${p}`).join("\n")}
79925
+
79926
+ - Home: hero + a short intro + 2-3 teaser sections that link through to the other pages.
79927
+ - Distribute the sections above across the pages sensibly (e.g. Services page = full services; About = the about/story + reviews; Contact = address/phone/hours/CTA). Don't cram everything onto Home.
79928
+ - The whole thing is STILL one App.jsx exporting a single default App component that renders <HashRouter>\u2026</HashRouter>. Define each page as a component in the same file.
79929
+ - Nav must be present on every page and highlight the active route. Include the phone/Book CTA in the nav.
79930
+ ` : ""}
79917
79931
  \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
79918
79932
  TECHNICAL REQUIREMENTS
79919
79933
  \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
79920
- - Single React component in App.jsx, exported as default
79934
+ - Single React component in App.jsx, exported as default${multipage ? " (it renders <HashRouter> with the pages above; define page components in the same file)" : ""}
79921
79935
  - Inline styles ONLY (no Tailwind, no CSS files, no external UI libs)
79922
79936
  - Mobile-responsive (test mental model: 375px wide must look great)
79923
79937
  - Use this constant for photos: const PHOTOS = REPLACE_PHOTOS;
@@ -79928,7 +79942,7 @@ TECHNICAL REQUIREMENTS
79928
79942
  - Phone CTA button: <a href="tel:${lead.phone}">
79929
79943
  - WhatsApp option: <a href="https://wa.me/${lead.phone.replace(/[^0-9]/g, "").replace(/^0/, "44")}">
79930
79944
  - Final line MUST be: export default App;
79931
- - Keep it focused: max 350 lines, no unused code
79945
+ - Keep it focused: max ${multipage ? 700 : 350} lines, no unused code
79932
79946
  ${includeCms ? `
79933
79947
  \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
79934
79948
  CMS INTEGRATION (MANDATORY when this brief mentions it)
@@ -80063,13 +80077,16 @@ ${prompt}` : prompt;
80063
80077
  import { createRoot } from 'react-dom/client';
80064
80078
  import App from './App.jsx';
80065
80079
  createRoot(document.getElementById('root')).render(<StrictMode><App /></StrictMode>);`;
80080
+ const baseDeps = { react: "^18.3.1", "react-dom": "^18.3.1" };
80081
+ if (multipage)
80082
+ baseDeps["react-router-dom"] = "^6.26.2";
80066
80083
  const packageJson = JSON.stringify({
80067
80084
  name: "sprout-demo",
80068
80085
  private: true,
80069
80086
  version: "0.0.1",
80070
80087
  type: "module",
80071
80088
  scripts: { build: "vite build", dev: "vite" },
80072
- dependencies: { react: "^18.3.1", "react-dom": "^18.3.1" },
80089
+ dependencies: { ...baseDeps },
80073
80090
  devDependencies: { "@vitejs/plugin-react": "^4.3.1", vite: "^5.4.2" }
80074
80091
  });
80075
80092
  const viteConfig = `import { defineConfig } from 'vite';
@@ -80086,8 +80103,7 @@ export default defineConfig({ plugins: [react()] });`;
80086
80103
  type: "module",
80087
80104
  scripts: { build: "vite build", dev: "vite" },
80088
80105
  dependencies: {
80089
- react: "^18.3.1",
80090
- "react-dom": "^18.3.1",
80106
+ ...baseDeps,
80091
80107
  "@vercel/blob": "^0.23.4",
80092
80108
  bcryptjs: "^2.4.3"
80093
80109
  },
@@ -81126,6 +81142,10 @@ var require_chat_tools = __commonJS({
81126
81142
  includeCms: {
81127
81143
  type: "boolean",
81128
81144
  description: "When true, deploy the site WITH a /admin CMS editor on the customer's own domain. The site becomes editable by the business owner via a password. Requires a Vercel Blob token to be configured in Settings. Returns an admin URL + password to hand off."
81145
+ },
81146
+ multipage: {
81147
+ type: "boolean",
81148
+ description: "When true, build a MULTI-PAGE site (Home/Services/About/Contact as real routed pages with a nav) instead of a single long-scroll page. Use when the user asks for a multipage site or a bigger site with separate pages."
81129
81149
  }
81130
81150
  },
81131
81151
  required: ["id"]
@@ -81254,6 +81274,7 @@ var require_chat_tools = __commonJS({
81254
81274
  placesKey,
81255
81275
  customInstructions: args.instructions,
81256
81276
  includeCms: args.includeCms,
81277
+ multipage: args.multipage,
81257
81278
  blobToken,
81258
81279
  onProgress: (msg) => args.ctx.emitProgress?.({
81259
81280
  channel: "sites:progress",
@@ -81490,6 +81511,7 @@ Interested?`;
81490
81511
  id,
81491
81512
  instructions: input.instructions,
81492
81513
  includeCms: !!input.includeCms,
81514
+ multipage: !!input.multipage,
81493
81515
  ctx
81494
81516
  });
81495
81517
  return { ...result, id };
@@ -81503,7 +81525,12 @@ Interested?`;
81503
81525
  error: "Please ask the user what they want changed before regenerating \u2014 instructions are required."
81504
81526
  };
81505
81527
  }
81506
- const result = await buildSiteForChat({ id, instructions, ctx });
81528
+ const result = await buildSiteForChat({
81529
+ id,
81530
+ instructions,
81531
+ multipage: !!input.multipage,
81532
+ ctx
81533
+ });
81507
81534
  return { ...result, id };
81508
81535
  }
81509
81536
  case "build_all_sites": {
@@ -82565,7 +82592,7 @@ var require_package = __commonJS({
82565
82592
  "package.json"(exports2, module2) {
82566
82593
  module2.exports = {
82567
82594
  name: "sproutkit-code",
82568
- version: "0.12.0",
82595
+ version: "0.14.0",
82569
82596
  description: "Sproutkit in your terminal \u2014 find leads, build sites, run outreach, all via chat.",
82570
82597
  bin: {
82571
82598
  sproutkit: "./dist/index.js"
@@ -83714,7 +83741,8 @@ async function cmdBuild(argv) {
83714
83741
  {
83715
83742
  id,
83716
83743
  instructions: flags.instructions ? String(flags.instructions) : void 0,
83717
- includeCms: !!flags.cms
83744
+ includeCms: !!flags.cms,
83745
+ multipage: !!flags.multipage
83718
83746
  },
83719
83747
  makeCtx({ json, status })
83720
83748
  );
@@ -83762,6 +83790,69 @@ async function cmdOutreach(argv) {
83762
83790
  if (result.noPhone) say(c.dim(` ${result.noPhone} had no phone number and were skipped for messaging.`));
83763
83791
  say();
83764
83792
  }
83793
+ async function cmdRegenerate(argv) {
83794
+ const { flags, rest } = parseArgs(argv);
83795
+ const json = !!flags.json;
83796
+ const id = Number(flags.id ?? rest[0]);
83797
+ const instructions = flags.instructions ? String(flags.instructions) : "";
83798
+ if (!id || Number.isNaN(id)) {
83799
+ const err = 'Usage: sproutkit regenerate <leadId> --instructions "what to change"';
83800
+ json ? emitJson({ ok: false, error: err }) : say(c.red(" " + err));
83801
+ process.exitCode = 1;
83802
+ return;
83803
+ }
83804
+ if (!instructions.trim()) {
83805
+ const err = "regenerate needs --instructions describing what to change.";
83806
+ json ? emitJson({ ok: false, error: err }) : say(c.red(" " + err));
83807
+ process.exitCode = 1;
83808
+ return;
83809
+ }
83810
+ const status = json ? void 0 : new Status();
83811
+ if (status) status.start(`Rebuilding website for lead #${id}`);
83812
+ try {
83813
+ const result = await runTool(
83814
+ "regenerate_site",
83815
+ { id, instructions, multipage: !!flags.multipage },
83816
+ makeCtx({ json, status })
83817
+ );
83818
+ if (result && result.ok === false) {
83819
+ if (status) status.done(false, result.error ?? "Regenerate failed");
83820
+ if (json) emitJson(result);
83821
+ process.exitCode = 1;
83822
+ return;
83823
+ }
83824
+ if (status) status.done(true, result.url ? `Live at ${result.url}` : `Rebuilt lead #${id}`);
83825
+ autoSync();
83826
+ if (json) emitJson(result);
83827
+ } catch (e) {
83828
+ if (status) status.done(false, "Regenerate failed");
83829
+ json ? emitJson({ ok: false, error: e.message }) : say(c.red(" " + e.message));
83830
+ process.exitCode = 1;
83831
+ }
83832
+ }
83833
+ async function cmdCms(argv) {
83834
+ const { flags, rest } = parseArgs(argv);
83835
+ const json = !!flags.json;
83836
+ const id = Number(flags.id ?? rest[0]);
83837
+ if (!id || Number.isNaN(id)) {
83838
+ const err = "Usage: sproutkit cms <leadId>";
83839
+ json ? emitJson({ ok: false, error: err }) : say(c.red(" " + err));
83840
+ process.exitCode = 1;
83841
+ return;
83842
+ }
83843
+ const status = json ? void 0 : new Status();
83844
+ if (status) status.start(`Setting up CMS for lead #${id}`);
83845
+ try {
83846
+ const result = await runTool("provision_cms", { id }, makeCtx({ json, status }));
83847
+ if (status) status.done(true, result.publicUrl ? `CMS live at ${result.publicUrl}` : `CMS set up for #${id}`);
83848
+ autoSync();
83849
+ if (json) emitJson(result);
83850
+ } catch (e) {
83851
+ if (status) status.done(false, "CMS setup failed");
83852
+ json ? emitJson({ ok: false, error: e.message }) : say(c.red(" " + e.message));
83853
+ process.exitCode = 1;
83854
+ }
83855
+ }
83765
83856
  async function cmdBook(argv) {
83766
83857
  const { flags, rest } = parseArgs(argv);
83767
83858
  const json = !!flags.json;
@@ -83896,10 +83987,12 @@ When a request is missing information the command needs, ask a SHORT, STRUCTURED
83896
83987
  - Once you have the needed choices, act immediately \u2014 one command, one line of result. Don't re-confirm what they already told you.
83897
83988
 
83898
83989
  COMMANDS:
83899
- - sproutkit find "<area>" --categories "<type>,<type>" --json \u2014 find leads
83990
+ - sproutkit find "<area>" --categories "<type>,<type>" --json \u2014 find website-less leads
83900
83991
  - sproutkit list --json [--status <s>] [--city <c>] \u2014 list leads (get ids)
83901
- - sproutkit build <id> --json [--instructions "..."] [--cms] \u2014 build a demo site
83992
+ - sproutkit build <id> --json [--instructions "..."] [--cms] [--multipage] \u2014 build a demo site (--multipage = real Home/Services/About/Contact pages, not one scroll)
83902
83993
  - sproutkit build-all --json [--ids 3,5] [--concurrency 3] \u2014 build many at once
83994
+ - sproutkit regenerate <id> --instructions "..." --json \u2014 rebuild an existing site with changes
83995
+ - sproutkit cms <id> --json \u2014 add the editable CMS on top of the site we built them
83903
83996
  - sproutkit book <id> --json [--standalone] [--instructions "..."] [--archetype slots|reservation|enquiry] [--email x@y.com] \u2014 booking site (--standalone = its own URL for clients who already have a website)
83904
83997
  - sproutkit outreach --json [--ids 3,5] \u2014 prepare outreach links
83905
83998
 
@@ -84042,7 +84135,7 @@ async function main() {
84042
84135
  closeTty();
84043
84136
  return;
84044
84137
  }
84045
- if (arg === "find" || arg === "list" || arg === "build" || arg === "build-all" || arg === "book" || arg === "outreach") {
84138
+ if (arg === "find" || arg === "list" || arg === "build" || arg === "build-all" || arg === "regenerate" || arg === "cms" || arg === "book" || arg === "outreach") {
84046
84139
  const subArgs = process.argv.slice(3);
84047
84140
  const lic2 = await check();
84048
84141
  if (lic2.status !== "active") {
@@ -84057,6 +84150,8 @@ async function main() {
84057
84150
  else if (arg === "list") await cmdList(subArgs);
84058
84151
  else if (arg === "build") await cmdBuild(subArgs);
84059
84152
  else if (arg === "build-all") await cmdBuildAll(subArgs);
84153
+ else if (arg === "regenerate") await cmdRegenerate(subArgs);
84154
+ else if (arg === "cms") await cmdCms(subArgs);
84060
84155
  else if (arg === "book") await cmdBook(subArgs);
84061
84156
  else await cmdOutreach(subArgs);
84062
84157
  closeTty();
@@ -84072,6 +84167,8 @@ async function main() {
84072
84167
  say(` ${c.bold("sproutkit list")} list leads (--status, --city, --json)`);
84073
84168
  say(` ${c.bold("sproutkit build <id>")} build a website (--instructions, --cms)`);
84074
84169
  say(` ${c.bold("sproutkit build-all")} build many at once (--ids, --concurrency 3)`);
84170
+ say(` ${c.bold("sproutkit regenerate <id>")} rebuild a site (--instructions "\u2026")`);
84171
+ say(` ${c.bold("sproutkit cms <id>")} add the editable CMS to a built site`);
84075
84172
  say(` ${c.bold("sproutkit book <id>")} booking site (--standalone for own-URL)`);
84076
84173
  say(` ${c.bold("sproutkit outreach")} prep outreach links (--ids 3,5)`);
84077
84174
  say(` ${c.bold("sproutkit setup")} set or update keys, licence, backend`);
@@ -84145,17 +84242,20 @@ async function main() {
84145
84242
  }
84146
84243
  const direct = line.replace(/^\//, "");
84147
84244
  const verb = direct.split(/\s+/)[0]?.toLowerCase();
84148
- const DIRECT_VERBS = ["find", "list", "build", "build-all", "book", "outreach"];
84245
+ const DIRECT_VERBS = ["find", "list", "build", "build-all", "regenerate", "cms", "book", "outreach"];
84149
84246
  if (verb === "commands" || verb === "help" || line === "/help" || line === "/?") {
84150
84247
  say();
84151
84248
  say(c.bold(" Commands (type with or without a leading /):"));
84152
- say(` ${c.greenBold("find")} "<area>" --categories "cafe,barber" find leads`);
84153
- say(` ${c.greenBold("list")} [--status <s>] [--city <c>] list leads`);
84154
- say(` ${c.greenBold("build")} <id> [--instructions "\u2026"] [--cms] build a demo site`);
84155
- say(` ${c.greenBold("build-all")} [--ids 3,5] [--concurrency 3] build many at once`);
84156
- say(` ${c.greenBold("book")} <id> [--standalone] [--archetype \u2026] booking site`);
84249
+ say(` ${c.greenBold("find")} "<area>" --categories "cafe,barber" find website-less leads`);
84250
+ say(` ${c.greenBold("list")} [--status <s>] [--city <c>] list leads (get ids)`);
84251
+ say(` ${c.greenBold("build")} <id> [--instructions "\u2026"] [--cms] [--multipage] build a demo site`);
84252
+ say(` ${c.greenBold("build-all")} [--ids 3,5] [--concurrency 3] build many at once`);
84253
+ say(` ${c.greenBold("regenerate")} <id> --instructions "\u2026" rebuild a site with changes`);
84254
+ say(` ${c.greenBold("cms")} <id> add the editable CMS to a built site`);
84255
+ say(` ${c.greenBold("book")} <id> [--standalone] [--archetype slots|reservation|enquiry] [--email \u2026]`);
84256
+ say(` ${c.dim(" ")} booking on the site, or --standalone for its own URL`);
84157
84257
  say(` ${c.greenBold("outreach")} [--ids 3,5] prepare outreach links`);
84158
- say(c.dim(" /freemode \u2014 open conversation \xB7 /taskmode \u2014 precise \xB7 exit \u2014 quit"));
84258
+ say(c.dim(" /freemode \u2014 open conversation & custom tweaks \xB7 /taskmode \u2014 precise \xB7 exit \u2014 quit"));
84159
84259
  say(c.dim(" Or just talk: \u201Cfind cafes in Leeds and build the best two\u201D."));
84160
84260
  say();
84161
84261
  continue;
@@ -84168,6 +84268,8 @@ async function main() {
84168
84268
  else if (verb === "list") await cmdList(argv);
84169
84269
  else if (verb === "build") await cmdBuild(argv);
84170
84270
  else if (verb === "build-all") await cmdBuildAll(argv);
84271
+ else if (verb === "regenerate") await cmdRegenerate(argv);
84272
+ else if (verb === "cms") await cmdCms(argv);
84171
84273
  else if (verb === "book") await cmdBook(argv);
84172
84274
  else if (verb === "outreach") await cmdOutreach(argv);
84173
84275
  } catch (e) {
@@ -48,6 +48,13 @@ you can parse results reliably, then summarise them for the user in prose.
48
48
  renders the progress. One `sproutkit build-all` call is correct; N parallel
49
49
  `sproutkit build` calls via subagents is not.
50
50
 
51
+ - **Rebuild a site** — `sproutkit regenerate <leadId> --instructions "..." --json`
52
+ - Rebuilds an existing site with the requested changes. Instructions required.
53
+
54
+ - **Add the CMS** — `sproutkit cms <leadId> --json`
55
+ - Adds the editable CMS on top of a site Sproutkit already built, so the
56
+ business owner can edit their own content (the claim/on-domain flow).
57
+
51
58
  - **Add a booking site** — `sproutkit book <leadId> --json`
52
59
  - Default: adds an online booking/reservation form onto a site Sproutkit
53
60
  already built for this lead (same domain, at /book).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutkit-code",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Sproutkit in your terminal — find leads, build sites, run outreach, all via chat.",
5
5
  "bin": {
6
6
  "sproutkit": "./dist/index.js"