scriptonia 0.6.0 → 0.7.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/bin/scriptonia.mjs +76 -5
  2. package/package.json +1 -1
@@ -32,7 +32,7 @@ const commands = {
32
32
  add, ingest: add, push: add,
33
33
  plan, plans, comment,
34
34
  query, contexts, link,
35
- status, logout, skill: reinstallSkill, mcp,
35
+ status, credits, logout, skill: reinstallSkill, mcp,
36
36
  flow, guide: flow,
37
37
  help: async () => usage(),
38
38
  };
@@ -132,13 +132,19 @@ async function flow() {
132
132
  const row = (n, name, desc, cmd) =>
133
133
  console.log(` ${C.v}${n}${C.r} ${C.b}${name.padEnd(7)}${C.r} ${C.dim}${desc.padEnd(34)}${C.r} ${cmd ? C.g + cmd + C.r : ""}`);
134
134
 
135
- console.log(` ${C.dim}THE LOOP${C.r} ${C.dim}${s.total} signals · ${plansN} plans${C.r}`);
135
+ const cr = o?.credits;
136
+ const creditStr = cr && cr.metered !== false ? ` · ${cr.plan} ${cr.used}/${cr.cap} credits` : "";
137
+ console.log(` ${C.dim}THE LOOP${C.r} ${C.dim}${s.total} signals · ${plansN} plans${creditStr}${C.r}`);
136
138
  row("1", "SIGNAL", "feed customer reality (text·md·vtt)", `scriptonia add <files>`);
137
139
  row("2", "PLAN", "issue → PLAN.md, sourced & checked", `scriptonia plan "<issue>"`);
138
140
  row("3", "BUILD", "your agent executes PLAN.md → PR", `claude "execute PLAN.md"`);
139
141
  row("4", "REFINE", "a note regenerates the plan", `scriptonia comment plan/<slug> "…"`);
140
142
 
141
- console.log(`\n ${C.b}NEXT${C.r} ${nextStep(s, plansN, recentPlan, repo, C)}\n`);
143
+ console.log(`\n ${C.b}NEXT${C.r} ${nextStep(s, plansN, recentPlan, repo, C)}`);
144
+ if (cr && cr.metered !== false && cr.available <= 0) {
145
+ console.log(` ${C.w}Out of credits${C.r} ${C.dim}— ${cr.topup}${C.r}`);
146
+ }
147
+ console.log("");
142
148
  }
143
149
 
144
150
  function nextStep(s, plansN, recentPlan, repo, C) {
@@ -339,6 +345,7 @@ async function comment(args) {
339
345
  body: JSON.stringify({ slug, body: text, author: flags.as ?? cfg.email }),
340
346
  });
341
347
  const data = await res.json().catch(() => ({}));
348
+ if (res.status === 402) failCredits(data);
342
349
  if (res.status === 404) fail(`no plan "${slug}" — generate one first: scriptonia plan "<issue>"`);
343
350
  if (!res.ok) fail(`comment failed (${res.status}): ${JSON.stringify(data).slice(0, 200)}`);
344
351
  // Approval-chain receipt: which human note produced which plan version.
@@ -362,6 +369,7 @@ async function comment(args) {
362
369
  body: JSON.stringify({ slug: target, body: text, author: flags.as ?? cfg.email }),
363
370
  });
364
371
  const data = await res.json().catch(() => ({}));
372
+ if (res.status === 402) failCredits(data);
365
373
  if (res.status === 404) fail(`no context "${target}" — see \`scriptonia contexts\``);
366
374
  if (!res.ok) fail(`comment failed (${res.status}): ${JSON.stringify(data)}`);
367
375
 
@@ -384,6 +392,7 @@ async function query(args) {
384
392
  body: JSON.stringify({ query: q, window: flags.window, segment: flags.segment }),
385
393
  });
386
394
  const data = await res.json();
395
+ if (res.status === 402) failCredits(data);
387
396
  if (!res.ok) fail(`query failed (${res.status}): ${JSON.stringify(data)}`);
388
397
 
389
398
  const wantJson = flags.json !== undefined || !process.stdout.isTTY;
@@ -412,12 +421,65 @@ async function link(args) {
412
421
  async function status() {
413
422
  const cfg = loadConfig();
414
423
  if (!cfg) { console.log("not logged in — run `scriptonia login`"); return; }
424
+ const active = activeCreds();
425
+ const C = colors();
415
426
  console.log(`signed in as ${cfg.email}`);
416
- console.log(`project ${cfg.project}`);
427
+ console.log(`project ${active.project}`);
428
+ const c = await fetchCredits(active);
429
+ if (c && c.metered !== false) {
430
+ const bar = creditBar(c.used, c.cap, C);
431
+ console.log(`plan ${C.b}${c.plan}${C.r}`);
432
+ console.log(`credits ${bar} ${c.used}/${c.cap} used · resets ${c.resets_at}`);
433
+ if (c.available <= 0) console.log(` ${C.w}out of credits${C.r} — ${c.topup}`);
434
+ else if (c.available <= Math.max(2, Math.round(c.cap * 0.2))) console.log(` ${C.dim}running low — ${c.topup}${C.r}`);
435
+ }
417
436
  console.log(`endpoint ${cfg.url}`);
418
437
  console.log(`config ${CONFIG_PATH}`);
419
438
  }
420
439
 
440
+ // ── credits ───────────────────────────────────────────────────
441
+ async function credits() {
442
+ const cfg = activeCreds();
443
+ const C = colors();
444
+ const c = await fetchCredits(cfg);
445
+ if (!c) fail("could not load credits");
446
+ if (c.metered === false) { console.log(`\n ${C.dim}This project isn't metered.${C.r}\n`); return; }
447
+ console.log(`\n ${C.b}${c.plan.toUpperCase()}${C.r} · ${cfg.project}`);
448
+ console.log(` ${creditBar(c.used, c.cap, C)} ${C.b}${c.available}${C.r} of ${c.cap} credits left ${C.dim}(resets ${c.resets_at})${C.r}`);
449
+ console.log(`\n ${C.dim}Plans & comments cost 5 · queries 1 · adding signal is free.${C.r}`);
450
+ if (c.available <= 0) console.log(` ${C.w}Out of credits.${C.r} ${c.topup}`);
451
+ else console.log(` ${C.dim}Need more? ${c.topup}${C.r}`);
452
+ console.log("");
453
+ }
454
+
455
+ async function fetchCredits(cfg) {
456
+ try {
457
+ const res = await fetch(`${cfg.url}/api/credits`, { headers: { authorization: `Bearer ${cfg.token}` } });
458
+ if (!res.ok) return null;
459
+ return await res.json();
460
+ } catch { return null; }
461
+ }
462
+
463
+ function creditBar(used, cap, C) {
464
+ const width = 16;
465
+ const frac = cap > 0 ? Math.min(1, used / cap) : 0;
466
+ const filled = Math.round(frac * width);
467
+ const color = frac >= 1 ? C.w : frac >= 0.8 ? C.w : C.g;
468
+ return `${color}${"█".repeat(filled)}${C.dim}${"░".repeat(width - filled)}${C.r}`;
469
+ }
470
+
471
+ /** Render an HTTP 402 credits_exhausted body and exit. Shared by plan/query/comment. */
472
+ function failCredits(data) {
473
+ const C = colors();
474
+ const resets = data.resets_at ? ` (resets ${data.resets_at})` : "";
475
+ console.error(`\n ${C.w}✕ Out of credits${C.r} — ${data.plan}: ${data.used}/${data.cap} used this month${resets}.\n`);
476
+ console.error(` Your signal is safe and still being collected. Plans & queries resume`);
477
+ console.error(` when credits do.\n`);
478
+ console.error(` ${C.b}Top up:${C.r} ${data.topup}`);
479
+ console.error(` ${C.dim}(adding signal, sync, contexts, plans list — all still free)${C.r}\n`);
480
+ process.exit(1);
481
+ }
482
+
421
483
  async function logout() {
422
484
  if (fs.existsSync(CONFIG_PATH)) fs.rmSync(CONFIG_PATH);
423
485
  console.log("logged out.");
@@ -621,8 +683,10 @@ function usage() {
621
683
  scriptonia query "<topic>" quick lookup, no full plan (--json)
622
684
  scriptonia link "<id>" a signal's full text
623
685
  scriptonia sync write the brain to local files
686
+ scriptonia credits plan, balance, reset date
624
687
 
625
688
  ACCOUNT scriptonia status | logout | skill | mcp
689
+ Plans & comments cost 5 credits · queries 1 · adding signal is free.
626
690
  `);
627
691
  }
628
692
 
@@ -798,6 +862,7 @@ async function plan(args) {
798
862
  body: JSON.stringify({ issue, repo_summary: repoSummary }),
799
863
  });
800
864
  const data = await res.json();
865
+ if (res.status === 402) failCredits(data);
801
866
  if (!res.ok) fail(`plan failed (${res.status}): ${JSON.stringify(data).slice(0, 200)}`);
802
867
  writePlan(cfg, data, flags);
803
868
  }
@@ -817,7 +882,13 @@ function writePlan(cfg, data, flags) {
817
882
  console.log(`\n ${C.g}✓${C.r} Plan ${C.b}${data.slug}${C.r} ${C.dim}v${data.version}${C.r} → ${C.v}${out}${C.r}`);
818
883
  console.log(` ${C.dim}${(data.sources ?? []).length} cited signal(s)${data.contradictions ? `, ${data.contradictions} unresolved contradiction(s)` : ""}${C.r}`);
819
884
  console.log(`\n ${C.b}Hand it to your agent:${C.r} ${C.g}claude "execute PLAN.md"${C.r} ${C.dim}(or codex · opencode · hermes)${C.r}`);
820
- console.log(` ${C.dim}refine:${C.r} ${C.g}scriptonia comment plan/${data.slug} "…"${C.r} ${C.dim}(rewrites PLAN.md)${C.r}\n`);
885
+ console.log(` ${C.dim}refine:${C.r} ${C.g}scriptonia comment plan/${data.slug} "…"${C.r} ${C.dim}(rewrites PLAN.md)${C.r}`);
886
+ if (data.credits) {
887
+ const cr = data.credits;
888
+ const low = cr.cap > 0 && cr.cap - cr.used <= Math.max(2, Math.round(cr.cap * 0.2));
889
+ console.log(` ${low ? C.w : C.dim}${cr.used}/${cr.cap} credits used this month${low ? " — top up: sathwik07@scriptonia.dev" : ""}${C.r}`);
890
+ }
891
+ console.log("");
821
892
  }
822
893
 
823
894
  // ── repo scan ─────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scriptonia",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Turn customer signal into executable plans your coding agent runs. Issue in, PLAN.md out.",
5
5
  "type": "module",
6
6
  "bin": {