sproutkit-code 0.10.0 → 0.12.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.
Files changed (2) hide show
  1. package/dist/index.js +142 -30
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -80731,29 +80731,39 @@ var require_places = __commonJS({
80731
80731
  }
80732
80732
  var COST_PER_REQUEST_GBP = 0.025;
80733
80733
  async function searchOneType(args) {
80734
- const body = {
80735
- textQuery: `${args.type} in ${args.city}`,
80736
- maxResultCount: 20
80737
- };
80738
- if (args.center && args.radiusMeters) {
80739
- body.locationBias = {
80740
- circle: {
80741
- center: { latitude: args.center.lat, longitude: args.center.lng },
80742
- radius: args.radiusMeters
80743
- }
80734
+ const all = [];
80735
+ let pageToken;
80736
+ for (let page = 0; page < 3; page++) {
80737
+ const body = {
80738
+ textQuery: `${args.type} in ${args.city}`,
80739
+ maxResultCount: 20
80744
80740
  };
80741
+ if (args.center && args.radiusMeters) {
80742
+ body.locationBias = {
80743
+ circle: {
80744
+ center: { latitude: args.center.lat, longitude: args.center.lng },
80745
+ radius: args.radiusMeters
80746
+ }
80747
+ };
80748
+ }
80749
+ if (pageToken)
80750
+ body.pageToken = pageToken;
80751
+ try {
80752
+ const r = await axios_1.default.post("https://places.googleapis.com/v1/places:searchText", body, {
80753
+ headers: {
80754
+ "X-Goog-Api-Key": args.apiKey,
80755
+ "X-Goog-FieldMask": "nextPageToken,places.id,places.displayName,places.formattedAddress,places.nationalPhoneNumber,places.websiteUri,places.rating,places.userRatingCount,places.location,places.priceLevel,places.businessStatus,places.regularOpeningHours"
80756
+ }
80757
+ });
80758
+ all.push(...r.data.places ?? []);
80759
+ pageToken = r.data.nextPageToken;
80760
+ if (!pageToken)
80761
+ break;
80762
+ } catch {
80763
+ break;
80764
+ }
80745
80765
  }
80746
- try {
80747
- const r = await axios_1.default.post("https://places.googleapis.com/v1/places:searchText", body, {
80748
- headers: {
80749
- "X-Goog-Api-Key": args.apiKey,
80750
- "X-Goog-FieldMask": "places.id,places.displayName,places.formattedAddress,places.nationalPhoneNumber,places.websiteUri,places.rating,places.userRatingCount,places.location,places.priceLevel,places.businessStatus,places.regularOpeningHours"
80751
- }
80752
- });
80753
- return { type: args.type, places: r.data.places ?? [] };
80754
- } catch {
80755
- return { type: args.type, places: [] };
80756
- }
80766
+ return { type: args.type, places: all };
80757
80767
  }
80758
80768
  function processRawPlaces(type, places, center, radiusMeters) {
80759
80769
  const out = [];
@@ -81022,7 +81032,7 @@ var require_chat_tools = __commonJS({
81022
81032
  },
81023
81033
  radiusMeters: {
81024
81034
  type: "number",
81025
- description: "Search radius around the location in metres (default 3000). Larger radius = more leads but less focused."
81035
+ description: "Search radius around the location in metres (default 10000 = 10km). Use a larger radius for big cities where central businesses mostly have websites; smaller (e.g. 3000) for a tight local search."
81026
81036
  }
81027
81037
  },
81028
81038
  required: ["location", "categories"]
@@ -81327,7 +81337,11 @@ Interested?`;
81327
81337
  types: categories,
81328
81338
  maxResults: Math.min(Number(input.maxResults ?? 25), 100),
81329
81339
  center: place ? { lat: place.lat, lng: place.lng } : void 0,
81330
- radiusMeters: Number(input.radiusMeters ?? 3e3)
81340
+ // Default widened from 3km → 10km. A 3km circle in a website-
81341
+ // saturated city centre (e.g. central London) yields almost no
81342
+ // no-website leads; 10km covers a realistic catchment. Caller/model
81343
+ // can still override for a tighter or wider search.
81344
+ radiusMeters: Number(input.radiusMeters ?? 1e4)
81331
81345
  },
81332
81346
  // Emit each lead the instant it's saved, so the UI/CLI streams results
81333
81347
  // in rather than waiting for every category to finish.
@@ -82505,12 +82519,53 @@ Return this exact shape:
82505
82519
  }
82506
82520
  });
82507
82521
 
82522
+ // ../desktop/dist/main/lib/vercel-store.js
82523
+ var require_vercel_store = __commonJS({
82524
+ "../desktop/dist/main/lib/vercel-store.js"(exports2) {
82525
+ "use strict";
82526
+ var __importDefault = exports2 && exports2.__importDefault || function(mod) {
82527
+ return mod && mod.__esModule ? mod : { "default": mod };
82528
+ };
82529
+ Object.defineProperty(exports2, "__esModule", { value: true });
82530
+ exports2.createBlobStore = createBlobStore2;
82531
+ var axios_1 = __importDefault(require_axios());
82532
+ async function createBlobStore2(args) {
82533
+ const name = (args.name || "sproutkit").slice(0, 70);
82534
+ const url = "https://api.vercel.com/storage/stores/blob" + (args.teamId ? `?teamId=${encodeURIComponent(args.teamId)}` : "");
82535
+ try {
82536
+ const res = await axios_1.default.post(url, { name }, {
82537
+ headers: {
82538
+ Authorization: `Bearer ${args.vercelToken}`,
82539
+ "Content-Type": "application/json"
82540
+ },
82541
+ timeout: 3e4
82542
+ });
82543
+ const store = res.data?.store ?? {};
82544
+ const storeId = store.id || store.storeId;
82545
+ return {
82546
+ ok: true,
82547
+ storeId,
82548
+ dashboardUrl: "https://vercel.com/dashboard/stores"
82549
+ };
82550
+ } catch (e) {
82551
+ const err = e;
82552
+ const status = err.response?.status;
82553
+ const msg = err.response?.data?.error?.message ?? err.message;
82554
+ if (status === 409 || /already|exists|conflict/i.test(msg)) {
82555
+ return { ok: false, alreadyExists: true, error: msg };
82556
+ }
82557
+ return { ok: false, error: msg };
82558
+ }
82559
+ }
82560
+ }
82561
+ });
82562
+
82508
82563
  // package.json
82509
82564
  var require_package = __commonJS({
82510
82565
  "package.json"(exports2, module2) {
82511
82566
  module2.exports = {
82512
82567
  name: "sproutkit-code",
82513
- version: "0.10.0",
82568
+ version: "0.12.0",
82514
82569
  description: "Sproutkit in your terminal \u2014 find leads, build sites, run outreach, all via chat.",
82515
82570
  bin: {
82516
82571
  sproutkit: "./dist/index.js"
@@ -82557,6 +82612,7 @@ var autoMode = require_auto_mode();
82557
82612
  var cms = require_cms_provision();
82558
82613
  var bookingPreset = require_booking_preset();
82559
82614
  var builder = require_builder();
82615
+ var vercelStore = require_vercel_store();
82560
82616
  var models = require_models();
82561
82617
  var llm = require_llm();
82562
82618
  var initDatabase = db.initDatabase;
@@ -82568,6 +82624,7 @@ var provisionCmsForLead = cms.provisionCmsForLead;
82568
82624
  var generatePreset = bookingPreset.generatePreset;
82569
82625
  var deployBookingOnly = builder.deployBookingOnly;
82570
82626
  var deployStandaloneBooking = builder.deployStandaloneBooking;
82627
+ var createBlobStore = vercelStore.createBlobStore;
82571
82628
  var refreshModels = models.refreshModels;
82572
82629
  var refreshIfStale = models.refreshIfStale;
82573
82630
  var listCachedModels = models.listCachedModels;
@@ -83029,6 +83086,25 @@ async function runOnboarding(force = false) {
83029
83086
  }
83030
83087
 
83031
83088
  // src/invoke-ipc.ts
83089
+ async function requireBlobToken(vercelToken, emit, leadId) {
83090
+ const existing = getSetting("blob_token");
83091
+ if (existing) return existing;
83092
+ emit("bookings:progress", { leadId, msg: "No Blob token yet \u2014 creating a Blob store for you\u2026" });
83093
+ const created = await createBlobStore({ vercelToken, name: "sproutkit" });
83094
+ if (created.ok) {
83095
+ throw new Error(
83096
+ "Created a Vercel Blob store for you \u2713 \u2014 one last one-time step: open " + (created.dashboardUrl || "https://vercel.com/dashboard/stores") + " , open the 'sproutkit' store, copy its BLOB_READ_WRITE_TOKEN, then run 'sproutkit setup' and paste it. You only do this once \u2014 it's reused for every booking site."
83097
+ );
83098
+ }
83099
+ if (created.alreadyExists) {
83100
+ throw new Error(
83101
+ "You already have a Blob store. Open https://vercel.com/dashboard/stores , copy its BLOB_READ_WRITE_TOKEN, and run 'sproutkit setup' to paste it (one-time \u2014 reused for all sites)."
83102
+ );
83103
+ }
83104
+ throw new Error(
83105
+ `Booking needs a Vercel Blob token and I couldn't auto-create a store (${created.error ?? "unknown"}). Create one at https://vercel.com/dashboard/stores (Create \u2192 Blob), copy BLOB_READ_WRITE_TOKEN, then 'sproutkit setup'.`
83106
+ );
83107
+ }
83032
83108
  function makeInvokeIpc(emit) {
83033
83109
  return async (channel, payload) => {
83034
83110
  if (channel === "auto:build:start") {
@@ -83080,11 +83156,10 @@ function makeInvokeIpc(emit) {
83080
83156
  if (!lead) throw new Error("Lead not found");
83081
83157
  const anthropicKey = getSetting("anthropic_key");
83082
83158
  const vercelToken = getSetting("vercel_token");
83083
- const blobToken = getSetting("blob_token");
83084
83159
  const resendKey = getSetting("resend_key");
83085
83160
  if (!anthropicKey) throw new Error("Anthropic API key not set");
83086
83161
  if (!vercelToken) throw new Error("Vercel token not set");
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.");
83162
+ const blobToken = await requireBlobToken(vercelToken, emit, p.leadId);
83088
83163
  emit("bookings:progress", { leadId: p.leadId, msg: "Designing the booking form\u2026" });
83089
83164
  const leadInfo = lead;
83090
83165
  const config = await generatePreset({
@@ -83122,10 +83197,9 @@ function makeInvokeIpc(emit) {
83122
83197
  const lead = db2.get("SELECT * FROM leads WHERE id = ?", [p.leadId]);
83123
83198
  if (!lead) throw new Error("Lead not found");
83124
83199
  const vercelToken = getSetting("vercel_token");
83125
- const blobToken = getSetting("blob_token");
83126
83200
  const resendKey = getSetting("resend_key");
83127
83201
  if (!vercelToken) throw new Error("Vercel token not set");
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.");
83202
+ const blobToken = await requireBlobToken(vercelToken, emit, p.leadId);
83129
83203
  emit("bookings:progress", { leadId: p.leadId, msg: "Designing the booking form\u2026" });
83130
83204
  const leadInfo = lead;
83131
83205
  const config = await generatePreset({
@@ -83802,8 +83876,14 @@ var TASK_SYSTEM_PROMPT = `You operate the "sproutkit" CLI on the user's behalf.
83802
83876
  HOW TO ACT \u2014 this is strict:
83803
83877
  - Fulfil each request by running the matching sproutkit command(s) via Bash. Nothing else.
83804
83878
  - 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.
83879
+ - Always pass --json and parse it. Get lead ids from \`sproutkit list --json\` \u2014 never guess an id.
83880
+
83881
+ OUTPUT FORMAT \u2014 terse, scannable, NO editorialising:
83882
+ - Never write rambling sentences or commentary about the results ("well short of 20, so the source is returning few matches" \u2014 DON'T). Just present the data.
83883
+ - For find: one line "Found N (M new)", then one line per NEW lead: "#<id> <name> \xB7 <type>".
83884
+ - For list: one line per lead "#<id> <name> \xB7 <type> \xB7 <city> \xB7 <status>" (cap at ~15, then "\u2026and K more").
83885
+ - For build/book: one line "\u2713 <name> \u2014 <url>".
83886
+ - Use a plain "#<id> <name>" style. No tables, no fluff, no "Here's what I found". If a result count is low, state the number plainly \u2014 do NOT speculate about why unless asked.
83807
83887
 
83808
83888
  GATHER REQUIRED CHOICES FIRST \u2014 ask, don't guess, don't chatter:
83809
83889
  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:
@@ -84063,6 +84143,38 @@ async function main() {
84063
84143
  say();
84064
84144
  continue;
84065
84145
  }
84146
+ const direct = line.replace(/^\//, "");
84147
+ const verb = direct.split(/\s+/)[0]?.toLowerCase();
84148
+ const DIRECT_VERBS = ["find", "list", "build", "build-all", "book", "outreach"];
84149
+ if (verb === "commands" || verb === "help" || line === "/help" || line === "/?") {
84150
+ say();
84151
+ say(c.bold(" Commands (type with or without a leading /):"));
84152
+ say(` ${c.greenBold("find")} "<area>" --categories "cafe,barber" find leads`);
84153
+ say(` ${c.greenBold("list")} [--status <s>] [--city <c>] list leads`);
84154
+ say(` ${c.greenBold("build")} <id> [--instructions "\u2026"] [--cms] build a demo site`);
84155
+ say(` ${c.greenBold("build-all")} [--ids 3,5] [--concurrency 3] build many at once`);
84156
+ say(` ${c.greenBold("book")} <id> [--standalone] [--archetype \u2026] booking site`);
84157
+ say(` ${c.greenBold("outreach")} [--ids 3,5] prepare outreach links`);
84158
+ say(c.dim(" /freemode \u2014 open conversation \xB7 /taskmode \u2014 precise \xB7 exit \u2014 quit"));
84159
+ say(c.dim(" Or just talk: \u201Cfind cafes in Leeds and build the best two\u201D."));
84160
+ say();
84161
+ continue;
84162
+ }
84163
+ const isSlash = line.startsWith("/");
84164
+ if (DIRECT_VERBS.includes(verb) && (isSlash || !freeMode)) {
84165
+ const argv = (direct.match(/"[^"]*"|'[^']*'|\S+/g) ?? []).slice(1).map((a) => a.replace(/^['"]|['"]$/g, ""));
84166
+ try {
84167
+ if (verb === "find") await cmdFind(argv);
84168
+ else if (verb === "list") await cmdList(argv);
84169
+ else if (verb === "build") await cmdBuild(argv);
84170
+ else if (verb === "build-all") await cmdBuildAll(argv);
84171
+ else if (verb === "book") await cmdBook(argv);
84172
+ else if (verb === "outreach") await cmdOutreach(argv);
84173
+ } catch (e) {
84174
+ say(c.red(` Error: ${e.message}`));
84175
+ }
84176
+ continue;
84177
+ }
84066
84178
  try {
84067
84179
  if (bridged) {
84068
84180
  const r = await runClaudeTurn({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutkit-code",
3
- "version": "0.10.0",
3
+ "version": "0.12.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"