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.
Files changed (2) hide show
  1. package/dist/index.js +133 -28
  2. 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: "content.json", limit: 1 });
32799
- const target = blobs.find((b) => b.pathname === "content.json");
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("content.json", JSON.stringify(clean), {
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: "booking-config.json", limit: 1 });
33860
- const target = blobs.find((b) => b.pathname === "booking-config.json");
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("booking-config.json", JSON.stringify(clean), {
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: "bookings.jsonl", limit: 1 });
33963
- const target = blobs.find((b) => b.pathname === "bookings.jsonl");
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("bookings.jsonl", text, {
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("content.json", JSON.stringify(initialContent), {
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
- await setProjectEnv(projectName, vercelToken, {
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("booking-config.json", JSON.stringify(bookingConfig), {
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
- await setProjectEnv(projectName, vercelToken, {
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
- await setProjectEnv(projectName, vercelToken, {
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("booking-config.json", JSON.stringify(bookingConfig), {
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.8.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: "optional \u2014 only needed for CMS/booking hand-off", required: false }
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 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.");
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 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.");
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
- say(
83954
- bridged ? c.dim(" Talk to Sproutkit (powered by your Claude subscription). Type a request, or 'exit'.") : c.dim(" Chat with Sproutkit. Type a request, or 'exit' to quit.")
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutkit-code",
3
- "version": "0.8.0",
3
+ "version": "0.10.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"