sproutkit-code 0.5.1 → 0.6.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 +134 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -80585,6 +80585,7 @@ var require_places = __commonJS({
80585
80585
  Object.defineProperty(exports2, "__esModule", { value: true });
80586
80586
  exports2.PLACES_COST_PER_REQUEST_GBP = void 0;
80587
80587
  exports2.scoreLead = scoreLead;
80588
+ exports2.searchPlacesStream = searchPlacesStream;
80588
80589
  exports2.searchPlaces = searchPlaces;
80589
80590
  exports2.geocodeForward = geocodeForward;
80590
80591
  exports2.geocodeReverse = geocodeReverse;
@@ -80642,6 +80643,53 @@ var require_places = __commonJS({
80642
80643
  return { type: args.type, places: [] };
80643
80644
  }
80644
80645
  }
80646
+ function processRawPlaces(type, places, center, radiusMeters) {
80647
+ const out = [];
80648
+ for (const p of places) {
80649
+ if (p.websiteUri)
80650
+ continue;
80651
+ if (center && radiusMeters && p.location) {
80652
+ const d = haversineMeters(center.lat, center.lng, p.location.latitude, p.location.longitude);
80653
+ if (d > radiusMeters)
80654
+ continue;
80655
+ }
80656
+ const phone = p.nationalPhoneNumber ?? "";
80657
+ const rating = p.rating ?? null;
80658
+ const review_count = p.userRatingCount ?? null;
80659
+ const price_level = p.priceLevel ?? null;
80660
+ const business_status = p.businessStatus ?? null;
80661
+ const has_hours = !!p.regularOpeningHours;
80662
+ out.push({
80663
+ place_id: p.id,
80664
+ name: p.displayName?.text ?? "Unknown",
80665
+ type,
80666
+ address: p.formattedAddress ?? "",
80667
+ phone,
80668
+ website: p.websiteUri ?? null,
80669
+ rating,
80670
+ review_count,
80671
+ lat: p.location?.latitude ?? null,
80672
+ lng: p.location?.longitude ?? null,
80673
+ price_level,
80674
+ business_status,
80675
+ has_hours,
80676
+ score: scoreLead({ rating, review_count, price_level, phone, has_hours, business_status })
80677
+ });
80678
+ }
80679
+ return out;
80680
+ }
80681
+ async function searchPlacesStream(args, onBatch) {
80682
+ await Promise.all(args.types.map(async (type) => {
80683
+ const { places } = await searchOneType({
80684
+ apiKey: args.apiKey,
80685
+ city: args.city,
80686
+ type,
80687
+ center: args.center,
80688
+ radiusMeters: args.radiusMeters
80689
+ });
80690
+ await onBatch(processRawPlaces(type, places, args.center, args.radiusMeters));
80691
+ }));
80692
+ }
80645
80693
  async function searchPlaces(args) {
80646
80694
  const batches = await Promise.all(args.types.map((type) => searchOneType({
80647
80695
  apiKey: args.apiKey,
@@ -80736,9 +80784,58 @@ var require_lead_search = __commonJS({
80736
80784
  "../desktop/dist/main/lib/lead-search.js"(exports2) {
80737
80785
  "use strict";
80738
80786
  Object.defineProperty(exports2, "__esModule", { value: true });
80787
+ exports2.searchAndSaveLeadsStream = searchAndSaveLeadsStream;
80739
80788
  exports2.searchAndSaveLeads = searchAndSaveLeads;
80740
80789
  var db_1 = require_db();
80741
80790
  var places_1 = require_places();
80791
+ async function searchAndSaveLeadsStream(args, onLead) {
80792
+ const db2 = (0, db_1.getDb)();
80793
+ const cap = args.maxResults ?? 50;
80794
+ const seen = /* @__PURE__ */ new Set();
80795
+ let saved = 0;
80796
+ let found = 0;
80797
+ await (0, places_1.searchPlacesStream)({
80798
+ apiKey: args.placesKey,
80799
+ city: args.city,
80800
+ types: args.types,
80801
+ center: args.center,
80802
+ radiusMeters: args.radiusMeters
80803
+ }, (batch) => {
80804
+ for (const r of batch) {
80805
+ found++;
80806
+ if (saved >= cap)
80807
+ continue;
80808
+ if (seen.has(r.place_id))
80809
+ continue;
80810
+ seen.add(r.place_id);
80811
+ const existing = db2.all("SELECT id FROM leads WHERE place_id = ?", [r.place_id]);
80812
+ if (existing.length > 0)
80813
+ continue;
80814
+ db2.run(`INSERT INTO leads
80815
+ (place_id, name, type, city, address, phone, website, rating, review_count, lat, lng, score, price_level, business_status, status)
80816
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'discovered')`, [
80817
+ r.place_id,
80818
+ r.name,
80819
+ r.type,
80820
+ args.city,
80821
+ r.address,
80822
+ r.phone,
80823
+ r.website,
80824
+ r.rating,
80825
+ r.review_count,
80826
+ r.lat,
80827
+ r.lng,
80828
+ r.score,
80829
+ r.price_level,
80830
+ r.business_status
80831
+ ]);
80832
+ const row = db2.get("SELECT id FROM leads WHERE place_id = ?", [r.place_id]);
80833
+ saved++;
80834
+ onLead({ id: row?.id ?? 0, place_id: r.place_id, name: r.name, type: r.type, city: args.city });
80835
+ }
80836
+ });
80837
+ return { found, saved };
80838
+ }
80742
80839
  async function searchAndSaveLeads(args) {
80743
80840
  const db2 = (0, db_1.getDb)();
80744
80841
  const results = await (0, places_1.searchPlaces)({
@@ -81106,15 +81203,20 @@ Interested?`;
81106
81203
  });
81107
81204
  const matches = await (0, places_1.geocodeForward)(location, 1);
81108
81205
  const place = matches[0];
81109
- const result = await (0, lead_search_1.searchAndSaveLeads)({
81110
- placesKey,
81111
- // searchPlaces uses `city` as the text query, so keep the user's words.
81112
- city: location,
81113
- types: categories,
81114
- maxResults: Math.min(Number(input.maxResults ?? 25), 100),
81115
- center: place ? { lat: place.lat, lng: place.lng } : void 0,
81116
- radiusMeters: Number(input.radiusMeters ?? 3e3)
81117
- });
81206
+ const result = await (0, lead_search_1.searchAndSaveLeadsStream)(
81207
+ {
81208
+ placesKey,
81209
+ // searchPlaces uses `city` as the text query, so keep the user's words.
81210
+ city: location,
81211
+ types: categories,
81212
+ maxResults: Math.min(Number(input.maxResults ?? 25), 100),
81213
+ center: place ? { lat: place.lat, lng: place.lng } : void 0,
81214
+ radiusMeters: Number(input.radiusMeters ?? 3e3)
81215
+ },
81216
+ // Emit each lead the instant it's saved, so the UI/CLI streams results
81217
+ // in rather than waiting for every category to finish.
81218
+ (lead) => ctx.emitProgress?.({ channel: "leads:found", payload: { lead } })
81219
+ );
81118
81220
  ctx.emitProgress?.({
81119
81221
  channel: "leads:changed",
81120
81222
  payload: { reason: "find_leads", ...result }
@@ -82282,7 +82384,7 @@ var require_package = __commonJS({
82282
82384
  "package.json"(exports2, module2) {
82283
82385
  module2.exports = {
82284
82386
  name: "sproutkit-code",
82285
- version: "0.5.1",
82387
+ version: "0.6.0",
82286
82388
  description: "Sproutkit in your terminal \u2014 find leads, build sites, run outreach, all via chat.",
82287
82389
  bin: {
82288
82390
  sproutkit: "./dist/index.js"
@@ -83244,6 +83346,7 @@ async function runModels() {
83244
83346
  // src/commands.ts
83245
83347
  function makeCtx(opts) {
83246
83348
  const emit = (channel, payload) => {
83349
+ opts.onEvent?.(channel, payload);
83247
83350
  if (opts.json) return;
83248
83351
  const p = payload;
83249
83352
  if (p?.msg && opts.status) opts.status.update(p.msg);
@@ -83287,8 +83390,18 @@ async function cmdFind(argv) {
83287
83390
  return;
83288
83391
  }
83289
83392
  const categories = String(flags.categories ?? "").split(",").map((s) => s.trim()).filter(Boolean);
83290
- const status = json ? void 0 : new Status();
83291
- if (status) status.start(`Finding leads in ${location}`);
83393
+ if (!json) say(c.dim(` Searching ${location}${categories.length ? ` for ${categories.join(", ")}` : ""}\u2026`));
83394
+ let streamed = 0;
83395
+ const onEvent = (channel, payload) => {
83396
+ if (json) return;
83397
+ if (channel === "leads:found") {
83398
+ const lead = payload.lead;
83399
+ if (lead) {
83400
+ streamed++;
83401
+ say(` ${c.green("+")} ${c.bold(lead.name)} ${c.dim(`#${lead.id} \xB7 ${lead.type}`)}`);
83402
+ }
83403
+ }
83404
+ };
83292
83405
  try {
83293
83406
  const result = await runTool(
83294
83407
  "find_leads",
@@ -83298,14 +83411,18 @@ async function cmdFind(argv) {
83298
83411
  maxResults: flags.max ? Number(flags.max) : void 0,
83299
83412
  radiusMeters: flags.radius ? Number(flags.radius) : void 0
83300
83413
  },
83301
- makeCtx({ json, status })
83414
+ makeCtx({ json, onEvent })
83302
83415
  );
83303
- if (status) status.done(true, `Found ${result.found ?? 0}, saved ${result.saved ?? 0} new`);
83304
83416
  autoSync();
83305
- if (json) emitJson(result);
83306
- else if (result.note) say(c.dim(" " + result.note));
83417
+ if (json) {
83418
+ emitJson(result);
83419
+ } else {
83420
+ say();
83421
+ say(c.green(` Saved ${result.saved ?? streamed} new lead${(result.saved ?? streamed) === 1 ? "" : "s"}.`));
83422
+ if (result.note) say(c.dim(" " + result.note));
83423
+ say();
83424
+ }
83307
83425
  } catch (e) {
83308
- if (status) status.done(false, "Find failed");
83309
83426
  json ? emitJson({ ok: false, error: e.message }) : say(c.red(" " + e.message));
83310
83427
  process.exitCode = 1;
83311
83428
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutkit-code",
3
- "version": "0.5.1",
3
+ "version": "0.6.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"