sproutkit-code 0.6.0 → 0.7.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
@@ -79482,6 +79482,7 @@ var require_builder = __commonJS({
79482
79482
  exports2.buildAndDeploySite = buildAndDeploySite;
79483
79483
  exports2.redeployCmsOnly = redeployCmsOnly;
79484
79484
  exports2.deployBookingOnly = deployBookingOnly2;
79485
+ exports2.deployStandaloneBooking = deployStandaloneBooking2;
79485
79486
  var sdk_1 = __importDefault(require_sdk());
79486
79487
  var axios_1 = __importDefault(require_axios());
79487
79488
  var runtime_1 = require_runtime();
@@ -80372,6 +80373,63 @@ export default defineConfig({ plugins: [react()] });`;
80372
80373
  onProgress(`Live at ${publicUrl}`);
80373
80374
  return { url: publicUrl, bookingUrl: `${publicUrl}/book` };
80374
80375
  }
80376
+ async function deployStandaloneBooking2(args) {
80377
+ const { lead, vercelToken, blobToken, resendKey, ownerEmail, bookingConfig, onProgress } = args;
80378
+ const slug = lead.name.toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 26);
80379
+ const projectName = `sprout-book-${lead.id}-${slug}`;
80380
+ onProgress("Seeding booking config\u2026");
80381
+ try {
80382
+ const { put } = await Promise.resolve().then(() => __importStar(require_dist5()));
80383
+ await put("booking-config.json", JSON.stringify(bookingConfig), {
80384
+ access: "public",
80385
+ addRandomSuffix: false,
80386
+ allowOverwrite: true,
80387
+ contentType: "application/json",
80388
+ token: blobToken
80389
+ });
80390
+ } catch (e) {
80391
+ throw new Error(`Failed to seed booking config: ${e.message}`);
80392
+ }
80393
+ onProgress("Setting booking env vars\u2026");
80394
+ await setProjectEnv(projectName, vercelToken, {
80395
+ RESEND_API_KEY: resendKey,
80396
+ OWNER_EMAIL: ownerEmail,
80397
+ BUSINESS_NAME: lead.name,
80398
+ BLOB_READ_WRITE_TOKEN: blobToken
80399
+ });
80400
+ const files = [
80401
+ { file: "index.html", data: booking_runtime_1.BOOK_INDEX_HTML, encoding: "utf-8" },
80402
+ { file: "app.js", data: booking_runtime_1.BOOK_APP_JS, encoding: "utf-8" },
80403
+ { file: "api/bookings.js", data: booking_runtime_1.API_BOOKINGS_JS, encoding: "utf-8" },
80404
+ { file: "api/booking-config.js", data: booking_runtime_1.API_BOOKING_CONFIG_JS, encoding: "utf-8" },
80405
+ {
80406
+ file: "package.json",
80407
+ data: JSON.stringify({
80408
+ name: "sprout-booking",
80409
+ private: true,
80410
+ version: "0.1.0",
80411
+ type: "module",
80412
+ dependencies: { "@vercel/blob": "^0.23.4" }
80413
+ }),
80414
+ encoding: "utf-8"
80415
+ }
80416
+ ];
80417
+ files[0] = {
80418
+ file: "index.html",
80419
+ data: booking_runtime_1.BOOK_INDEX_HTML.replace(/\/book\/app\.js/g, "/app.js"),
80420
+ encoding: "utf-8"
80421
+ };
80422
+ onProgress("Deploying booking site\u2026");
80423
+ const res = await postVercelDeployment({ name: projectName, files, target: "production" }, vercelToken, onProgress);
80424
+ await axios_1.default.patch(`https://api.vercel.com/v9/projects/${res.data.name}`, { ssoProtection: null }, { headers: { Authorization: `Bearer ${vercelToken}` } }).catch(() => null);
80425
+ const publicUrl = await stableProjectUrl({
80426
+ projectName,
80427
+ vercelToken,
80428
+ fallbackUrl: `https://${res.data.url}`
80429
+ });
80430
+ onProgress(`Live at ${publicUrl}`);
80431
+ return { url: publicUrl, bookingUrl: publicUrl };
80432
+ }
80375
80433
  function hasBookLink(code) {
80376
80434
  return /(href\s*=\s*["'`{]*\s*["'`]?\/book\/?["'`}]?)|(location(?:\.href)?\s*=\s*["'`]\/book["'`])/i.test(code);
80377
80435
  }
@@ -81047,11 +81105,15 @@ var require_chat_tools = __commonJS({
81047
81105
  },
81048
81106
  {
81049
81107
  name: "add_booking_site",
81050
- description: "Add an online booking / reservation form to a lead's EXISTING website (the site must already be built). Sproutkit designs a booking form suited to the business \u2014 appointment slots for barbers/salons, table reservations for restaurants, or an enquiry form \u2014 and deploys it onto the same domain. Use this for requests like 'add a booking system to The Porch' or 'give the barber online appointments'. Pass `instructions` to customise it (services, slot length, party size, occasion, copy) and `archetype` to force a specific style. Costs a Claude call + a Vercel redeploy.",
81108
+ description: "Create an online booking / reservation form for a lead. Sproutkit designs a form suited to the business \u2014 appointment slots for barbers/salons, table reservations for restaurants, or an enquiry form. TWO modes: (1) default \u2014 deploy it onto a website Sproutkit ALREADY built for this lead, on the same domain ('add a booking system to The Porch'); (2) standalone (set standalone=true) \u2014 a self-contained booking site with its OWN url, for a client who already has their own website and just needs a booking page to link to ('build a booking site for this client'). Pass `instructions` to customise (services, slot length, party size, copy) and `archetype` to force a style. Costs a Claude call + a Vercel deploy.",
81051
81109
  input_schema: {
81052
81110
  type: "object",
81053
81111
  properties: {
81054
- id: { type: "number", description: "The lead's id. Its website must already be built." },
81112
+ id: { type: "number", description: "The lead's id." },
81113
+ standalone: {
81114
+ type: "boolean",
81115
+ description: "true = build a standalone booking site with its own URL (no Sproutkit-built website required \u2014 for clients who already have their own site). false/omit = add booking onto a site Sproutkit already built for this lead."
81116
+ },
81055
81117
  instructions: {
81056
81118
  type: "string",
81057
81119
  description: "Optional custom direction for the booking form, in plain English. e.g. '45-minute slots, add a beard trim at \xA312', 'tables up to 12 people, ask for the occasion', 'just a simple enquiry form'. Shapes the deployed form."
@@ -81397,10 +81459,20 @@ Interested?`;
81397
81459
  const lead = db2.get("SELECT site_url FROM leads WHERE id = ?", [id]);
81398
81460
  if (!lead)
81399
81461
  throw new Error(`Lead #${id} not found`);
81462
+ const standalone = input.standalone === true;
81463
+ if (standalone) {
81464
+ const result2 = await ctx.invokeIpc("bookings:provision-standalone", {
81465
+ leadId: id,
81466
+ instructions: input.instructions,
81467
+ archetypeOverride: input.archetype,
81468
+ ownerEmail: input.ownerEmail
81469
+ });
81470
+ return { ok: true, id, standalone: true, ...result2 };
81471
+ }
81400
81472
  if (!lead.site_url) {
81401
81473
  return {
81402
81474
  ok: false,
81403
- error: "This lead has no website yet. Build the site first, then add booking \u2014 bookings live on the same domain."
81475
+ error: "This lead has no Sproutkit-built website. Either build the site first, or set standalone=true to create a booking site with its own URL (for clients who already have their own website)."
81404
81476
  };
81405
81477
  }
81406
81478
  const result = await ctx.invokeIpc("bookings:provision", {
@@ -82384,7 +82456,7 @@ var require_package = __commonJS({
82384
82456
  "package.json"(exports2, module2) {
82385
82457
  module2.exports = {
82386
82458
  name: "sproutkit-code",
82387
- version: "0.6.0",
82459
+ version: "0.7.0",
82388
82460
  description: "Sproutkit in your terminal \u2014 find leads, build sites, run outreach, all via chat.",
82389
82461
  bin: {
82390
82462
  sproutkit: "./dist/index.js"
@@ -82441,6 +82513,7 @@ var startAutoJob = autoMode.startAutoJob;
82441
82513
  var provisionCmsForLead = cms.provisionCmsForLead;
82442
82514
  var generatePreset = bookingPreset.generatePreset;
82443
82515
  var deployBookingOnly = builder.deployBookingOnly;
82516
+ var deployStandaloneBooking = builder.deployStandaloneBooking;
82444
82517
  var refreshModels = models.refreshModels;
82445
82518
  var refreshIfStale = models.refreshIfStale;
82446
82519
  var listCachedModels = models.listCachedModels;
@@ -82988,6 +83061,46 @@ function makeInvokeIpc(emit) {
82988
83061
  emit("bookings:done", { leadId: p.leadId, ...result });
82989
83062
  return result;
82990
83063
  }
83064
+ if (channel === "bookings:provision-standalone") {
83065
+ const p = payload;
83066
+ const db2 = getDb();
83067
+ const lead = db2.get("SELECT * FROM leads WHERE id = ?", [p.leadId]);
83068
+ if (!lead) throw new Error("Lead not found");
83069
+ const vercelToken = getSetting("vercel_token");
83070
+ const blobToken = getSetting("blob_token");
83071
+ const resendKey = getSetting("resend_key");
83072
+ if (!vercelToken) throw new Error("Vercel token not set");
83073
+ if (!blobToken) throw new Error("Vercel Blob token not set");
83074
+ emit("bookings:progress", { leadId: p.leadId, msg: "Designing the booking form\u2026" });
83075
+ const leadInfo = lead;
83076
+ const config = await generatePreset({
83077
+ anthropicKey: getSetting("anthropic_key") ?? "",
83078
+ lead: { name: leadInfo.name, type: leadInfo.type, city: leadInfo.city },
83079
+ instructions: p.instructions,
83080
+ archetypeOverride: p.archetypeOverride
83081
+ });
83082
+ const result = await deployStandaloneBooking({
83083
+ lead,
83084
+ vercelToken,
83085
+ blobToken,
83086
+ resendKey: resendKey || "",
83087
+ ownerEmail: p.ownerEmail ?? "",
83088
+ bookingConfig: config,
83089
+ onProgress: (msg) => emit("bookings:progress", { leadId: p.leadId, msg })
83090
+ });
83091
+ db2.run(
83092
+ "UPDATE leads SET booking_url = ?, owner_email = ?, booking_archetype = ?, booking_config = ? WHERE id = ?",
83093
+ [
83094
+ result.bookingUrl,
83095
+ p.ownerEmail ?? "",
83096
+ config.archetype,
83097
+ JSON.stringify(config),
83098
+ p.leadId
83099
+ ]
83100
+ );
83101
+ emit("bookings:done", { leadId: p.leadId, ...result });
83102
+ return result;
83103
+ }
82991
83104
  throw new Error(`channel not supported in CLI: ${channel}`);
82992
83105
  };
82993
83106
  }
@@ -83520,6 +83633,48 @@ async function cmdOutreach(argv) {
83520
83633
  if (result.noPhone) say(c.dim(` ${result.noPhone} had no phone number and were skipped for messaging.`));
83521
83634
  say();
83522
83635
  }
83636
+ async function cmdBook(argv) {
83637
+ const { flags, rest } = parseArgs(argv);
83638
+ const json = !!flags.json;
83639
+ const id = Number(flags.id ?? rest[0]);
83640
+ if (!id || Number.isNaN(id)) {
83641
+ const err = 'Usage: sproutkit book <leadId> [--standalone] [--instructions "..."] [--email owner@x.com]';
83642
+ json ? emitJson({ ok: false, error: err }) : say(c.red(" " + err));
83643
+ process.exitCode = 1;
83644
+ return;
83645
+ }
83646
+ const standalone = !!flags.standalone;
83647
+ const status = json ? void 0 : new Status();
83648
+ if (status) {
83649
+ status.start(standalone ? `Building standalone booking site for lead #${id}` : `Adding booking to lead #${id}`);
83650
+ }
83651
+ try {
83652
+ const result = await runTool(
83653
+ "add_booking_site",
83654
+ {
83655
+ id,
83656
+ standalone,
83657
+ instructions: flags.instructions ? String(flags.instructions) : void 0,
83658
+ archetype: flags.archetype ? String(flags.archetype) : void 0,
83659
+ ownerEmail: flags.email ? String(flags.email) : void 0
83660
+ },
83661
+ makeCtx({ json, status })
83662
+ );
83663
+ if (result && result.ok === false) {
83664
+ if (status) status.done(false, result.error ?? "Booking failed");
83665
+ if (json) emitJson(result);
83666
+ process.exitCode = 1;
83667
+ return;
83668
+ }
83669
+ if (status) status.done(true, result.bookingUrl ? `Booking live at ${result.bookingUrl}` : `Booking added to lead #${id}`);
83670
+ autoSync();
83671
+ if (json) emitJson(result);
83672
+ } catch (e) {
83673
+ if (status) status.done(false, "Booking failed");
83674
+ json ? emitJson({ ok: false, error: e.message }) : say(c.red(" " + e.message));
83675
+ process.exitCode = 1;
83676
+ }
83677
+ }
83523
83678
  async function cmdBuildAll(argv) {
83524
83679
  const { flags } = parseArgs(argv);
83525
83680
  const json = !!flags.json;
@@ -83619,7 +83774,7 @@ async function main() {
83619
83774
  closeTty();
83620
83775
  return;
83621
83776
  }
83622
- if (arg === "find" || arg === "list" || arg === "build" || arg === "build-all" || arg === "outreach") {
83777
+ if (arg === "find" || arg === "list" || arg === "build" || arg === "build-all" || arg === "book" || arg === "outreach") {
83623
83778
  const subArgs = process.argv.slice(3);
83624
83779
  const lic2 = await check();
83625
83780
  if (lic2.status !== "active") {
@@ -83634,6 +83789,7 @@ async function main() {
83634
83789
  else if (arg === "list") await cmdList(subArgs);
83635
83790
  else if (arg === "build") await cmdBuild(subArgs);
83636
83791
  else if (arg === "build-all") await cmdBuildAll(subArgs);
83792
+ else if (arg === "book") await cmdBook(subArgs);
83637
83793
  else await cmdOutreach(subArgs);
83638
83794
  closeTty();
83639
83795
  return;
@@ -83648,6 +83804,7 @@ async function main() {
83648
83804
  say(` ${c.bold("sproutkit list")} list leads (--status, --city, --json)`);
83649
83805
  say(` ${c.bold("sproutkit build <id>")} build a website (--instructions, --cms)`);
83650
83806
  say(` ${c.bold("sproutkit build-all")} build many at once (--ids, --concurrency 3)`);
83807
+ say(` ${c.bold("sproutkit book <id>")} booking site (--standalone for own-URL)`);
83651
83808
  say(` ${c.bold("sproutkit outreach")} prep outreach links (--ids 3,5)`);
83652
83809
  say(` ${c.bold("sproutkit setup")} set or update keys, licence, backend`);
83653
83810
  say(` ${c.bold("sproutkit models")} list Claude models and pick one`);
@@ -48,6 +48,17 @@ 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
+ - **Add a booking site** — `sproutkit book <leadId> --json`
52
+ - Default: adds an online booking/reservation form onto a site Sproutkit
53
+ already built for this lead (same domain, at /book).
54
+ - `--standalone`: builds a self-contained booking site with its OWN url, for
55
+ a client who already has their own website and just needs a booking page to
56
+ link to. No Sproutkit-built site required.
57
+ - Optional: `--instructions "45-min slots, add beard trim £12"`,
58
+ `--archetype slots|reservation|enquiry`, `--email owner@x.com`.
59
+ - Use `book --standalone` when the user says the client "already has a
60
+ website" or "just needs a booking page".
61
+
51
62
  - **Prepare outreach** — `sproutkit outreach --json` (optional `--ids 3,5,9`)
52
63
  - Fills the message template, moves the leads to Contacted, and returns a
53
64
  link per lead to open in WhatsApp/SMS. It does NOT auto-send — give the user
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutkit-code",
3
- "version": "0.6.0",
3
+ "version": "0.7.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"