sproutkit-code 0.1.3 → 0.2.1

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 +158 -24
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -30351,11 +30351,11 @@ var require_models = __commonJS({
30351
30351
  };
30352
30352
  Object.defineProperty(exports2, "__esModule", { value: true });
30353
30353
  exports2.fetchModelsFromAnthropic = fetchModelsFromAnthropic;
30354
- exports2.refreshModels = refreshModels;
30355
- exports2.listCachedModels = listCachedModels;
30354
+ exports2.refreshModels = refreshModels2;
30355
+ exports2.listCachedModels = listCachedModels2;
30356
30356
  exports2.getCacheFetchedAt = getCacheFetchedAt;
30357
- exports2.refreshIfStale = refreshIfStale;
30358
- exports2.getPreferredModel = getPreferredModel;
30357
+ exports2.refreshIfStale = refreshIfStale2;
30358
+ exports2.getPreferredModel = getPreferredModel2;
30359
30359
  var axios_1 = __importDefault(require_axios());
30360
30360
  var db_1 = require_db();
30361
30361
  var CACHE_KEY = "claude_models_cache";
@@ -30398,36 +30398,36 @@ var require_models = __commonJS({
30398
30398
  created_at: m.created_at
30399
30399
  }));
30400
30400
  }
30401
- async function refreshModels(apiKey) {
30402
- const models = await fetchModelsFromAnthropic(apiKey);
30403
- if (models.length === 0)
30401
+ async function refreshModels2(apiKey) {
30402
+ const models2 = await fetchModelsFromAnthropic(apiKey);
30403
+ if (models2.length === 0)
30404
30404
  throw new Error("Anthropic returned no models");
30405
- writeCache({ fetched_at: Date.now(), models });
30406
- return models;
30405
+ writeCache({ fetched_at: Date.now(), models: models2 });
30406
+ return models2;
30407
30407
  }
30408
- function listCachedModels() {
30408
+ function listCachedModels2() {
30409
30409
  const cache = readCache();
30410
30410
  return cache?.models ?? FALLBACK_MODELS;
30411
30411
  }
30412
30412
  function getCacheFetchedAt() {
30413
30413
  return readCache()?.fetched_at ?? null;
30414
30414
  }
30415
- async function refreshIfStale(apiKey) {
30415
+ async function refreshIfStale2(apiKey) {
30416
30416
  if (!apiKey)
30417
30417
  return;
30418
30418
  const cache = readCache();
30419
30419
  if (cache && Date.now() - cache.fetched_at < REFRESH_INTERVAL_MS)
30420
30420
  return;
30421
30421
  try {
30422
- await refreshModels(apiKey);
30422
+ await refreshModels2(apiKey);
30423
30423
  } catch {
30424
30424
  }
30425
30425
  }
30426
- function getPreferredModel() {
30426
+ function getPreferredModel2() {
30427
30427
  const db2 = (0, db_1.getDb)();
30428
30428
  const row = db2.get("SELECT value FROM settings WHERE key = ?", ["claude_model"]);
30429
30429
  const chosen = row?.value;
30430
- const available = listCachedModels();
30430
+ const available = listCachedModels2();
30431
30431
  if (chosen && available.some((m) => m.id === chosen))
30432
30432
  return chosen;
30433
30433
  const sortedByRecency = [...available].sort((a, b) => {
@@ -79735,28 +79735,43 @@ content state.
79735
79735
  OUTPUT
79736
79736
  \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
79737
79737
  Return ONLY the complete App.jsx code. No markdown fences, no explanation, no preamble. Just code.`;
79738
+ let lastStop = null;
79738
79739
  async function generateOnce(extraSystemNote) {
79739
79740
  const finalPrompt = extraSystemNote ? `${extraSystemNote}
79740
79741
 
79741
79742
  ${prompt}` : prompt;
79742
79743
  const res2 = await anthropic.messages.create({
79743
79744
  model: (0, models_1.getPreferredModel)(),
79744
- max_tokens: 16e3,
79745
+ // The API requires a max_tokens, so we set it to the model's ceiling
79746
+ // rather than an arbitrary low cap — truncation was the main build
79747
+ // failure. 64000 covers current Claude output limits; the model stops
79748
+ // naturally at end_turn long before this for a normal site.
79749
+ max_tokens: 64e3,
79745
79750
  messages: [{ role: "user", content: finalPrompt }]
79746
79751
  });
79752
+ lastStop = res2.stop_reason ?? null;
79747
79753
  const textBlock = res2.content.find((b) => b.type === "text");
79748
79754
  if (!textBlock) {
79749
79755
  const types = res2.content.map((b) => b.type).join(", ") || "empty";
79750
79756
  throw new Error(`Claude returned no text (stop_reason=${res2.stop_reason}, blocks=[${types}], model=${res2.model})`);
79751
79757
  }
79752
79758
  if (res2.stop_reason === "max_tokens") {
79753
- onProgress(`\u26A0\uFE0E Claude hit max_tokens \u2014 output may be truncated.`);
79759
+ onProgress(`\u26A0\uFE0E Claude hit the length limit \u2014 output was truncated.`);
79754
79760
  }
79755
79761
  return textBlock.text.replace(/^```[a-z]*\s*/i, "").replace(/\s*```$/, "").trim();
79756
79762
  }
79757
79763
  let appCode = await generateOnce();
79758
79764
  if (!appCode.includes("export default App")) {
79759
- throw new Error("Generated code is incomplete (no export default)");
79765
+ const wasTruncated = lastStop === "max_tokens";
79766
+ onProgress(wasTruncated ? "Output was truncated \u2014 retrying\u2026" : "First attempt came back incomplete \u2014 retrying\u2026");
79767
+ const retry = await generateOnce("Your previous attempt did not finish with `export default App;`. Produce the COMPLETE, self-contained App component ending in that exact line. Do not stop early.");
79768
+ if (retry.includes("export default App")) {
79769
+ appCode = retry;
79770
+ } else if (lastStop === "max_tokens") {
79771
+ throw new Error("This site's content is unusually large and Claude's output was cut off. Try regenerating with 'keep it minimal \u2014 few sections'.");
79772
+ } else {
79773
+ throw new Error(`Claude returned incomplete code twice (stop reason: ${lastStop ?? "unknown"}). This usually clears on another try, or check the model with 'sproutkit models'.`);
79774
+ }
79760
79775
  }
79761
79776
  const stockHosts = findStockImages(appCode);
79762
79777
  if (stockHosts.length > 0) {
@@ -81172,8 +81187,9 @@ Formatting \u2014 you're writing into a narrow chat bubble, not a document:
81172
81187
  - Skip empty fields entirely rather than writing "\u2014" or "N/A" in a column.
81173
81188
  - Lead with the answer. If the user asked "how many", give the number first, then the breakdown.
81174
81189
  - For more than about 8 leads, show the most relevant handful and say how many others there are instead of listing everything.`;
81190
+ var VOLATILE_INPUT_FIELDS = /* @__PURE__ */ new Set(["instructions", "message", "concurrency"]);
81175
81191
  function confirmKey(name, input) {
81176
- const norm = Object.keys(input ?? {}).sort().reduce((acc, k) => {
81192
+ const norm = Object.keys(input ?? {}).filter((k) => !VOLATILE_INPUT_FIELDS.has(k)).sort().reduce((acc, k) => {
81177
81193
  const v = input[k];
81178
81194
  if (v === void 0 || v === null || v === false || v === "")
81179
81195
  return acc;
@@ -82047,7 +82063,7 @@ var require_package = __commonJS({
82047
82063
  "package.json"(exports2, module2) {
82048
82064
  module2.exports = {
82049
82065
  name: "sproutkit-code",
82050
- version: "0.1.3",
82066
+ version: "0.2.1",
82051
82067
  description: "Sproutkit in your terminal \u2014 find leads, build sites, run outreach, all via chat.",
82052
82068
  bin: {
82053
82069
  sproutkit: "./dist/index.js"
@@ -82093,6 +82109,7 @@ var autoMode = require_auto_mode();
82093
82109
  var cms = require_cms_provision();
82094
82110
  var bookingPreset = require_booking_preset();
82095
82111
  var builder = require_builder();
82112
+ var models = require_models();
82096
82113
  var initDatabase = db.initDatabase;
82097
82114
  var getDb = db.getDb;
82098
82115
  var chatWithTools = chat.chatWithTools;
@@ -82100,6 +82117,10 @@ var startAutoJob = autoMode.startAutoJob;
82100
82117
  var provisionCmsForLead = cms.provisionCmsForLead;
82101
82118
  var generatePreset = bookingPreset.generatePreset;
82102
82119
  var deployBookingOnly = builder.deployBookingOnly;
82120
+ var refreshModels = models.refreshModels;
82121
+ var refreshIfStale = models.refreshIfStale;
82122
+ var listCachedModels = models.listCachedModels;
82123
+ var getPreferredModel = models.getPreferredModel;
82103
82124
 
82104
82125
  // src/config.ts
82105
82126
  var REQUIRED_KEYS = [
@@ -82236,6 +82257,53 @@ function closeTty() {
82236
82257
  function say(msg = "") {
82237
82258
  process.stdout.write(msg + "\n");
82238
82259
  }
82260
+ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
82261
+ var Status = class {
82262
+ timer = null;
82263
+ frame = 0;
82264
+ label = "";
82265
+ stage = "";
82266
+ tty = process.stdout.isTTY && useColor;
82267
+ start(label) {
82268
+ this.label = label;
82269
+ this.stage = "";
82270
+ if (!this.tty) {
82271
+ say(c.cyan(` \u27F3 ${label}`));
82272
+ return;
82273
+ }
82274
+ this.render();
82275
+ this.timer = setInterval(() => {
82276
+ this.frame = (this.frame + 1) % FRAMES.length;
82277
+ this.render();
82278
+ }, 80);
82279
+ }
82280
+ // Update the current stage (e.g. "Generating site with Claude…").
82281
+ update(stage) {
82282
+ this.stage = stage;
82283
+ if (!this.tty) {
82284
+ say(c.dim(` \u2026 ${stage}`));
82285
+ return;
82286
+ }
82287
+ this.render();
82288
+ }
82289
+ render() {
82290
+ const spin = c.cyan(FRAMES[this.frame]);
82291
+ const stage = this.stage ? c.dim(` \xB7 ${this.stage}`) : "";
82292
+ process.stdout.write(`\r\x1B[2K ${spin} ${this.label}${stage}`);
82293
+ }
82294
+ // Stop with a terminal state, leaving one clean line behind.
82295
+ done(ok, finalLabel) {
82296
+ if (this.timer) {
82297
+ clearInterval(this.timer);
82298
+ this.timer = null;
82299
+ }
82300
+ const label = finalLabel ?? this.label;
82301
+ const mark = ok ? c.green("\u2713") : c.red("\u2717");
82302
+ if (this.tty) process.stdout.write(`\r\x1B[2K ${mark} ${label}
82303
+ `);
82304
+ else say(` ${mark} ${label}`);
82305
+ }
82306
+ };
82239
82307
 
82240
82308
  // src/onboarding.ts
82241
82309
  async function onboardLicense(force) {
@@ -82286,6 +82354,13 @@ async function runOnboarding(force = false) {
82286
82354
  }
82287
82355
  say();
82288
82356
  }
82357
+ const anthropicKey = getSetting("anthropic_key");
82358
+ if (anthropicKey) {
82359
+ try {
82360
+ await refreshModels(anthropicKey);
82361
+ } catch {
82362
+ }
82363
+ }
82289
82364
  const stillMissing = missingRequiredKeys();
82290
82365
  if (stillMissing.length > 0) {
82291
82366
  say(c.amber(` Still missing: ${stillMissing.map((m) => m.label).join(", ")}.`));
@@ -82529,12 +82604,14 @@ async function runTurn(userMessage) {
82529
82604
  const history = loadHistory().slice(0, -1);
82530
82605
  const confirmations = {};
82531
82606
  const completedResults = {};
82607
+ const status = new Status();
82608
+ let statusActive = false;
82532
82609
  let lastProgress = "";
82533
- const emit = (channel, payload) => {
82610
+ const emit = (_channel, payload) => {
82534
82611
  const p = payload;
82535
82612
  if (p?.msg && p.msg !== lastProgress) {
82536
82613
  lastProgress = p.msg;
82537
- say(c.dim(` \u2026 ${p.msg}`));
82614
+ if (statusActive) status.update(p.msg);
82538
82615
  }
82539
82616
  };
82540
82617
  const invokeIpc = makeInvokeIpc(emit);
@@ -82553,11 +82630,15 @@ async function runTurn(userMessage) {
82553
82630
  const t = e.data;
82554
82631
  if (t.status === "pending" && !seenRunning.has(t.id)) {
82555
82632
  seenRunning.add(t.id);
82556
- say(c.cyan(` \u27F3 ${labelFor(t)}`));
82633
+ lastProgress = "";
82634
+ status.start(labelFor(t));
82635
+ statusActive = true;
82557
82636
  } else if (t.status === "completed") {
82558
- say(c.green(` \u2713 ${labelFor(t)}`));
82637
+ status.done(true, labelFor(t));
82638
+ statusActive = false;
82559
82639
  } else if (t.status === "error") {
82560
- say(c.red(` \u2717 ${labelFor(t)} \u2014 ${t.error ?? "failed"}`));
82640
+ status.done(false, `${labelFor(t)} \u2014 ${t.error ?? "failed"}`);
82641
+ statusActive = false;
82561
82642
  }
82562
82643
  }
82563
82644
  },
@@ -82576,6 +82657,10 @@ async function runTurn(userMessage) {
82576
82657
  if (ranSomething) autoSync();
82577
82658
  return;
82578
82659
  }
82660
+ if (statusActive) {
82661
+ status.done(false, "paused for confirmation");
82662
+ statusActive = false;
82663
+ }
82579
82664
  say();
82580
82665
  say(c.amber(` ${pending.length} action${pending.length === 1 ? "" : "s"} need confirmation:`));
82581
82666
  for (const t of pending) {
@@ -82651,6 +82736,47 @@ async function notifyIfOutdated() {
82651
82736
  }
82652
82737
  }
82653
82738
 
82739
+ // src/models-cmd.ts
82740
+ async function runModels() {
82741
+ const apiKey = getSetting("anthropic_key");
82742
+ if (!apiKey) {
82743
+ say(c.red(" No Anthropic key set. Run 'sproutkit setup' first."));
82744
+ return;
82745
+ }
82746
+ say(c.dim(" Fetching your available models from Anthropic\u2026"));
82747
+ let models2 = listCachedModels();
82748
+ try {
82749
+ models2 = await refreshModels(apiKey);
82750
+ } catch (e) {
82751
+ say(c.amber(` Couldn't refresh from the API (${e.message}).`));
82752
+ say(c.dim(" Showing the last known list instead."));
82753
+ }
82754
+ if (models2.length === 0) {
82755
+ say(c.red(" No models returned. Check your Anthropic key and connection."));
82756
+ return;
82757
+ }
82758
+ const current = getPreferredModel();
82759
+ say();
82760
+ say(c.bold(" Available models:"));
82761
+ models2.forEach((m, i) => {
82762
+ const active = m.id === current ? c.green(" \u2190 current") : "";
82763
+ say(` ${c.greenBold(String(i + 1).padStart(2))} ${m.display_name ?? m.id} ${c.dim(m.id)}${active}`);
82764
+ });
82765
+ say();
82766
+ const pick = await ask(c.green(" Pick a number to switch (or Enter to keep current): "));
82767
+ if (!pick) {
82768
+ say(c.dim(" Kept current model."));
82769
+ return;
82770
+ }
82771
+ const idx = Number(pick) - 1;
82772
+ if (Number.isNaN(idx) || idx < 0 || idx >= models2.length) {
82773
+ say(c.amber(" Not a valid choice \u2014 no change made."));
82774
+ return;
82775
+ }
82776
+ setSetting("claude_model", models2[idx].id);
82777
+ say(c.green(` \u2713 Now using ${models2[idx].display_name ?? models2[idx].id}.`));
82778
+ }
82779
+
82654
82780
  // src/index.ts
82655
82781
  process.env.SPROUTKIT_HEADLESS = "1";
82656
82782
  async function main() {
@@ -82666,6 +82792,11 @@ async function main() {
82666
82792
  closeTty();
82667
82793
  return;
82668
82794
  }
82795
+ if (arg === "models") {
82796
+ await runModels();
82797
+ closeTty();
82798
+ return;
82799
+ }
82669
82800
  if (arg === "sync") {
82670
82801
  const r = syncToDesktop();
82671
82802
  if (!r.ok && r.reason === "no_desktop_db") {
@@ -82687,6 +82818,7 @@ async function main() {
82687
82818
  say();
82688
82819
  say(` ${c.bold("sproutkit")} start the chat`);
82689
82820
  say(` ${c.bold("sproutkit setup")} set or update your keys and licence`);
82821
+ say(` ${c.bold("sproutkit models")} list Claude models and pick one`);
82690
82822
  say(` ${c.bold("sproutkit sync")} push your leads into the desktop app`);
82691
82823
  say(` ${c.bold("sproutkit update")} update to the latest version`);
82692
82824
  say(` ${c.bold("sproutkit version")} show the installed version`);
@@ -82715,6 +82847,8 @@ async function main() {
82715
82847
  closeTty();
82716
82848
  return;
82717
82849
  }
82850
+ await refreshIfStale(getSetting("anthropic_key")).catch(() => {
82851
+ });
82718
82852
  await notifyIfOutdated();
82719
82853
  say(c.dim(" Chat with Sproutkit. Type a request, or 'exit' to quit."));
82720
82854
  say();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sproutkit-code",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
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"