sproutkit-code 0.8.0 → 0.10.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 +133 -28
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -187,6 +187,10 @@ var require_db = __commonJS({
|
|
|
187
187
|
db2.exec("ALTER TABLE leads ADD COLUMN dead_at TEXT");
|
|
188
188
|
} catch {
|
|
189
189
|
}
|
|
190
|
+
try {
|
|
191
|
+
db2.exec("ALTER TABLE leads ADD COLUMN site_uid TEXT");
|
|
192
|
+
} catch {
|
|
193
|
+
}
|
|
190
194
|
}
|
|
191
195
|
function getDb2() {
|
|
192
196
|
return db2;
|
|
@@ -32728,6 +32732,12 @@ var require_cms_runtime = __commonJS({
|
|
|
32728
32732
|
exports2.hashPassword = hashPassword;
|
|
32729
32733
|
exports2.generateSessionSecret = generateSessionSecret;
|
|
32730
32734
|
exports2.API_CONTENT_JS = `import { list, put } from "@vercel/blob";
|
|
32735
|
+
|
|
32736
|
+
// Per-site Blob namespace so many CMS sites share one store (one token per
|
|
32737
|
+
// agency) without overwriting each other's content. Empty when SITE_ID unset \u2192
|
|
32738
|
+
// legacy sites keep the flat content.json key, so nothing deployed breaks.
|
|
32739
|
+
const PFX = process.env.SITE_ID ? "sites/" + process.env.SITE_ID + "/" : "";
|
|
32740
|
+
const CONTENT_KEY = PFX + "content.json";
|
|
32731
32741
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
32732
32742
|
|
|
32733
32743
|
// Runs on Node.js (not Edge) because @vercel/blob's undici dependency
|
|
@@ -32795,8 +32805,8 @@ function sanitise(raw) {
|
|
|
32795
32805
|
|
|
32796
32806
|
async function readContent() {
|
|
32797
32807
|
try {
|
|
32798
|
-
const { blobs } = await list({ prefix:
|
|
32799
|
-
const target = blobs.find((b) => b.pathname ===
|
|
32808
|
+
const { blobs } = await list({ prefix: CONTENT_KEY, limit: 1 });
|
|
32809
|
+
const target = blobs.find((b) => b.pathname === CONTENT_KEY);
|
|
32800
32810
|
if (!target) return SAFE_DEFAULTS;
|
|
32801
32811
|
const res = await fetch(target.url);
|
|
32802
32812
|
if (!res.ok) return SAFE_DEFAULTS;
|
|
@@ -32864,7 +32874,7 @@ export default async function handler(req, res) {
|
|
|
32864
32874
|
body = raw ? JSON.parse(raw) : null;
|
|
32865
32875
|
}
|
|
32866
32876
|
const clean = sanitise(body);
|
|
32867
|
-
await put(
|
|
32877
|
+
await put(CONTENT_KEY, JSON.stringify(clean), {
|
|
32868
32878
|
access: "public",
|
|
32869
32879
|
addRandomSuffix: false,
|
|
32870
32880
|
allowOverwrite: true,
|
|
@@ -32988,6 +32998,10 @@ export default function handler(req, res) {
|
|
|
32988
32998
|
|
|
32989
32999
|
export const config = { runtime: "nodejs", api: { bodyParser: false } };
|
|
32990
33000
|
|
|
33001
|
+
// Per-site namespace so uploaded photos don't collide across sites sharing one
|
|
33002
|
+
// Blob store. Empty when SITE_ID unset (legacy sites keep flat photos/ path).
|
|
33003
|
+
const PFX = process.env.SITE_ID ? "sites/" + process.env.SITE_ID + "/" : "";
|
|
33004
|
+
|
|
32991
33005
|
async function verifySession(req) {
|
|
32992
33006
|
const cookie = req.headers.cookie || "";
|
|
32993
33007
|
const match = cookie.match(/sk_admin=([^;]+)/);
|
|
@@ -33036,7 +33050,7 @@ export default async function handler(req, res) {
|
|
|
33036
33050
|
res.status(413).json({ error: "too_large" });
|
|
33037
33051
|
return;
|
|
33038
33052
|
}
|
|
33039
|
-
const name = "photos/" + Date.now() + "-" + Math.random().toString(36).slice(2, 8) + ".webp";
|
|
33053
|
+
const name = PFX + "photos/" + Date.now() + "-" + Math.random().toString(36).slice(2, 8) + ".webp";
|
|
33040
33054
|
const blob = await put(name, buf, {
|
|
33041
33055
|
access: "public",
|
|
33042
33056
|
addRandomSuffix: false,
|
|
@@ -33804,6 +33818,13 @@ import { createHmac, timingSafeEqual } from "node:crypto";
|
|
|
33804
33818
|
|
|
33805
33819
|
export const config = { runtime: "nodejs" };
|
|
33806
33820
|
|
|
33821
|
+
// Per-site Blob namespace: every site isolates its files under sites/<id>/ so
|
|
33822
|
+
// many booking sites can share ONE Blob store (one token per agency) without
|
|
33823
|
+
// overwriting each other. Empty when SITE_ID is unset \u2192 older sites keep
|
|
33824
|
+
// reading/writing the legacy flat keys, so nothing already deployed breaks.
|
|
33825
|
+
const PFX = process.env.SITE_ID ? "sites/" + process.env.SITE_ID + "/" : "";
|
|
33826
|
+
const CONFIG_KEY = PFX + "booking-config.json";
|
|
33827
|
+
|
|
33807
33828
|
const SAFE_DEFAULTS = {
|
|
33808
33829
|
archetype: "enquiry",
|
|
33809
33830
|
services: [],
|
|
@@ -33856,8 +33877,8 @@ function sanitise(raw) {
|
|
|
33856
33877
|
|
|
33857
33878
|
async function readConfig() {
|
|
33858
33879
|
try {
|
|
33859
|
-
const { blobs } = await list({ prefix:
|
|
33860
|
-
const target = blobs.find((b) => b.pathname ===
|
|
33880
|
+
const { blobs } = await list({ prefix: CONFIG_KEY, limit: 1 });
|
|
33881
|
+
const target = blobs.find((b) => b.pathname === CONFIG_KEY);
|
|
33861
33882
|
if (!target) return SAFE_DEFAULTS;
|
|
33862
33883
|
const res = await fetch(target.url);
|
|
33863
33884
|
if (!res.ok) return SAFE_DEFAULTS;
|
|
@@ -33912,7 +33933,7 @@ export default async function handler(req, res) {
|
|
|
33912
33933
|
body = raw ? JSON.parse(raw) : null;
|
|
33913
33934
|
}
|
|
33914
33935
|
const clean = sanitise(body);
|
|
33915
|
-
await put(
|
|
33936
|
+
await put(CONFIG_KEY, JSON.stringify(clean), {
|
|
33916
33937
|
access: "public", addRandomSuffix: false, allowOverwrite: true,
|
|
33917
33938
|
contentType: "application/json",
|
|
33918
33939
|
});
|
|
@@ -33930,6 +33951,10 @@ import { createHmac, timingSafeEqual } from "node:crypto";
|
|
|
33930
33951
|
|
|
33931
33952
|
export const config = { runtime: "nodejs" };
|
|
33932
33953
|
|
|
33954
|
+
// Per-site namespace (see booking-config.js). Empty when SITE_ID unset.
|
|
33955
|
+
const PFX = process.env.SITE_ID ? "sites/" + process.env.SITE_ID + "/" : "";
|
|
33956
|
+
const BOOKINGS_KEY = PFX + "bookings.jsonl";
|
|
33957
|
+
|
|
33933
33958
|
function verifySession(req) {
|
|
33934
33959
|
const cookie = req.headers.cookie || "";
|
|
33935
33960
|
const match = cookie.match(/sk_admin=([^;]+)/);
|
|
@@ -33959,8 +33984,8 @@ function readBody(req) {
|
|
|
33959
33984
|
|
|
33960
33985
|
async function readAllBookings() {
|
|
33961
33986
|
try {
|
|
33962
|
-
const { blobs } = await list({ prefix:
|
|
33963
|
-
const target = blobs.find((b) => b.pathname ===
|
|
33987
|
+
const { blobs } = await list({ prefix: BOOKINGS_KEY, limit: 1 });
|
|
33988
|
+
const target = blobs.find((b) => b.pathname === BOOKINGS_KEY);
|
|
33964
33989
|
if (!target) return [];
|
|
33965
33990
|
const res = await fetch(target.url);
|
|
33966
33991
|
if (!res.ok) return [];
|
|
@@ -33975,7 +34000,7 @@ async function readAllBookings() {
|
|
|
33975
34000
|
|
|
33976
34001
|
async function writeAllBookings(bookings) {
|
|
33977
34002
|
const text = bookings.map((b) => JSON.stringify(b)).join("\\n");
|
|
33978
|
-
await put(
|
|
34003
|
+
await put(BOOKINGS_KEY, text, {
|
|
33979
34004
|
access: "public", addRandomSuffix: false, allowOverwrite: true,
|
|
33980
34005
|
contentType: "application/x-ndjson",
|
|
33981
34006
|
});
|
|
@@ -79485,7 +79510,9 @@ var require_builder = __commonJS({
|
|
|
79485
79510
|
exports2.deployStandaloneBooking = deployStandaloneBooking2;
|
|
79486
79511
|
var sdk_1 = __importDefault(require_sdk());
|
|
79487
79512
|
var axios_1 = __importDefault(require_axios());
|
|
79513
|
+
var crypto_1 = require("crypto");
|
|
79488
79514
|
var runtime_1 = require_runtime();
|
|
79515
|
+
var db_1 = require_db();
|
|
79489
79516
|
var llm_1 = require_llm();
|
|
79490
79517
|
var fs3 = __importStar(require("fs"));
|
|
79491
79518
|
var path3 = __importStar(require("path"));
|
|
@@ -79493,6 +79520,15 @@ var require_builder = __commonJS({
|
|
|
79493
79520
|
var cms_template_1 = require_cms_template();
|
|
79494
79521
|
var cms_runtime_1 = require_cms_runtime();
|
|
79495
79522
|
var booking_runtime_1 = require_booking_runtime();
|
|
79523
|
+
function siteUidFor(leadId) {
|
|
79524
|
+
const db2 = (0, db_1.getDb)();
|
|
79525
|
+
const row = db2.get("SELECT site_uid FROM leads WHERE id = ?", [leadId]);
|
|
79526
|
+
if (row?.site_uid)
|
|
79527
|
+
return row.site_uid;
|
|
79528
|
+
const uid = (0, crypto_1.randomUUID)();
|
|
79529
|
+
db2.run("UPDATE leads SET site_uid = ? WHERE id = ?", [uid, leadId]);
|
|
79530
|
+
return uid;
|
|
79531
|
+
}
|
|
79496
79532
|
var STOCK_HOSTS = [
|
|
79497
79533
|
"unsplash.com",
|
|
79498
79534
|
"images.unsplash.com",
|
|
@@ -80058,6 +80094,7 @@ export default defineConfig({ plugins: [react()] });`;
|
|
|
80058
80094
|
devDependencies: { "@vitejs/plugin-react": "^4.3.1", vite: "^5.4.2" }
|
|
80059
80095
|
}) : packageJson;
|
|
80060
80096
|
if (includeCms) {
|
|
80097
|
+
const cmsSiteUid = siteUidFor(lead.id);
|
|
80061
80098
|
onProgress(reuseAdminPassword ? "Reusing existing credentials\u2026" : "Generating admin credentials\u2026");
|
|
80062
80099
|
const adminPassword = reuseAdminPassword ?? (0, cms_runtime_1.generateFriendlyPassword)();
|
|
80063
80100
|
const adminHash = await (0, cms_runtime_1.hashPassword)(adminPassword);
|
|
@@ -80092,7 +80129,7 @@ export default defineConfig({ plugins: [react()] });`;
|
|
|
80092
80129
|
onProgress("Seeding CMS content\u2026");
|
|
80093
80130
|
try {
|
|
80094
80131
|
const { put } = await Promise.resolve().then(() => __importStar(require_dist5()));
|
|
80095
|
-
await put(
|
|
80132
|
+
await put(`sites/${cmsSiteUid}/content.json`, JSON.stringify(initialContent), {
|
|
80096
80133
|
access: "public",
|
|
80097
80134
|
addRandomSuffix: false,
|
|
80098
80135
|
allowOverwrite: true,
|
|
@@ -80107,6 +80144,9 @@ export default defineConfig({ plugins: [react()] });`;
|
|
|
80107
80144
|
onProgress("Setting CMS env vars\u2026");
|
|
80108
80145
|
await setProjectEnv(projectName, vercelToken, {
|
|
80109
80146
|
BLOB_READ_WRITE_TOKEN: blobToken,
|
|
80147
|
+
// Namespaces this site's Blob files so it never collides with other
|
|
80148
|
+
// sites sharing the same store/token.
|
|
80149
|
+
SITE_ID: cmsSiteUid,
|
|
80110
80150
|
ADMIN_PASSWORD_HASH: adminHash,
|
|
80111
80151
|
SESSION_SECRET: sessionSecret,
|
|
80112
80152
|
BUSINESS_NAME: lead.name
|
|
@@ -80224,12 +80264,16 @@ export default defineConfig({ plugins: [react()] });`;
|
|
|
80224
80264
|
throw new Error("Couldn't fetch src/App.jsx from the existing deployment. Try Regenerate website first, then Redeploy CMS.");
|
|
80225
80265
|
}
|
|
80226
80266
|
onProgress("Refreshing CMS env vars\u2026");
|
|
80227
|
-
|
|
80267
|
+
const existingUid = (0, db_1.getDb)().get("SELECT site_uid FROM leads WHERE id = ?", [lead.id])?.site_uid;
|
|
80268
|
+
const cmsEnv = {
|
|
80228
80269
|
BLOB_READ_WRITE_TOKEN: blobToken,
|
|
80229
80270
|
ADMIN_PASSWORD_HASH: adminHash,
|
|
80230
80271
|
SESSION_SECRET: sessionSecret,
|
|
80231
80272
|
BUSINESS_NAME: lead.name
|
|
80232
|
-
}
|
|
80273
|
+
};
|
|
80274
|
+
if (existingUid)
|
|
80275
|
+
cmsEnv.SITE_ID = existingUid;
|
|
80276
|
+
await setProjectEnv(projectName, vercelToken, cmsEnv);
|
|
80233
80277
|
onProgress("Deploying\u2026");
|
|
80234
80278
|
const res = await postVercelDeployment({
|
|
80235
80279
|
name: projectName,
|
|
@@ -80293,10 +80337,12 @@ createRoot(document.getElementById('root')).render(<StrictMode><App /></StrictMo
|
|
|
80293
80337
|
import react from '@vitejs/plugin-react';
|
|
80294
80338
|
export default defineConfig({ plugins: [react()] });`;
|
|
80295
80339
|
files.push({ file: "index.html", data: indexHtml, encoding: "utf-8" }, { file: "src/main.jsx", data: mainJsx, encoding: "utf-8" }, { file: "vite.config.js", data: viteConfig, encoding: "utf-8" });
|
|
80340
|
+
const bookingExistingUid = (0, db_1.getDb)().get("SELECT site_uid FROM leads WHERE id = ?", [lead.id])?.site_uid;
|
|
80341
|
+
const bookingKey = bookingExistingUid ? `sites/${bookingExistingUid}/booking-config.json` : "booking-config.json";
|
|
80296
80342
|
onProgress("Seeding booking config into Blob\u2026");
|
|
80297
80343
|
try {
|
|
80298
80344
|
const { put } = await Promise.resolve().then(() => __importStar(require_dist5()));
|
|
80299
|
-
await put(
|
|
80345
|
+
await put(bookingKey, JSON.stringify(bookingConfig), {
|
|
80300
80346
|
access: "public",
|
|
80301
80347
|
addRandomSuffix: false,
|
|
80302
80348
|
allowOverwrite: true,
|
|
@@ -80307,19 +80353,25 @@ export default defineConfig({ plugins: [react()] });`;
|
|
|
80307
80353
|
throw new Error(`Failed to seed booking config: ${e.message}`);
|
|
80308
80354
|
}
|
|
80309
80355
|
onProgress("Setting booking env vars\u2026");
|
|
80310
|
-
|
|
80356
|
+
const bookingEnv = {
|
|
80311
80357
|
RESEND_API_KEY: resendKey,
|
|
80312
80358
|
OWNER_EMAIL: ownerEmail,
|
|
80313
80359
|
BUSINESS_NAME: lead.name
|
|
80314
|
-
}
|
|
80360
|
+
};
|
|
80361
|
+
if (bookingExistingUid)
|
|
80362
|
+
bookingEnv.SITE_ID = bookingExistingUid;
|
|
80363
|
+
await setProjectEnv(projectName, vercelToken, bookingEnv);
|
|
80315
80364
|
if (lead.admin_password) {
|
|
80316
80365
|
onProgress("Refreshing admin session env\u2026");
|
|
80317
80366
|
const adminHash = await (0, cms_runtime_1.hashPassword)(lead.admin_password);
|
|
80318
|
-
|
|
80367
|
+
const adminEnv = {
|
|
80319
80368
|
BLOB_READ_WRITE_TOKEN: blobToken,
|
|
80320
80369
|
ADMIN_PASSWORD_HASH: adminHash,
|
|
80321
80370
|
SESSION_SECRET: (0, cms_runtime_1.generateSessionSecret)()
|
|
80322
|
-
}
|
|
80371
|
+
};
|
|
80372
|
+
if (bookingExistingUid)
|
|
80373
|
+
adminEnv.SITE_ID = bookingExistingUid;
|
|
80374
|
+
await setProjectEnv(projectName, vercelToken, adminEnv);
|
|
80323
80375
|
}
|
|
80324
80376
|
const packageJsonBooking = JSON.stringify({
|
|
80325
80377
|
name: "sprout-site",
|
|
@@ -80378,9 +80430,10 @@ export default defineConfig({ plugins: [react()] });`;
|
|
|
80378
80430
|
const slug = lead.name.toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 26);
|
|
80379
80431
|
const projectName = `sprout-book-${lead.id}-${slug}`;
|
|
80380
80432
|
onProgress("Seeding booking config\u2026");
|
|
80433
|
+
const standaloneSiteUid = siteUidFor(lead.id);
|
|
80381
80434
|
try {
|
|
80382
80435
|
const { put } = await Promise.resolve().then(() => __importStar(require_dist5()));
|
|
80383
|
-
await put(
|
|
80436
|
+
await put(`sites/${standaloneSiteUid}/booking-config.json`, JSON.stringify(bookingConfig), {
|
|
80384
80437
|
access: "public",
|
|
80385
80438
|
addRandomSuffix: false,
|
|
80386
80439
|
allowOverwrite: true,
|
|
@@ -80395,7 +80448,8 @@ export default defineConfig({ plugins: [react()] });`;
|
|
|
80395
80448
|
RESEND_API_KEY: resendKey,
|
|
80396
80449
|
OWNER_EMAIL: ownerEmail,
|
|
80397
80450
|
BUSINESS_NAME: lead.name,
|
|
80398
|
-
BLOB_READ_WRITE_TOKEN: blobToken
|
|
80451
|
+
BLOB_READ_WRITE_TOKEN: blobToken,
|
|
80452
|
+
SITE_ID: standaloneSiteUid
|
|
80399
80453
|
});
|
|
80400
80454
|
const files = [
|
|
80401
80455
|
{ file: "index.html", data: booking_runtime_1.BOOK_INDEX_HTML, encoding: "utf-8" },
|
|
@@ -82456,7 +82510,7 @@ var require_package = __commonJS({
|
|
|
82456
82510
|
"package.json"(exports2, module2) {
|
|
82457
82511
|
module2.exports = {
|
|
82458
82512
|
name: "sproutkit-code",
|
|
82459
|
-
version: "0.
|
|
82513
|
+
version: "0.10.0",
|
|
82460
82514
|
description: "Sproutkit in your terminal \u2014 find leads, build sites, run outreach, all via chat.",
|
|
82461
82515
|
bin: {
|
|
82462
82516
|
sproutkit: "./dist/index.js"
|
|
@@ -82529,7 +82583,8 @@ var REQUIRED_KEYS = [
|
|
|
82529
82583
|
{ key: "anthropic_key", label: "Anthropic API key", hint: "sk-ant-\u2026 \u2014 powers the chat + site generation", required: true, apiBackendOnly: true },
|
|
82530
82584
|
{ key: "places_key", label: "Google Places API key", hint: "finds businesses, photos, reviews", required: true },
|
|
82531
82585
|
{ key: "vercel_token", label: "Vercel token", hint: "deploys the generated sites", required: true },
|
|
82532
|
-
{ key: "blob_token", label: "Vercel Blob token", hint: "
|
|
82586
|
+
{ key: "blob_token", label: "Vercel Blob token", hint: "required for booking sites & CMS (stores form config/submissions)", required: false },
|
|
82587
|
+
{ key: "resend_key", label: "Resend API key", hint: "optional \u2014 emails the business owner when a booking comes in", required: false }
|
|
82533
82588
|
];
|
|
82534
82589
|
function getSetting(key) {
|
|
82535
82590
|
const row = getDb().get("SELECT value FROM settings WHERE key = ?", [key]);
|
|
@@ -83029,7 +83084,7 @@ function makeInvokeIpc(emit) {
|
|
|
83029
83084
|
const resendKey = getSetting("resend_key");
|
|
83030
83085
|
if (!anthropicKey) throw new Error("Anthropic API key not set");
|
|
83031
83086
|
if (!vercelToken) throw new Error("Vercel token not set");
|
|
83032
|
-
if (!blobToken) throw new Error("Vercel Blob token
|
|
83087
|
+
if (!blobToken) throw new Error("Booking sites need a Vercel Blob token (stores the form config & submissions). Add one with 'sproutkit setup' \u2014 create a Blob store at vercel.com, copy its read-write token.");
|
|
83033
83088
|
emit("bookings:progress", { leadId: p.leadId, msg: "Designing the booking form\u2026" });
|
|
83034
83089
|
const leadInfo = lead;
|
|
83035
83090
|
const config = await generatePreset({
|
|
@@ -83070,7 +83125,7 @@ function makeInvokeIpc(emit) {
|
|
|
83070
83125
|
const blobToken = getSetting("blob_token");
|
|
83071
83126
|
const resendKey = getSetting("resend_key");
|
|
83072
83127
|
if (!vercelToken) throw new Error("Vercel token not set");
|
|
83073
|
-
if (!blobToken) throw new Error("Vercel Blob token
|
|
83128
|
+
if (!blobToken) throw new Error("Booking sites need a Vercel Blob token (stores the form config & submissions). Add one with 'sproutkit setup' \u2014 create a Blob store at vercel.com, copy its read-write token.");
|
|
83074
83129
|
emit("bookings:progress", { leadId: p.leadId, msg: "Designing the booking form\u2026" });
|
|
83075
83130
|
const leadInfo = lead;
|
|
83076
83131
|
const config = await generatePreset({
|
|
@@ -83742,8 +83797,36 @@ async function cmdBuildAll(argv) {
|
|
|
83742
83797
|
|
|
83743
83798
|
// src/claude-bridge.ts
|
|
83744
83799
|
var import_child_process3 = require("child_process");
|
|
83800
|
+
var TASK_SYSTEM_PROMPT = `You operate the "sproutkit" CLI on the user's behalf. Sproutkit finds local businesses without websites, builds them demo sites, adds booking, and prepares outreach.
|
|
83801
|
+
|
|
83802
|
+
HOW TO ACT \u2014 this is strict:
|
|
83803
|
+
- Fulfil each request by running the matching sproutkit command(s) via Bash. Nothing else.
|
|
83804
|
+
- Do NOT read, list, or explore the repo. Do NOT narrate a plan, think out loud, or look for a skill. No preamble, no "let me\u2026". Either ask a crisp question (below) or run the command.
|
|
83805
|
+
- Always pass --json and parse it. When an action completes, report the outcome in ONE short line (what happened + the URL/result).
|
|
83806
|
+
- Get lead ids from \`sproutkit list --json\` \u2014 never guess an id.
|
|
83807
|
+
|
|
83808
|
+
GATHER REQUIRED CHOICES FIRST \u2014 ask, don't guess, don't chatter:
|
|
83809
|
+
When a request is missing information the command needs, ask a SHORT, STRUCTURED question that lists the options \u2014 then wait. Present choices as a tight numbered/bulleted list, no rambling. Specifically:
|
|
83810
|
+
- Which business/lead is not clear \u2192 run \`sproutkit list --json\`, then ask: "Which business?" and list a few (id + name).
|
|
83811
|
+
- "make/build a booking site" \u2192 you MUST establish the booking TYPE before running. Ask: "What type of booking site?" and present exactly:
|
|
83812
|
+
1. Slot-based \u2014 timed appointments (barbers, salons, clinics)
|
|
83813
|
+
2. Reservation \u2014 table / party booking (restaurants, cafes)
|
|
83814
|
+
3. Enquiry \u2014 a simple request/contact form
|
|
83815
|
+
Also ask whether it should be standalone (its own URL, for a client who already has a website) or added to a Sproutkit-built site. Then run \`sproutkit book <id> --archetype <slots|reservation|enquiry> [--standalone] --json\`.
|
|
83816
|
+
- Once you have the needed choices, act immediately \u2014 one command, one line of result. Don't re-confirm what they already told you.
|
|
83817
|
+
|
|
83818
|
+
COMMANDS:
|
|
83819
|
+
- sproutkit find "<area>" --categories "<type>,<type>" --json \u2014 find leads
|
|
83820
|
+
- sproutkit list --json [--status <s>] [--city <c>] \u2014 list leads (get ids)
|
|
83821
|
+
- sproutkit build <id> --json [--instructions "..."] [--cms] \u2014 build a demo site
|
|
83822
|
+
- sproutkit build-all --json [--ids 3,5] [--concurrency 3] \u2014 build many at once
|
|
83823
|
+
- 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)
|
|
83824
|
+
- sproutkit outreach --json [--ids 3,5] \u2014 prepare outreach links
|
|
83825
|
+
|
|
83826
|
+
If no command fits the request, say so in one line and name the closest thing you can do. Never invent results.`;
|
|
83827
|
+
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.`;
|
|
83745
83828
|
function runClaudeTurn(args) {
|
|
83746
|
-
const { message, sessionId, write } = args;
|
|
83829
|
+
const { message, sessionId, write, freeMode } = args;
|
|
83747
83830
|
const timeoutMs = args.timeoutMs ?? 6e5;
|
|
83748
83831
|
const cliArgs = [
|
|
83749
83832
|
"-p",
|
|
@@ -83752,6 +83835,11 @@ function runClaudeTurn(args) {
|
|
|
83752
83835
|
"--include-partial-messages",
|
|
83753
83836
|
"--verbose",
|
|
83754
83837
|
// required for stream_event emission
|
|
83838
|
+
// Inject the operating instructions directly so behaviour is precise and
|
|
83839
|
+
// doesn't depend on flaky skill discovery. Task mode pins exact commands;
|
|
83840
|
+
// free mode allows open conversation.
|
|
83841
|
+
"--append-system-prompt",
|
|
83842
|
+
freeMode ? FREE_SYSTEM_PROMPT : TASK_SYSTEM_PROMPT,
|
|
83755
83843
|
// Let Claude drive the sproutkit CLI without hanging on a permission
|
|
83756
83844
|
// prompt, but ONLY the sproutkit command — nothing else runs unattended.
|
|
83757
83845
|
"--allowedTools",
|
|
@@ -83950,20 +84038,37 @@ async function main() {
|
|
|
83950
84038
|
if (bridged && !skillInstalled()) {
|
|
83951
84039
|
say(c.amber(" Tip: run 'sproutkit setup' to install the Sproutkit skill so Claude can find/build/book for you."));
|
|
83952
84040
|
}
|
|
83953
|
-
|
|
83954
|
-
|
|
83955
|
-
);
|
|
84041
|
+
if (bridged) {
|
|
84042
|
+
say(c.dim(" Talk to Sproutkit (powered by your Claude subscription). Type a request, or 'exit'."));
|
|
84043
|
+
say(c.dim(" It runs the right action precisely. Type '/freemode' for open conversation + custom tweaks."));
|
|
84044
|
+
} else {
|
|
84045
|
+
say(c.dim(" Chat with Sproutkit. Type a request, or 'exit' to quit."));
|
|
84046
|
+
}
|
|
83956
84047
|
say();
|
|
83957
84048
|
let claudeSession = null;
|
|
84049
|
+
let freeMode = false;
|
|
83958
84050
|
while (true) {
|
|
83959
84051
|
const line = await ask(c.greenBold("sproutkit \u203A "));
|
|
83960
84052
|
if (!line) continue;
|
|
83961
84053
|
if (line === "exit" || line === "quit") break;
|
|
84054
|
+
if (bridged && (line === "/freemode" || line === "/free")) {
|
|
84055
|
+
freeMode = true;
|
|
84056
|
+
say(c.amber(" Free mode ON \u2014 open conversation. You can ask for small custom changes. '/taskmode' to switch back."));
|
|
84057
|
+
say();
|
|
84058
|
+
continue;
|
|
84059
|
+
}
|
|
84060
|
+
if (bridged && (line === "/taskmode" || line === "/task")) {
|
|
84061
|
+
freeMode = false;
|
|
84062
|
+
say(c.green(" Task mode ON \u2014 precise actions."));
|
|
84063
|
+
say();
|
|
84064
|
+
continue;
|
|
84065
|
+
}
|
|
83962
84066
|
try {
|
|
83963
84067
|
if (bridged) {
|
|
83964
84068
|
const r = await runClaudeTurn({
|
|
83965
84069
|
message: line,
|
|
83966
84070
|
sessionId: claudeSession,
|
|
84071
|
+
freeMode,
|
|
83967
84072
|
write: (t) => process.stdout.write(t)
|
|
83968
84073
|
});
|
|
83969
84074
|
say();
|