sproutkit-code 0.17.0 → 0.18.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
@@ -94597,7 +94597,7 @@ var require_package2 = __commonJS({
94597
94597
  "package.json"(exports2, module2) {
94598
94598
  module2.exports = {
94599
94599
  name: "sproutkit-code",
94600
- version: "0.17.0",
94600
+ version: "0.18.0",
94601
94601
  description: "Sproutkit in your terminal \u2014 find leads, build sites, run outreach, all via chat.",
94602
94602
  bin: {
94603
94603
  sproutkit: "./dist/index.js"
@@ -95818,6 +95818,90 @@ async function cmdOutreach(argv) {
95818
95818
  if (result.note) say(c.dim(" " + result.note));
95819
95819
  say();
95820
95820
  }
95821
+ async function cmdPipeline(argv) {
95822
+ const { flags, rest } = parseArgs(argv);
95823
+ const json = !!flags.json;
95824
+ const xArg = rest.find((r) => /^x\d+$/i.test(r));
95825
+ const concurrency = Math.min(
95826
+ Math.max(Number(flags.concurrency ?? (xArg ? xArg.slice(1) : 2)), 1),
95827
+ 4
95828
+ );
95829
+ const withBooking = !!flags.book;
95830
+ const limit = Math.min(Math.max(Number(flags.limit ?? 10), 1), 50);
95831
+ const explicitIds = String(flags.ids ?? "").split(",").map((s) => Number(s.trim())).filter((n) => !Number.isNaN(n) && n > 0);
95832
+ const all = await runTool("list_leads", { limit: 100 }, makeCtx({ json }));
95833
+ const targets = (explicitIds.length ? all.filter((l) => explicitIds.includes(l.id)) : all.filter((l) => !l.site_url)).slice(0, limit);
95834
+ if (targets.length === 0) {
95835
+ const msg = "No leads to run. Find some first, or pass --ids.";
95836
+ json ? emitJson({ ok: false, error: msg }) : say(c.amber(" " + msg));
95837
+ return;
95838
+ }
95839
+ const board = json ? null : new StatusBoard();
95840
+ const rowFor = (t, state, detail) => ({ key: t.id, label: `#${t.id} ${t.name}`, state, detail });
95841
+ if (board) {
95842
+ board.start(
95843
+ `Pipeline for ${targets.length} client${targets.length === 1 ? "" : "s"} (${concurrency} at a time)`,
95844
+ targets.map((t) => rowFor(t, "queued"))
95845
+ );
95846
+ }
95847
+ const results = [];
95848
+ const queue = [...targets];
95849
+ async function runOne(t) {
95850
+ const setRow = (state, detail) => board?.update(rowFor(t, state, detail));
95851
+ setRow("building", "building site\u2026");
95852
+ try {
95853
+ const built = await runTool(
95854
+ "build_site",
95855
+ { id: t.id, skipQa: !!flags["skip-qa"], multipage: !!flags.multipage },
95856
+ makeCtx({ json: true })
95857
+ // suppress per-tool spinners; the board is the UI
95858
+ );
95859
+ if (built && built.ok === false) {
95860
+ setRow("failed", built.error ?? "build failed");
95861
+ results.push({ id: t.id, name: t.name, ok: false, error: built.error });
95862
+ return;
95863
+ }
95864
+ if (withBooking) {
95865
+ setRow("building", "adding booking\u2026");
95866
+ try {
95867
+ await runTool("add_booking_site", { id: t.id }, makeCtx({ json: true }));
95868
+ } catch (e) {
95869
+ setRow("building", `booking skipped (${e.message.slice(0, 40)})`);
95870
+ }
95871
+ }
95872
+ setRow("building", "preparing outreach\u2026");
95873
+ await runTool("prepare_outreach", { ids: [t.id] }, makeCtx({ json: true }));
95874
+ setRow("built", built.url ?? "done");
95875
+ results.push({ id: t.id, name: t.name, ok: true, url: built.url });
95876
+ } catch (e) {
95877
+ setRow("failed", e.message.slice(0, 60));
95878
+ results.push({ id: t.id, name: t.name, ok: false, error: e.message });
95879
+ }
95880
+ }
95881
+ const workers = Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
95882
+ for (; ; ) {
95883
+ const next = queue.shift();
95884
+ if (!next) return;
95885
+ await runOne(next);
95886
+ }
95887
+ });
95888
+ await Promise.all(workers);
95889
+ board?.stop();
95890
+ autoSync();
95891
+ const ok = results.filter((r) => r.ok).length;
95892
+ const failed = results.length - ok;
95893
+ if (json) {
95894
+ emitJson({ ok: failed === 0, ran: results.length, succeeded: ok, failed, concurrency, results });
95895
+ if (failed > 0) process.exitCode = 1;
95896
+ return;
95897
+ }
95898
+ say();
95899
+ say(
95900
+ c.green(` Pipeline done \u2014 ${ok} client${ok === 1 ? "" : "s"} built + outreach prepared`) + (failed ? c.red(`, ${failed} failed`) : "") + c.dim(` (concurrency ${concurrency}).`)
95901
+ );
95902
+ say(c.dim(" Open the message links above, or check emailed leads in your Contacted column."));
95903
+ say();
95904
+ }
95821
95905
  async function cmdRegenerate(argv) {
95822
95906
  const { flags, rest } = parseArgs(argv);
95823
95907
  const json = !!flags.json;
@@ -96023,6 +96107,7 @@ COMMANDS:
96023
96107
  - sproutkit cms <id> --json \u2014 add the editable CMS on top of the site we built them
96024
96108
  - 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)
96025
96109
  - sproutkit outreach --json [--ids 3,5] \u2014 prepare outreach links
96110
+ - sproutkit pipeline x3 --json [--book] [--limit 10] \u2014 full pipeline (build\u2192booking\u2192outreach) for many clients concurrently
96026
96111
 
96027
96112
  If no command fits the request, say so in one line and name the closest thing you can do. Never invent results.`;
96028
96113
  var FREE_SYSTEM_PROMPT = `You are Sproutkit's assistant, talking with the user about their lead-generation and website work. You can run the sproutkit CLI (find, list, build, build-all, book, outreach \u2014 all support --json) via Bash, and you can help with small custom changes and questions. Be conversational and helpful, but stay within Sproutkit's domain (leads, demo sites, booking, outreach). You don't need to collapse everything to a single command here \u2014 it's fine to discuss, then act.`;
@@ -96291,7 +96376,7 @@ async function main() {
96291
96376
  closeTty();
96292
96377
  return;
96293
96378
  }
96294
- if (arg === "find" || arg === "list" || arg === "build" || arg === "build-all" || arg === "regenerate" || arg === "cms" || arg === "book" || arg === "outreach") {
96379
+ if (arg === "find" || arg === "list" || arg === "build" || arg === "build-all" || arg === "regenerate" || arg === "cms" || arg === "book" || arg === "outreach" || arg === "pipeline") {
96295
96380
  const subArgs = process.argv.slice(3);
96296
96381
  const lic2 = await check();
96297
96382
  if (lic2.status !== "active") {
@@ -96309,6 +96394,7 @@ async function main() {
96309
96394
  else if (arg === "regenerate") await cmdRegenerate(subArgs);
96310
96395
  else if (arg === "cms") await cmdCms(subArgs);
96311
96396
  else if (arg === "book") await cmdBook(subArgs);
96397
+ else if (arg === "pipeline") await cmdPipeline(subArgs);
96312
96398
  else await cmdOutreach(subArgs);
96313
96399
  closeTty();
96314
96400
  return;
@@ -96327,6 +96413,7 @@ async function main() {
96327
96413
  say(` ${c.bold("sproutkit cms <id>")} add the editable CMS to a built site`);
96328
96414
  say(` ${c.bold("sproutkit book <id>")} booking site (--standalone for own-URL)`);
96329
96415
  say(` ${c.bold("sproutkit outreach")} prep outreach links (--ids 3,5)`);
96416
+ say(` ${c.bold("sproutkit pipeline x3")} build+outreach many clients concurrently`);
96330
96417
  say(` ${c.bold("sproutkit setup")} set or update keys, licence, backend`);
96331
96418
  say(` ${c.bold("sproutkit doctor")} check everything is wired up`);
96332
96419
  say(` ${c.bold("sproutkit models")} list Claude models and pick one`);
@@ -96399,7 +96486,7 @@ async function main() {
96399
96486
  }
96400
96487
  const direct = line.replace(/^\//, "");
96401
96488
  const verb = direct.split(/\s+/)[0]?.toLowerCase();
96402
- const DIRECT_VERBS = ["find", "list", "build", "build-all", "regenerate", "cms", "book", "outreach", "doctor"];
96489
+ const DIRECT_VERBS = ["find", "list", "build", "build-all", "regenerate", "cms", "book", "outreach", "pipeline", "doctor"];
96403
96490
  if (verb === "commands" || verb === "help" || line === "/help" || line === "/?") {
96404
96491
  say();
96405
96492
  say(c.bold(" Commands (type with or without a leading /):"));
@@ -96412,6 +96499,7 @@ async function main() {
96412
96499
  say(` ${c.greenBold("book")} <id> [--standalone] [--archetype slots|reservation|enquiry] [--email \u2026]`);
96413
96500
  say(` ${c.dim(" ")} booking on the site, or --standalone for its own URL`);
96414
96501
  say(` ${c.greenBold("outreach")} [--ids 3,5] prepare outreach links`);
96502
+ say(` ${c.greenBold("pipeline")} x3 [--book] [--limit 10] build + outreach many, concurrently`);
96415
96503
  say(c.dim(" doctor \u2014 check your setup \xB7 /freemode \u2014 open chat \xB7 /taskmode \u2014 precise \xB7 exit"));
96416
96504
  say(c.dim(" Or just talk: \u201Cfind cafes in Leeds and build the best two\u201D."));
96417
96505
  say();
@@ -96429,6 +96517,7 @@ async function main() {
96429
96517
  else if (verb === "cms") await cmdCms(argv);
96430
96518
  else if (verb === "book") await cmdBook(argv);
96431
96519
  else if (verb === "outreach") await cmdOutreach(argv);
96520
+ else if (verb === "pipeline") await cmdPipeline(argv);
96432
96521
  else if (verb === "doctor") await runDoctor();
96433
96522
  } catch (e) {
96434
96523
  say(c.red(` Error: ${e.message}`));
@@ -66,6 +66,12 @@ you can parse results reliably, then summarise them for the user in prose.
66
66
  - Use `book --standalone` when the user says the client "already has a
67
67
  website" or "just needs a booking page".
68
68
 
69
+ - **Run the full pipeline for many clients** — `sproutkit pipeline x3 --json` (optional `--book`, `--limit 10`, `--ids 3,5`)
70
+ - Builds → (optionally adds booking) → prepares outreach for several clients
71
+ CONCURRENTLY (capped at 4), refilling as each finishes. Use for "run the
72
+ pipeline", "do the whole thing for my leads", "build and message them all".
73
+ - Run `sproutkit find` FIRST — pipeline works through leads you already have.
74
+
69
75
  - **Prepare outreach** — `sproutkit outreach --json` (optional `--ids 3,5,9`)
70
76
  - Per lead: if it has a PHONE, returns a WhatsApp/SMS link for the user to
71
77
  open (not auto-sent); if it has NO phone but an EMAIL (and SMTP is set up),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutkit-code",
3
- "version": "0.17.0",
3
+ "version": "0.18.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"