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 +162 -5
- package/dist/skill/SKILL.md +11 -0
- package/package.json +1 -1
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: "
|
|
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.
|
|
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
|
|
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.
|
|
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`);
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -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
|