sproutkit-code 0.5.0 → 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.
- package/dist/index.js +165 -38
- 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;
|
|
@@ -80617,33 +80618,90 @@ var require_places = __commonJS({
|
|
|
80617
80618
|
return Math.max(0, Math.round(score));
|
|
80618
80619
|
}
|
|
80619
80620
|
var COST_PER_REQUEST_GBP = 0.025;
|
|
80620
|
-
async function
|
|
80621
|
-
const
|
|
80622
|
-
|
|
80623
|
-
|
|
80624
|
-
|
|
80625
|
-
|
|
80626
|
-
|
|
80627
|
-
|
|
80628
|
-
|
|
80621
|
+
async function searchOneType(args) {
|
|
80622
|
+
const body = {
|
|
80623
|
+
textQuery: `${args.type} in ${args.city}`,
|
|
80624
|
+
maxResultCount: 20
|
|
80625
|
+
};
|
|
80626
|
+
if (args.center && args.radiusMeters) {
|
|
80627
|
+
body.locationBias = {
|
|
80628
|
+
circle: {
|
|
80629
|
+
center: { latitude: args.center.lat, longitude: args.center.lng },
|
|
80630
|
+
radius: args.radiusMeters
|
|
80631
|
+
}
|
|
80629
80632
|
};
|
|
80630
|
-
|
|
80631
|
-
|
|
80632
|
-
circle: {
|
|
80633
|
-
center: { latitude: args.center.lat, longitude: args.center.lng },
|
|
80634
|
-
radius: args.radiusMeters
|
|
80635
|
-
}
|
|
80636
|
-
};
|
|
80637
|
-
}
|
|
80633
|
+
}
|
|
80634
|
+
try {
|
|
80638
80635
|
const r = await axios_1.default.post("https://places.googleapis.com/v1/places:searchText", body, {
|
|
80639
80636
|
headers: {
|
|
80640
80637
|
"X-Goog-Api-Key": args.apiKey,
|
|
80641
80638
|
"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"
|
|
80642
80639
|
}
|
|
80643
80640
|
});
|
|
80644
|
-
|
|
80645
|
-
|
|
80646
|
-
|
|
80641
|
+
return { type: args.type, places: r.data.places ?? [] };
|
|
80642
|
+
} catch {
|
|
80643
|
+
return { type: args.type, places: [] };
|
|
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
|
+
}
|
|
80693
|
+
async function searchPlaces(args) {
|
|
80694
|
+
const batches = await Promise.all(args.types.map((type) => searchOneType({
|
|
80695
|
+
apiKey: args.apiKey,
|
|
80696
|
+
city: args.city,
|
|
80697
|
+
type,
|
|
80698
|
+
center: args.center,
|
|
80699
|
+
radiusMeters: args.radiusMeters
|
|
80700
|
+
})));
|
|
80701
|
+
const results = [];
|
|
80702
|
+
const seen = /* @__PURE__ */ new Set();
|
|
80703
|
+
for (const { type, places } of batches) {
|
|
80704
|
+
for (const p of places) {
|
|
80647
80705
|
if (seen.has(p.id))
|
|
80648
80706
|
continue;
|
|
80649
80707
|
seen.add(p.id);
|
|
@@ -80687,7 +80745,7 @@ var require_places = __commonJS({
|
|
|
80687
80745
|
}
|
|
80688
80746
|
}
|
|
80689
80747
|
results.sort((a, b) => b.score - a.score);
|
|
80690
|
-
return results;
|
|
80748
|
+
return results.slice(0, args.maxResults);
|
|
80691
80749
|
}
|
|
80692
80750
|
function haversineMeters(lat1, lon1, lat2, lon2) {
|
|
80693
80751
|
const R = 6371e3;
|
|
@@ -80726,9 +80784,58 @@ var require_lead_search = __commonJS({
|
|
|
80726
80784
|
"../desktop/dist/main/lib/lead-search.js"(exports2) {
|
|
80727
80785
|
"use strict";
|
|
80728
80786
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
80787
|
+
exports2.searchAndSaveLeadsStream = searchAndSaveLeadsStream;
|
|
80729
80788
|
exports2.searchAndSaveLeads = searchAndSaveLeads;
|
|
80730
80789
|
var db_1 = require_db();
|
|
80731
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
|
+
}
|
|
80732
80839
|
async function searchAndSaveLeads(args) {
|
|
80733
80840
|
const db2 = (0, db_1.getDb)();
|
|
80734
80841
|
const results = await (0, places_1.searchPlaces)({
|
|
@@ -81096,15 +81203,20 @@ Interested?`;
|
|
|
81096
81203
|
});
|
|
81097
81204
|
const matches = await (0, places_1.geocodeForward)(location, 1);
|
|
81098
81205
|
const place = matches[0];
|
|
81099
|
-
const result = await (0, lead_search_1.
|
|
81100
|
-
|
|
81101
|
-
|
|
81102
|
-
|
|
81103
|
-
|
|
81104
|
-
|
|
81105
|
-
|
|
81106
|
-
|
|
81107
|
-
|
|
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
|
+
);
|
|
81108
81220
|
ctx.emitProgress?.({
|
|
81109
81221
|
channel: "leads:changed",
|
|
81110
81222
|
payload: { reason: "find_leads", ...result }
|
|
@@ -82272,7 +82384,7 @@ var require_package = __commonJS({
|
|
|
82272
82384
|
"package.json"(exports2, module2) {
|
|
82273
82385
|
module2.exports = {
|
|
82274
82386
|
name: "sproutkit-code",
|
|
82275
|
-
version: "0.
|
|
82387
|
+
version: "0.6.0",
|
|
82276
82388
|
description: "Sproutkit in your terminal \u2014 find leads, build sites, run outreach, all via chat.",
|
|
82277
82389
|
bin: {
|
|
82278
82390
|
sproutkit: "./dist/index.js"
|
|
@@ -83234,6 +83346,7 @@ async function runModels() {
|
|
|
83234
83346
|
// src/commands.ts
|
|
83235
83347
|
function makeCtx(opts) {
|
|
83236
83348
|
const emit = (channel, payload) => {
|
|
83349
|
+
opts.onEvent?.(channel, payload);
|
|
83237
83350
|
if (opts.json) return;
|
|
83238
83351
|
const p = payload;
|
|
83239
83352
|
if (p?.msg && opts.status) opts.status.update(p.msg);
|
|
@@ -83277,8 +83390,18 @@ async function cmdFind(argv) {
|
|
|
83277
83390
|
return;
|
|
83278
83391
|
}
|
|
83279
83392
|
const categories = String(flags.categories ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
83280
|
-
|
|
83281
|
-
|
|
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
|
+
};
|
|
83282
83405
|
try {
|
|
83283
83406
|
const result = await runTool(
|
|
83284
83407
|
"find_leads",
|
|
@@ -83288,14 +83411,18 @@ async function cmdFind(argv) {
|
|
|
83288
83411
|
maxResults: flags.max ? Number(flags.max) : void 0,
|
|
83289
83412
|
radiusMeters: flags.radius ? Number(flags.radius) : void 0
|
|
83290
83413
|
},
|
|
83291
|
-
makeCtx({ json,
|
|
83414
|
+
makeCtx({ json, onEvent })
|
|
83292
83415
|
);
|
|
83293
|
-
if (status) status.done(true, `Found ${result.found ?? 0}, saved ${result.saved ?? 0} new`);
|
|
83294
83416
|
autoSync();
|
|
83295
|
-
if (json)
|
|
83296
|
-
|
|
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
|
+
}
|
|
83297
83425
|
} catch (e) {
|
|
83298
|
-
if (status) status.done(false, "Find failed");
|
|
83299
83426
|
json ? emitJson({ ok: false, error: e.message }) : say(c.red(" " + e.message));
|
|
83300
83427
|
process.exitCode = 1;
|
|
83301
83428
|
}
|