sproutkit-code 1.11.1 → 1.13.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 +272 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -97,7 +97,8 @@ var require_runtime = __commonJS({
|
|
|
97
97
|
"report_key",
|
|
98
98
|
"smtp_pass",
|
|
99
99
|
"imap_pass",
|
|
100
|
-
"stripe_secret_key"
|
|
100
|
+
"stripe_secret_key",
|
|
101
|
+
"elevenlabs_key"
|
|
101
102
|
]);
|
|
102
103
|
function isSecretSetting(key) {
|
|
103
104
|
return SECRET_SETTINGS.has(key);
|
|
@@ -394,6 +395,30 @@ var require_db = __commonJS({
|
|
|
394
395
|
handed_over_at TEXT
|
|
395
396
|
);
|
|
396
397
|
`);
|
|
398
|
+
for (const col of [
|
|
399
|
+
"domain_choice TEXT",
|
|
400
|
+
"domain_price_pence INTEGER",
|
|
401
|
+
"domain_cost_usd REAL",
|
|
402
|
+
"domain_status TEXT",
|
|
403
|
+
"domain_order_id TEXT"
|
|
404
|
+
]) {
|
|
405
|
+
try {
|
|
406
|
+
db2.exec(`ALTER TABLE leads ADD COLUMN ${col}`);
|
|
407
|
+
} catch {
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
for (const col of ["pitch_video_url TEXT", "pitch_poster_url TEXT", "pitch_script TEXT"]) {
|
|
411
|
+
try {
|
|
412
|
+
db2.exec(`ALTER TABLE leads ADD COLUMN ${col}`);
|
|
413
|
+
} catch {
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
for (const col of ["domain TEXT", "domain_pence INTEGER NOT NULL DEFAULT 0"]) {
|
|
417
|
+
try {
|
|
418
|
+
db2.exec(`ALTER TABLE deals ADD COLUMN ${col}`);
|
|
419
|
+
} catch {
|
|
420
|
+
}
|
|
421
|
+
}
|
|
397
422
|
}
|
|
398
423
|
function getDb2() {
|
|
399
424
|
return db2;
|
|
@@ -30655,7 +30680,7 @@ var require_llm = __commonJS({
|
|
|
30655
30680
|
};
|
|
30656
30681
|
if (args.allowWebSearch) {
|
|
30657
30682
|
req.tools = [
|
|
30658
|
-
{ type: "web_search_20250305", name: "web_search", max_uses: 3 }
|
|
30683
|
+
{ type: "web_search_20250305", name: "web_search", max_uses: args.maxSearches ?? 3 }
|
|
30659
30684
|
];
|
|
30660
30685
|
}
|
|
30661
30686
|
const res = await anthropic.messages.create(req);
|
|
@@ -81964,7 +81989,7 @@ var require_builder = __commonJS({
|
|
|
81964
81989
|
throw new Error("CMS is enabled but no Vercel Blob token is set. Add it in Settings \u2192 Vercel Blob token.");
|
|
81965
81990
|
}
|
|
81966
81991
|
let details = { reviews: [], photoNames: [], hours: [], priceLevel: null };
|
|
81967
|
-
if (lead.place_id && placesKey) {
|
|
81992
|
+
if (lead.place_id && !lead.place_id.startsWith("ai:") && placesKey) {
|
|
81968
81993
|
onProgress("Fetching reviews and photos from Google...");
|
|
81969
81994
|
details = await fetchPlaceDetails(lead.place_id, placesKey);
|
|
81970
81995
|
}
|
|
@@ -84268,6 +84293,199 @@ var require_lead_search = __commonJS({
|
|
|
84268
84293
|
}
|
|
84269
84294
|
});
|
|
84270
84295
|
|
|
84296
|
+
// ../desktop/dist/main/lib/ai-search.js
|
|
84297
|
+
var require_ai_search = __commonJS({
|
|
84298
|
+
"../desktop/dist/main/lib/ai-search.js"(exports2) {
|
|
84299
|
+
"use strict";
|
|
84300
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
84301
|
+
exports2.isAiPlaceId = isAiPlaceId;
|
|
84302
|
+
exports2.aiSearchLeads = aiSearchLeads;
|
|
84303
|
+
var crypto_1 = require("crypto");
|
|
84304
|
+
var db_1 = require_db();
|
|
84305
|
+
var llm_1 = require_llm();
|
|
84306
|
+
var models_1 = require_models();
|
|
84307
|
+
var places_1 = require_places();
|
|
84308
|
+
function isAiPlaceId(id) {
|
|
84309
|
+
return !!id && id.startsWith("ai:");
|
|
84310
|
+
}
|
|
84311
|
+
function aiPlaceId(name, city) {
|
|
84312
|
+
const key = `${name}|${city}`.toLowerCase().replace(/[^a-z0-9|]/g, "");
|
|
84313
|
+
return "ai:" + (0, crypto_1.createHash)("sha1").update(key).digest("hex").slice(0, 16);
|
|
84314
|
+
}
|
|
84315
|
+
function simplify(s) {
|
|
84316
|
+
return s.toLowerCase().replace(/&/g, "and").replace(/[^a-z0-9]/g, "");
|
|
84317
|
+
}
|
|
84318
|
+
function sameBusiness(a, b) {
|
|
84319
|
+
const x = simplify(a), y = simplify(b);
|
|
84320
|
+
return !!x && !!y && (x.includes(y) || y.includes(x));
|
|
84321
|
+
}
|
|
84322
|
+
function parseFound(text) {
|
|
84323
|
+
const body = text.match(/<leads>\s*([\s\S]*?)\s*<\/leads>/)?.[1] ?? text.match(/\[[\s\S]*\]/)?.[0];
|
|
84324
|
+
if (!body)
|
|
84325
|
+
return [];
|
|
84326
|
+
try {
|
|
84327
|
+
const raw = JSON.parse(body.replace(/^```[a-z]*\s*/i, "").replace(/\s*```$/, ""));
|
|
84328
|
+
if (!Array.isArray(raw))
|
|
84329
|
+
return [];
|
|
84330
|
+
return raw.map((r) => ({
|
|
84331
|
+
name: String(r?.name ?? "").trim(),
|
|
84332
|
+
type: String(r?.type ?? "").trim(),
|
|
84333
|
+
address: String(r?.address ?? "").trim(),
|
|
84334
|
+
phone: String(r?.phone ?? "").trim(),
|
|
84335
|
+
source_url: String(r?.source_url ?? "").trim()
|
|
84336
|
+
})).filter((r) => r.name.length > 1);
|
|
84337
|
+
} catch {
|
|
84338
|
+
return [];
|
|
84339
|
+
}
|
|
84340
|
+
}
|
|
84341
|
+
async function aiSearchLeads(args, onLead) {
|
|
84342
|
+
const db2 = (0, db_1.getDb)();
|
|
84343
|
+
const cap = Math.max(1, Math.min(args.maxResults ?? 20, 40));
|
|
84344
|
+
const progress = args.onProgress ?? (() => {
|
|
84345
|
+
});
|
|
84346
|
+
const kinds = args.types.length ? args.types.join(", ") : "local service businesses";
|
|
84347
|
+
progress(`Searching the web for ${kinds} in ${args.city}\u2026`);
|
|
84348
|
+
const prompt = `Find real, currently trading local businesses in ${args.city} that do NOT have a website of their own. Types: ${kinds}.
|
|
84349
|
+
|
|
84350
|
+
Search the web: business directories (Yell, Thomson Local, Checkatrade, Bark, FreeIndex, Scoot), Facebook and Instagram business pages, Google Maps listings, local community pages. A business that only has a Facebook page, Instagram, or a directory listing counts. A business with its own domain (e.g. kingscuts.co.uk), a Wix/Squarespace site, or a page on a franchise or chain's site does NOT count \u2014 leave it out. Leave out national chains.
|
|
84351
|
+
|
|
84352
|
+
Find up to ${cap}. Only include a business you actually saw on a page during this search, and give that page's URL. Copy the phone number and address exactly as shown; use "" if the page doesn't show one. Never guess or invent details.
|
|
84353
|
+
|
|
84354
|
+
Reply with only this, no other text:
|
|
84355
|
+
<leads>
|
|
84356
|
+
[{ "name": "", "type": "one of: ${kinds}", "address": "", "phone": "", "source_url": "" }]
|
|
84357
|
+
</leads>`;
|
|
84358
|
+
const res = await (0, llm_1.generateText)({
|
|
84359
|
+
apiKey: args.anthropicKey,
|
|
84360
|
+
prompt,
|
|
84361
|
+
model: (0, models_1.getPreferredModel)(),
|
|
84362
|
+
maxTokens: 8e3,
|
|
84363
|
+
allowWebSearch: true,
|
|
84364
|
+
maxSearches: 10,
|
|
84365
|
+
timeoutMs: 42e4
|
|
84366
|
+
});
|
|
84367
|
+
const found = parseFound(res.text ?? "");
|
|
84368
|
+
if (found.length === 0) {
|
|
84369
|
+
return {
|
|
84370
|
+
found: 0,
|
|
84371
|
+
saved: 0,
|
|
84372
|
+
errors: [res.text ? "AI search didn't return any businesses. Try a bigger town or different business types." : "AI search returned nothing."]
|
|
84373
|
+
};
|
|
84374
|
+
}
|
|
84375
|
+
const existing = db2.all("SELECT name, city, place_id FROM leads", []);
|
|
84376
|
+
const known = new Set(existing.map((e) => e.place_id).filter(Boolean));
|
|
84377
|
+
const inPipeline = (name) => existing.some((e) => sameBusiness(e.name, name) && (!e.city || sameBusiness(e.city, args.city)));
|
|
84378
|
+
let saved = 0, withWebsite = 0, alreadyInPipeline = 0;
|
|
84379
|
+
for (const f of found) {
|
|
84380
|
+
if (saved >= cap)
|
|
84381
|
+
break;
|
|
84382
|
+
if (inPipeline(f.name)) {
|
|
84383
|
+
alreadyInPipeline++;
|
|
84384
|
+
continue;
|
|
84385
|
+
}
|
|
84386
|
+
let lead = {
|
|
84387
|
+
place_id: aiPlaceId(f.name, args.city),
|
|
84388
|
+
name: f.name,
|
|
84389
|
+
type: f.type || args.types[0] || "local business",
|
|
84390
|
+
address: f.address,
|
|
84391
|
+
phone: f.phone,
|
|
84392
|
+
rating: null,
|
|
84393
|
+
review_count: null,
|
|
84394
|
+
lat: null,
|
|
84395
|
+
lng: null,
|
|
84396
|
+
price_level: null,
|
|
84397
|
+
business_status: null,
|
|
84398
|
+
has_hours: false
|
|
84399
|
+
};
|
|
84400
|
+
if (args.placesKey) {
|
|
84401
|
+
progress(`Checking ${f.name} on Google\u2026`);
|
|
84402
|
+
const g = await (0, places_1.findOneBusiness)({
|
|
84403
|
+
apiKey: args.placesKey,
|
|
84404
|
+
query: `${f.name} ${f.address || args.city}`
|
|
84405
|
+
});
|
|
84406
|
+
if (g && sameBusiness(g.name, f.name)) {
|
|
84407
|
+
if (g.website) {
|
|
84408
|
+
withWebsite++;
|
|
84409
|
+
continue;
|
|
84410
|
+
}
|
|
84411
|
+
lead = { ...lead, ...g, type: lead.type, phone: g.phone || f.phone, address: g.address || f.address };
|
|
84412
|
+
}
|
|
84413
|
+
}
|
|
84414
|
+
if (known.has(lead.place_id)) {
|
|
84415
|
+
alreadyInPipeline++;
|
|
84416
|
+
continue;
|
|
84417
|
+
}
|
|
84418
|
+
if (lead.lat == null && lead.address) {
|
|
84419
|
+
try {
|
|
84420
|
+
const [hit] = await (0, places_1.geocodeForward)(lead.address, 1);
|
|
84421
|
+
if (hit) {
|
|
84422
|
+
lead.lat = hit.lat;
|
|
84423
|
+
lead.lng = hit.lng;
|
|
84424
|
+
}
|
|
84425
|
+
} catch {
|
|
84426
|
+
}
|
|
84427
|
+
await new Promise((r) => setTimeout(r, 1100));
|
|
84428
|
+
}
|
|
84429
|
+
known.add(lead.place_id);
|
|
84430
|
+
const score = (0, places_1.scoreLead)({
|
|
84431
|
+
rating: lead.rating,
|
|
84432
|
+
review_count: lead.review_count,
|
|
84433
|
+
price_level: lead.price_level,
|
|
84434
|
+
phone: lead.phone,
|
|
84435
|
+
has_hours: lead.has_hours,
|
|
84436
|
+
business_status: lead.business_status
|
|
84437
|
+
});
|
|
84438
|
+
db2.run(`INSERT INTO leads
|
|
84439
|
+
(place_id, name, type, city, address, phone, website, rating, review_count, lat, lng, score, price_level, business_status, status, lead_kind, notes)
|
|
84440
|
+
VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, 'discovered', 'no_website', ?)`, [
|
|
84441
|
+
lead.place_id,
|
|
84442
|
+
lead.name,
|
|
84443
|
+
lead.type,
|
|
84444
|
+
args.city,
|
|
84445
|
+
lead.address,
|
|
84446
|
+
lead.phone,
|
|
84447
|
+
lead.rating,
|
|
84448
|
+
lead.review_count,
|
|
84449
|
+
lead.lat,
|
|
84450
|
+
lead.lng,
|
|
84451
|
+
score,
|
|
84452
|
+
lead.price_level,
|
|
84453
|
+
lead.business_status,
|
|
84454
|
+
f.source_url ? `Found by AI search: ${f.source_url}` : "Found by AI search"
|
|
84455
|
+
]);
|
|
84456
|
+
const row = db2.get("SELECT id FROM leads WHERE place_id = ?", [lead.place_id]);
|
|
84457
|
+
saved++;
|
|
84458
|
+
onLead?.({ id: row?.id ?? 0, place_id: lead.place_id, name: lead.name, type: lead.type, city: args.city, website: null });
|
|
84459
|
+
}
|
|
84460
|
+
return { found: found.length, saved, withWebsite, alreadyInPipeline };
|
|
84461
|
+
}
|
|
84462
|
+
}
|
|
84463
|
+
});
|
|
84464
|
+
|
|
84465
|
+
// ../desktop/dist/main/lib/pitch-text.js
|
|
84466
|
+
var require_pitch_text = __commonJS({
|
|
84467
|
+
"../desktop/dist/main/lib/pitch-text.js"(exports2) {
|
|
84468
|
+
"use strict";
|
|
84469
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
84470
|
+
exports2.withVideo = withVideo;
|
|
84471
|
+
function withVideo(message, videoUrl) {
|
|
84472
|
+
if (!videoUrl)
|
|
84473
|
+
return message.replace(/\n?[^\n]*\{video\}[^\n]*/g, "");
|
|
84474
|
+
if (message.includes("{video}"))
|
|
84475
|
+
return message.replace(/\{video\}/g, videoUrl);
|
|
84476
|
+
const line = `I also recorded a short video walking you through it: ${videoUrl}`;
|
|
84477
|
+
const lines = message.split("\n");
|
|
84478
|
+
const at = lines.findIndex((l) => /https?:\/\//.test(l));
|
|
84479
|
+
if (at < 0)
|
|
84480
|
+
return `${message}
|
|
84481
|
+
|
|
84482
|
+
${line}`;
|
|
84483
|
+
lines.splice(at + 1, 0, "", line);
|
|
84484
|
+
return lines.join("\n");
|
|
84485
|
+
}
|
|
84486
|
+
}
|
|
84487
|
+
});
|
|
84488
|
+
|
|
84271
84489
|
// ../desktop/dist/main/lib/followup.js
|
|
84272
84490
|
var require_followup = __commonJS({
|
|
84273
84491
|
"../desktop/dist/main/lib/followup.js"(exports2) {
|
|
@@ -97090,6 +97308,8 @@ var require_chat_tools = __commonJS({
|
|
|
97090
97308
|
var runtime_1 = require_runtime();
|
|
97091
97309
|
var builder_1 = require_builder();
|
|
97092
97310
|
var lead_search_1 = require_lead_search();
|
|
97311
|
+
var ai_search_1 = require_ai_search();
|
|
97312
|
+
var pitch_text_1 = require_pitch_text();
|
|
97093
97313
|
var places_1 = require_places();
|
|
97094
97314
|
var booking_detect_1 = require_booking_detect();
|
|
97095
97315
|
var site_audit_1 = require_site_audit();
|
|
@@ -97135,6 +97355,24 @@ var require_chat_tools = __commonJS({
|
|
|
97135
97355
|
},
|
|
97136
97356
|
requiresConfirmation: true
|
|
97137
97357
|
},
|
|
97358
|
+
{
|
|
97359
|
+
name: "ai_search_leads",
|
|
97360
|
+
description: "AI search: Claude searches the web (directories, Facebook/Instagram pages, Yell, Checkatrade) for local businesses with NO website, instead of Google Places. Use when the user asks for AI search, has no Google Places key, or Places found too few. Slower (1\u20133 minutes), runs on the user's Claude plan, max 40 per run. With a Places key set, each result is checked on Google and dropped if it has a website. Saves new leads like find_leads.",
|
|
97361
|
+
input_schema: {
|
|
97362
|
+
type: "object",
|
|
97363
|
+
properties: {
|
|
97364
|
+
location: { type: "string", description: "Town, city, postcode or area, e.g. 'Cardiff'." },
|
|
97365
|
+
categories: {
|
|
97366
|
+
type: "array",
|
|
97367
|
+
items: { type: "string" },
|
|
97368
|
+
description: "Business types, e.g. ['plumber', 'barber shop']."
|
|
97369
|
+
},
|
|
97370
|
+
maxResults: { type: "number", description: "Maximum leads to save (default 20, max 40)." }
|
|
97371
|
+
},
|
|
97372
|
+
required: ["location", "categories"]
|
|
97373
|
+
},
|
|
97374
|
+
requiresConfirmation: true
|
|
97375
|
+
},
|
|
97138
97376
|
{
|
|
97139
97377
|
name: "list_leads",
|
|
97140
97378
|
description: "List all leads, optionally filtered by status (discovered, building, site_built, contacted, replied, won, dead) or city. Returns id, name, status, type, city, phone, site_url.",
|
|
@@ -97661,6 +97899,22 @@ If you'd rather not hear from me again, just reply and let me know.`;
|
|
|
97661
97899
|
note: result.saved === 0 && result.found > 0 ? "All matches were already in your pipeline \u2014 nothing new saved." : void 0
|
|
97662
97900
|
};
|
|
97663
97901
|
}
|
|
97902
|
+
case "ai_search_leads": {
|
|
97903
|
+
const location = String(input.location ?? "").trim();
|
|
97904
|
+
const categories = input.categories ?? [];
|
|
97905
|
+
if (!location)
|
|
97906
|
+
return { ok: false, error: "A location is required." };
|
|
97907
|
+
const result = await (0, ai_search_1.aiSearchLeads)({
|
|
97908
|
+
anthropicKey: getDecryptedSetting("anthropic_key"),
|
|
97909
|
+
placesKey: getDecryptedSetting("places_key"),
|
|
97910
|
+
city: location,
|
|
97911
|
+
types: categories,
|
|
97912
|
+
maxResults: Math.min(Number(input.maxResults ?? 20), 40),
|
|
97913
|
+
onProgress: (msg) => ctx.emitProgress?.({ channel: "leads:searching", payload: { msg } })
|
|
97914
|
+
}, (lead) => ctx.emitProgress?.({ channel: "leads:found", payload: { lead } }));
|
|
97915
|
+
ctx.emitProgress?.({ channel: "leads:changed", payload: { reason: "ai_search_leads", ...result } });
|
|
97916
|
+
return { ...result, location, categories, source: "ai" };
|
|
97917
|
+
}
|
|
97664
97918
|
case "list_leads": {
|
|
97665
97919
|
const status = input.status;
|
|
97666
97920
|
const city = input.city;
|
|
@@ -97753,12 +98007,12 @@ If you'd rather not hear from me again, just reply and let me know.`;
|
|
|
97753
98007
|
}
|
|
97754
98008
|
case "prepare_outreach": {
|
|
97755
98009
|
const explicit = input.ids;
|
|
97756
|
-
const rows = explicit && explicit.length > 0 ? db2.all(`SELECT id, name, phone, email, address, city, site_url, lead_kind, booking_platform, booking_url FROM leads WHERE id IN (${explicit.map(() => "?").join(",")})`, explicit) : db2.all(
|
|
98010
|
+
const rows = explicit && explicit.length > 0 ? db2.all(`SELECT id, name, phone, email, address, city, site_url, lead_kind, booking_platform, booking_url, pitch_video_url FROM leads WHERE id IN (${explicit.map(() => "?").join(",")})`, explicit) : db2.all(
|
|
97757
98011
|
// Eligible = we have something to show them: either a demo site
|
|
97758
98012
|
// we built (site_url) OR a booking page we built (booking_url).
|
|
97759
98013
|
// Booking leads never get a site_url, so requiring it silently
|
|
97760
98014
|
// excluded every one-shot booking customer.
|
|
97761
|
-
"SELECT id, name, phone, email, address, city, site_url, lead_kind, booking_platform, booking_url FROM leads WHERE status IN ('site_built','discovered') AND ((site_url IS NOT NULL AND site_url != '') OR (booking_url IS NOT NULL AND booking_url != '')) ORDER BY score DESC NULLS LAST, id DESC",
|
|
98015
|
+
"SELECT id, name, phone, email, address, city, site_url, lead_kind, booking_platform, booking_url, pitch_video_url FROM leads WHERE status IN ('site_built','discovered') AND ((site_url IS NOT NULL AND site_url != '') OR (booking_url IS NOT NULL AND booking_url != '')) ORDER BY score DESC NULLS LAST, id DESC",
|
|
97762
98016
|
[]
|
|
97763
98017
|
);
|
|
97764
98018
|
const eligible = rows.filter((r) => r.site_url || r.booking_url);
|
|
@@ -97780,7 +98034,16 @@ If you'd rather not hear from me again, just reply and let me know.`;
|
|
|
97780
98034
|
let harvested = 0;
|
|
97781
98035
|
for (const r of eligible) {
|
|
97782
98036
|
const isBookingLead = r.lead_kind === "no_booking";
|
|
97783
|
-
|
|
98037
|
+
let videoUrl = r.pitch_video_url ?? null;
|
|
98038
|
+
if (!isBookingLead && !videoUrl && r.site_url && getDecryptedSetting("pitch_auto") === "1") {
|
|
98039
|
+
ctx.emitProgress?.({ channel: "outreach:progress", payload: { msg: `Recording a video for ${r.name}\u2026` } });
|
|
98040
|
+
try {
|
|
98041
|
+
videoUrl = (await ctx.invokeIpc("pitch:make", r.id)).url;
|
|
98042
|
+
} catch {
|
|
98043
|
+
videoUrl = null;
|
|
98044
|
+
}
|
|
98045
|
+
}
|
|
98046
|
+
const message = (0, pitch_text_1.withVideo)((isBookingLead ? bookingTemplate : template).replace(/\{name\}/g, r.name).replace(/\{site_url\}/g, (isBookingLead ? r.booking_url : r.site_url) ?? r.site_url ?? "").replace(/\{booking_how\}/g, r.booking_platform || "WhatsApp/phone"), isBookingLead ? null : videoUrl);
|
|
97784
98047
|
const e164 = toE164(r.phone ?? "");
|
|
97785
98048
|
if (!r.email && smtp && (preferEmail || !e164)) {
|
|
97786
98049
|
ctx.emitProgress?.({
|
|
@@ -98308,7 +98571,7 @@ Tools available:
|
|
|
98308
98571
|
- list_leads, get_lead, summarise_pipeline (read-only)
|
|
98309
98572
|
- move_lead, whatsapp_message (low-risk writes)
|
|
98310
98573
|
- prepare_outreach (fills the template, moves leads to Contacted, returns links to open)
|
|
98311
|
-
- find_leads, build_site, build_all_sites, regenerate_site, add_booking_site, provision_cms, delete_lead (spend money or change things \u2014 require confirmation)
|
|
98574
|
+
- find_leads, ai_search_leads, build_site, build_all_sites, regenerate_site, add_booking_site, provision_cms, delete_lead (spend money or change things \u2014 require confirmation)
|
|
98312
98575
|
|
|
98313
98576
|
When the user asks you to do something, prefer USING the tools to describing how. Examples:
|
|
98314
98577
|
- "How many leads do I have in Won?" \u2192 call summarise_pipeline
|
|
@@ -98340,7 +98603,7 @@ Choosing between build_site and build_all_sites:
|
|
|
98340
98603
|
The user may send messages that begin with a slash command, e.g. "/find-leads barber shops in Shoreditch", "/build Ada's Barber Shop", "/build *", "/pipeline". Treat these as ordinary instructions and map them to the matching tool. "/build *" means build everything unbuilt \u2192 build_all_sites. If a slash command arrives with no arguments, ask for the missing detail rather than guessing.
|
|
98341
98604
|
|
|
98342
98605
|
Confirmation rules:
|
|
98343
|
-
- For tools that spend money or change things (find_leads, build_site, build_all_sites, regenerate_site, add_booking_site, provision_cms, delete_lead) the system pauses and asks the user to click Confirm. Don't ask "shall I?" \u2014 just call the tool, the UI handles confirmation.
|
|
98606
|
+
- For tools that spend money or change things (find_leads, ai_search_leads, build_site, build_all_sites, regenerate_site, add_booking_site, provision_cms, delete_lead) the system pauses and asks the user to click Confirm. Don't ask "shall I?" \u2014 just call the tool, the UI handles confirmation.
|
|
98344
98607
|
- For non-destructive tools, run them immediately.
|
|
98345
98608
|
- Do mention the rough cost when it's relevant: lead searches cost about \xA30.025 per category, a site build about \xA30.02 in Anthropic credits.
|
|
98346
98609
|
|
|
@@ -99791,7 +100054,7 @@ var require_package2 = __commonJS({
|
|
|
99791
100054
|
"package.json"(exports2, module2) {
|
|
99792
100055
|
module2.exports = {
|
|
99793
100056
|
name: "sproutkit-code",
|
|
99794
|
-
version: "1.
|
|
100057
|
+
version: "1.13.0",
|
|
99795
100058
|
description: "Sproutkit in your terminal \u2014 find leads, build sites, run outreach, all via chat.",
|
|
99796
100059
|
bin: {
|
|
99797
100060
|
sproutkit: "./dist/index.js"
|