scaledthought 0.2.2 → 0.3.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 (3) hide show
  1. package/README.md +8 -8
  2. package/dist/index.js +774 -61
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,7 +7,7 @@ JSON when stdout isn't a terminal, and every error is one JSON line on stderr wi
7
7
  Agents: the full setup protocol is at <https://scaledthought.com/llms.txt>.
8
8
 
9
9
  ```bash
10
- npx -y scaledthought@latest init --region us # or: --region ca (data stays in Canada)
10
+ npx -y scaledthought@0.3 init --region us # or: --region ca (data stays in Canada)
11
11
  ```
12
12
 
13
13
  `init` creates an account, writes `ST_API_KEY` and `ST_BASE_URL` to `./.env` (mode 0600), adds `.env` to
@@ -30,9 +30,9 @@ Inference is prepaid, and a new account has no credits: until your human pays, c
30
30
  `402 insufficient_credits`. So the first thing after `init` is a top-up your human approves:
31
31
 
32
32
  ```bash
33
- npx -y scaledthought@latest billing topup --amount-usd 5 # prints approval_url and topup_id
33
+ npx -y scaledthought@0.3 billing topup --amount-usd 20 # prints approval_url and topup_id
34
34
  # show your human the approval_url, exactly as printed; they pay by card
35
- npx -y scaledthought@latest billing wait # returns once they've paid; on topup_pending run it again
35
+ npx -y scaledthought@0.3 billing wait # returns once they've paid; on topup_pending run it again
36
36
  ```
37
37
 
38
38
  `billing wait` returns within about 90 seconds (`--timeout-s` to change it), under Claude Code's default 2-minute
@@ -40,11 +40,11 @@ shell timeout. Prefer it to `billing topup --wait`: an agent's shell tool usuall
40
40
  ends, so the link would reach the human only after the payment it is waiting for.
41
41
 
42
42
  ```bash
43
- npx -y scaledthought@latest models --live # models serving in your region now
44
- npx -y scaledthought@latest chat -m auto "Reply with OK" # one real completion
45
- npx -y scaledthought@latest mcp # `claude mcp add …` command + JSON config
46
- npx -y scaledthought@latest account # plan, region, spend
47
- npx -y scaledthought@latest account ad-measurement off # opt out of ad measurement
43
+ npx -y scaledthought@0.3 models --live # models serving in your region now
44
+ npx -y scaledthought@0.3 chat -m auto "Reply with OK" # one real completion
45
+ npx -y scaledthought@0.3 mcp # `claude mcp add …` command + JSON config
46
+ npx -y scaledthought@0.3 account # plan, region, spend
47
+ npx -y scaledthought@0.3 account ad-measurement off # opt out of ad measurement
48
48
  ```
49
49
 
50
50
  Other commands: `keys list|create|rotate|revoke|revoke-self` (`keys create --expires-in 30d --scopes inference --child`, `keys rotate <id> --grace 24h`), `usage`, `billing status|topup|wait|auto-reload|transactions`, `batches …`,
package/dist/index.js CHANGED
@@ -553,7 +553,7 @@ import { join } from "node:path";
553
553
  // package.json
554
554
  var package_default = {
555
555
  name: "scaledthought",
556
- version: "0.2.2",
556
+ version: "0.3.0",
557
557
  description: "Headless open-model inference, run by your agent. Prepaid, pay per token. US or Canada.",
558
558
  license: "MIT",
559
559
  keywords: [
@@ -631,9 +631,20 @@ function readDotEnv(cwd) {
631
631
  }
632
632
  return out;
633
633
  }
634
- var CREDITS_HINT = "This account has no credits for this request (inference is prepaid). Run `npx -y scaledthought@latest billing topup --amount-usd 5` (creating it charges nothing) and give your human its message_for_human. If it printed opened_in_browser: true, run `npx -y scaledthought@latest billing wait` in the same turn (again on topup_pending, up to 15 minutes): it returns as soon as they pay. Otherwise end your turn with the message and run billing wait once they're back. Then retry. Don't retry before that or create another account.";
634
+ var CREDITS_HINT = "This account has no credits for this request (inference is prepaid). Run `npx -y scaledthought@0.3 billing topup --amount-usd 20` (creating it charges nothing) and give your human its message_for_human. If it printed opened_in_browser: true, run `npx -y scaledthought@0.3 billing wait` in the same turn (again on topup_pending, up to 15 minutes): it returns as soon as they pay. Otherwise end your turn with the message and run billing wait once they're back. Then retry. Don't retry before that or create another account.";
635
635
  var HINTS = {
636
- no_key: "Run `npx -y scaledthought@latest init --region us|ca` in this directory, or set ST_API_KEY.",
636
+ no_key: "For an existing account run `npx -y scaledthought@0.3 login`; for a new one, `init --region us|ca`. Or set ST_API_KEY.",
637
+ key_exists: "This folder already has a key (ST_API_KEY in ./.env or the environment). Use it, or pass --force to replace it.",
638
+ authorization_pending: "Your human hasn't typed the code yet. If they have the page open, run `npx -y scaledthought@0.3 login --wait` again right away (codes last 10 minutes); otherwise remind them of the code and the URL.",
639
+ access_denied: "Your human denied the connection. Ask them before running login again.",
640
+ expired_token: "The code expired. Run `npx -y scaledthought@0.3 login` again for a new one.",
641
+ already_claimed: "This code was already used. Run `npx -y scaledthought@0.3 login` again for a new one.",
642
+ invalid_connect_code: "A connect line works once, for 15 minutes, and its code starts with stl_. Ask your human for a new line from the dashboard.",
643
+ too_many_failed_claims: "Too many failed connect-line claims in this region. Wait a few minutes, then retry.",
644
+ invalid_device_code: "Run `npx -y scaledthought@0.3 login` again for a new code.",
645
+ no_login_pending: "Run `npx -y scaledthought@0.3 login` first: it prints a code for your human.",
646
+ account_not_found: "Pass a saved account's id or exact name (see choose_account from `init`), or connect another with `login`.",
647
+ no_saved_accounts: "No account is saved on this machine. Run `npx -y scaledthought@0.3 login` to connect an existing one.",
637
648
  bad_key: "ST_API_KEY must look like st_{us|ca}_{live|test}_<40 chars>. Re-run init in a directory without one.",
638
649
  invalid_api_key: "The key is missing, revoked or mistyped. Check ST_API_KEY in .env; create a new one with `keys create`.",
639
650
  missing_api_key: "Send the key as `Authorization: Bearer $ST_API_KEY`.",
@@ -647,12 +658,12 @@ var HINTS = {
647
658
  credits_required_for_fine_tuning: `Fine-tuning is paid from prepaid credits. ${CREDITS_HINT}`,
648
659
  approval_required: "This change needs your human's approval. Send them the approval_url from `billing topup`; disabling or lowering auto-reload needs none.",
649
660
  daily_cap_exceeded: "An operator-set daily cap on this account is reached. It resets at 00:00 UTC. Tell your human.",
650
- free_tier_limit: "Accounts that haven't bought credits have lower limits. The first credit purchase lifts them: run `billing topup --amount-usd 5` and show your human the approval_url.",
661
+ free_tier_limit: "Accounts that haven't bought credits have lower limits. The first credit purchase lifts them: run `billing topup --amount-usd 20` and show your human the approval_url.",
651
662
  free_tier_key_limit: "Accounts that haven't bought credits have at most 5 active keys. Revoke one with `keys revoke <id>`, or buy credits with `billing topup`.",
652
663
  too_many_pending_topups: "Two approval links are still unpaid. Ask your human to use one (see `billing status` pending_topups), or wait for them to expire.",
653
664
  daily_topup_limit: "Top-ups are limited to $2,000 per account per rolling 24 hours (open links and auto-reloads included). Use a smaller amount (see available_usd) or wait.",
654
- billing_upgrade_removed: "Run `billing topup --amount-usd 5` and show your human the approval_url.",
655
- gone: "This endpoint was removed. For credits use `billing topup --amount-usd 5`.",
665
+ billing_upgrade_removed: "Run `billing topup --amount-usd 20` and show your human the approval_url.",
666
+ gone: "This endpoint was removed. For credits use `billing topup --amount-usd 20`.",
656
667
  fine_tuning_requires_self_hosted: "This base model can't be fine-tuned in your region (it isn't served on our own GPUs). Choose another base model.",
657
668
  rate_limit_exceeded: "Wait the number of seconds in retry_after, then retry.",
658
669
  session_limit_exceeded: "Too many realtime sessions are open on this key. Close one, then retry.",
@@ -905,8 +916,8 @@ async function speak(ctx, o) {
905
916
 
906
917
  // src/commands.ts
907
918
  import { spawn } from "node:child_process";
908
- import { chmodSync, existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
909
- import { join as join3, resolve as resolve2 } from "node:path";
919
+ import { chmodSync as chmodSync3, existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
920
+ import { join as join5, resolve as resolve2 } from "node:path";
910
921
 
911
922
  // ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/util.js
912
923
  function getEnumValues(entries) {
@@ -9240,28 +9251,516 @@ var TERMS_URL = `${LEGAL_SITE}${LEGAL_PATHS.terms}`;
9240
9251
  var PRIVACY_URL = `${LEGAL_SITE}${LEGAL_PATHS.privacy}`;
9241
9252
  var ACCEPTABLE_USE_URL = `${LEGAL_SITE}${LEGAL_PATHS.acceptableUse}`;
9242
9253
  var REFUND_POLICY_URL = `${LEGAL_SITE}${LEGAL_PATHS.creditsAndRefunds}`;
9243
- var TERMS_NOTICE = `By creating this account you accept the ScaledThought Terms of Service (${TERMS_URL}) and Privacy Policy ` + `(${PRIVACY_URL}) on behalf of the person or organisation you act for, who is the customer.`;
9254
+ var TERMS_NOTICE = `Nothing has been accepted on your behalf. You accept the ScaledThought Terms of Service (${TERMS_URL}) and the ` + `Credits & Refunds policy (${REFUND_POLICY_URL}) yourself when you buy credits. Privacy Policy: ${PRIVACY_URL}.`;
9255
+
9256
+ // src/connect.ts
9257
+ import { chmodSync as chmodSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
9258
+ import { hostname } from "node:os";
9259
+ import { basename as basename2, join as join4 } from "node:path";
9260
+
9261
+ // src/credentials.ts
9262
+ import { spawnSync } from "node:child_process";
9263
+ import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
9264
+ import { join as join3 } from "node:path";
9265
+ var SERVICE = "scaledthought";
9266
+ var slot = (a) => `${a.region}:${a.account_id}`;
9267
+ var SLOT_RE = /^(us|ca):acct_[0-9A-Za-z]{1,40}$/;
9268
+ function writePrivate(file, data) {
9269
+ writeFileSync(file, `${JSON.stringify(data, null, 2)}
9270
+ `, { mode: 384 });
9271
+ chmodSync(file, 384);
9272
+ }
9273
+ function readJson(file, fallback) {
9274
+ try {
9275
+ return existsSync2(file) ? JSON.parse(readFileSync3(file, "utf8")) : fallback;
9276
+ } catch {
9277
+ return fallback;
9278
+ }
9279
+ }
9280
+ function fileBackend(dir) {
9281
+ const file = join3(dir, "credentials.json");
9282
+ const all = () => readJson(file, {});
9283
+ return {
9284
+ where: "~/.config/scaledthought/credentials.json (0600)",
9285
+ get: (s) => all()[s] ?? null,
9286
+ set: (s, key) => {
9287
+ writePrivate(file, { ...all(), [s]: key });
9288
+ return true;
9289
+ },
9290
+ del: (s) => {
9291
+ const a = all();
9292
+ delete a[s];
9293
+ writePrivate(file, a);
9294
+ }
9295
+ };
9296
+ }
9297
+ function keychainBackend() {
9298
+ return {
9299
+ where: "macOS Keychain (service scaledthought)",
9300
+ get: (s) => {
9301
+ const r = spawnSync("security", ["find-generic-password", "-s", SERVICE, "-a", s, "-w"], { encoding: "utf8" });
9302
+ return r.status === 0 ? r.stdout.trim() || null : null;
9303
+ },
9304
+ set: (s, key) => spawnSync("security", ["-i"], {
9305
+ input: `add-generic-password -U -s ${SERVICE} -a ${s} -l ScaledThought -w ${key}
9306
+ `,
9307
+ encoding: "utf8"
9308
+ }).status === 0,
9309
+ del: (s) => {
9310
+ spawnSync("security", ["delete-generic-password", "-s", SERVICE, "-a", s], { encoding: "utf8" });
9311
+ }
9312
+ };
9313
+ }
9314
+ function secretToolBackend() {
9315
+ return {
9316
+ where: "the Secret Service keyring (secret-tool, service scaledthought)",
9317
+ get: (s) => {
9318
+ const r = spawnSync("secret-tool", ["lookup", "service", SERVICE, "account", s], { encoding: "utf8" });
9319
+ return r.status === 0 ? r.stdout.trim() || null : null;
9320
+ },
9321
+ set: (s, key) => spawnSync("secret-tool", ["store", `--label=ScaledThought ${s}`, "service", SERVICE, "account", s], {
9322
+ input: key,
9323
+ encoding: "utf8"
9324
+ }).status === 0,
9325
+ del: (s) => {
9326
+ spawnSync("secret-tool", ["clear", "service", SERVICE, "account", s], { encoding: "utf8" });
9327
+ }
9328
+ };
9329
+ }
9330
+ var has = (bin) => spawnSync(bin, ["--help"], { stdio: "ignore" }).error === undefined;
9331
+ function storeAt(dir, os) {
9332
+ const indexFile = join3(dir, "accounts.json");
9333
+ const file = fileBackend(dir);
9334
+ const index = () => readJson(indexFile, []).filter((e) => SLOT_RE.test(slot(e)));
9335
+ const ensureDir = () => mkdirSync(dir, { recursive: true, mode: 448 });
9336
+ let where = os?.where ?? file.where;
9337
+ return {
9338
+ get where() {
9339
+ return where;
9340
+ },
9341
+ list: () => index().map(({ in: _in, ...a }) => a),
9342
+ getKey: (a) => {
9343
+ const e = index().find((x) => slot(x) === slot(a));
9344
+ if (!e)
9345
+ return null;
9346
+ return (e.in === "os" && os ? os.get(slot(a)) : file.get(slot(a))) ?? null;
9347
+ },
9348
+ save: (a, key) => {
9349
+ ensureDir();
9350
+ let into = "file";
9351
+ if (os?.set(slot(a), key))
9352
+ into = "os";
9353
+ else
9354
+ file.set(slot(a), key);
9355
+ where = into === "os" && os ? os.where : file.where;
9356
+ writePrivate(indexFile, [...index().filter((x) => slot(x) !== slot(a)), { ...a, in: into }]);
9357
+ },
9358
+ remove: (a) => {
9359
+ const e = index().find((x) => slot(x) === slot(a));
9360
+ if (!e)
9361
+ return;
9362
+ if (e.in === "os" && os)
9363
+ os.del(slot(a));
9364
+ else
9365
+ file.del(slot(a));
9366
+ writePrivate(indexFile, index().filter((x) => slot(x) !== slot(a)));
9367
+ },
9368
+ touch: (a) => {
9369
+ const all = index();
9370
+ const e = all.find((x) => slot(x) === slot(a));
9371
+ if (!e)
9372
+ return;
9373
+ e.last_used_at = new Date().toISOString();
9374
+ writePrivate(indexFile, all);
9375
+ }
9376
+ };
9377
+ }
9378
+ function machineStore(env, platform = process.platform) {
9379
+ const mode = env.ST_CREDENTIAL_STORE;
9380
+ const home = env.HOME || env.USERPROFILE;
9381
+ if (mode === "off" || !home)
9382
+ return null;
9383
+ const dir = join3(env.XDG_CONFIG_HOME || join3(home, ".config"), "scaledthought");
9384
+ let os = null;
9385
+ if (mode !== "file") {
9386
+ if (platform === "darwin" && existsSync2("/usr/bin/security"))
9387
+ os = keychainBackend();
9388
+ else if (platform === "linux" && env.DBUS_SESSION_BUS_ADDRESS && has("secret-tool"))
9389
+ os = secretToolBackend();
9390
+ }
9391
+ return storeAt(dir, os);
9392
+ }
9393
+
9394
+ // src/connect.ts
9395
+ var NPX = "npx -y scaledthought@0.3";
9396
+ var LOGIN_DIR = ".scaledthought";
9397
+ var LOGIN_FILE = join4(LOGIN_DIR, "login.json");
9398
+ var KEY_SAVED_TO = ".env (ST_API_KEY, ST_BASE_URL)";
9399
+ var CONNECT_CODE_RE = /^stl_[A-Za-z0-9_-]{22,64}$/;
9400
+ var PROJECT_SCOPES = ["inference", "read", "keys", "billing"];
9401
+ var storeOf = (ctx) => ctx.store !== undefined ? ctx.store : machineStore(ctx.env);
9402
+ function defaultClientLabel(env, host = hostname()) {
9403
+ const keys = Object.keys(env).filter((k) => env[k] !== undefined && env[k] !== "");
9404
+ const tool = env.CLAUDECODE ? "Claude Code" : keys.some((k) => k.startsWith("CODEX_")) ? "Codex" : keys.some((k) => k.startsWith("CURSOR_")) ? "Cursor" : "CLI";
9405
+ return `${tool} on ${host.replace(/\.local$/, "")}`.slice(0, 80);
9406
+ }
9407
+ function readLoginState(cwd) {
9408
+ const f = join4(cwd, LOGIN_FILE);
9409
+ if (!existsSync3(f))
9410
+ return null;
9411
+ try {
9412
+ const s = JSON.parse(readFileSync4(f, "utf8"));
9413
+ return typeof s.device_code === "string" && (s.region === "us" || s.region === "ca") ? s : null;
9414
+ } catch {
9415
+ return null;
9416
+ }
9417
+ }
9418
+ function writeLoginState(cwd, s) {
9419
+ mkdirSync2(join4(cwd, LOGIN_DIR), { recursive: true, mode: 448 });
9420
+ const f = join4(cwd, LOGIN_FILE);
9421
+ writeFileSync2(f, `${JSON.stringify(s)}
9422
+ `, { mode: 384 });
9423
+ chmodSync2(f, 384);
9424
+ ensureIgnored(cwd, `${LOGIN_DIR}/`);
9425
+ }
9426
+ var clearLoginState = (cwd) => rmSync(join4(cwd, LOGIN_FILE), { force: true });
9427
+ function ensureIgnored(cwd, entry) {
9428
+ const gi = join4(cwd, ".gitignore");
9429
+ const text = existsSync3(gi) ? readFileSync4(gi, "utf8") : "";
9430
+ const bare = entry.replace(/\/$/, "");
9431
+ if (text.split(/\r?\n/).some((l) => [entry, bare, `/${entry}`, `/${bare}`].includes(l.trim())))
9432
+ return;
9433
+ const sep = text === "" || text.endsWith(`
9434
+ `) ? "" : `
9435
+ `;
9436
+ writeFileSync2(gi, `${text}${sep}${entry}
9437
+ `);
9438
+ }
9439
+ function refuseExistingKey(ctx, force) {
9440
+ if (force)
9441
+ return;
9442
+ const where = ctx.env.ST_API_KEY ? "the ST_API_KEY environment variable" : readDotEnv(ctx.cwd).ST_API_KEY ? "./.env" : null;
9443
+ if (where)
9444
+ throw new CliError("key_exists", `This folder already has a ScaledThought key (${where}). Nothing was changed.`);
9445
+ }
9446
+ function saveToStore(ctx, a, key, noSave) {
9447
+ if (noSave)
9448
+ return false;
9449
+ const store = storeOf(ctx);
9450
+ if (!store)
9451
+ return false;
9452
+ try {
9453
+ store.save({ ...a, saved_at: new Date().toISOString() }, key);
9454
+ return store.where;
9455
+ } catch {
9456
+ return false;
9457
+ }
9458
+ }
9459
+ function finish(ctx, c, o) {
9460
+ writeEnv(ctx.cwd, { ST_API_KEY: c.api_key, ST_BASE_URL: c.base_url });
9461
+ ensureGitignored(ctx.cwd);
9462
+ const saved = saveToStore(ctx, { region: c.region, account_id: c.account_id, account_name: c.account_name, base_url: c.base_url }, c.api_key, o.noSave);
9463
+ clearLoginState(ctx.cwd);
9464
+ return {
9465
+ status: "connected",
9466
+ account_id: c.account_id,
9467
+ account_name: c.account_name,
9468
+ region: c.region,
9469
+ key_name: c.key_name,
9470
+ key_saved_to: KEY_SAVED_TO,
9471
+ saved_to_credential_store: saved !== false,
9472
+ ...saved ? { credential_store: saved } : {}
9473
+ };
9474
+ }
9475
+ var claim2 = (ctx, region, body) => api(ctx, "POST", "/v1/device/token", { auth: false, region, body });
9476
+ async function login(ctx, o = {}) {
9477
+ refuseExistingKey(ctx, o.force);
9478
+ const region = o.region ?? "us";
9479
+ const label = (o.label ?? defaultClientLabel(ctx.env)).slice(0, 80);
9480
+ const r = await api(ctx, "POST", "/v1/device/codes", { auth: false, region, body: { client_label: label, origin: "agent" } });
9481
+ writeLoginState(ctx.cwd, {
9482
+ device_code: r.device_code,
9483
+ region,
9484
+ origin: "agent",
9485
+ user_code: r.user_code,
9486
+ created_at: new Date().toISOString()
9487
+ });
9488
+ const noOpen = o.noOpen || ctx.env.ST_NO_BROWSER === "1";
9489
+ const opened = !noOpen && await (o.open ?? openInBrowser)(r.verification_url);
9490
+ return {
9491
+ status: "pending",
9492
+ user_code: r.user_code,
9493
+ verification_url: r.verification_url,
9494
+ opened_in_browser: opened,
9495
+ expires_in: r.expires_in,
9496
+ client_label: label,
9497
+ message_for_human: opened ? `To connect this agent to your ScaledThought account, I've opened ${r.verification_url}. Sign in and type the code ${r.user_code}.` : `To connect this agent to your ScaledThought account, open ${r.verification_url}, sign in and type the code ${r.user_code}.`,
9498
+ next_command: `${NPX} login --wait`,
9499
+ next_steps: [
9500
+ opened ? `Tell your human message_for_human, then in the same turn run: ${NPX} login --wait. On authorization_pending run it again, for up to 10 minutes in all.` : `Give your human message_for_human and end your turn. When they say they've typed the code, run: ${NPX} login --wait.`,
9501
+ "Never put the code in a link: the human types it on the page."
9502
+ ]
9503
+ };
9504
+ }
9505
+ async function loginWait(ctx, o = {}) {
9506
+ const s = readLoginState(ctx.cwd);
9507
+ if (s?.origin !== "agent")
9508
+ throw new CliError("no_login_pending", "No login is waiting in this folder.");
9509
+ refuseExistingKey(ctx, o.force);
9510
+ const deadline = Date.now() + (o.timeoutS ?? 90) * 1000;
9511
+ for (;; ) {
9512
+ try {
9513
+ return finish(ctx, await claim2(ctx, s.region, { device_code: s.device_code }), o);
9514
+ } catch (e) {
9515
+ if (!(e instanceof CliError))
9516
+ throw e;
9517
+ if (e.code !== "authorization_pending") {
9518
+ if (["expired_token", "access_denied", "already_claimed", "invalid_device_code"].includes(e.code))
9519
+ clearLoginState(ctx.cwd);
9520
+ throw e;
9521
+ }
9522
+ if (Date.now() >= deadline)
9523
+ throw new CliError("authorization_pending", "Your human hasn't approved the code yet.", undefined, {
9524
+ user_code: s.user_code ?? null
9525
+ });
9526
+ }
9527
+ await new Promise((r) => setTimeout(r, o.pollMs ?? 5000));
9528
+ }
9529
+ }
9530
+ async function loginWithCode(ctx, o) {
9531
+ const code = o.code.trim();
9532
+ if (!CONNECT_CODE_RE.test(code))
9533
+ throw new CliError("invalid_connect_code", "That isn't a connect line's code (it starts with stl_). Copy the whole line from the dashboard.");
9534
+ refuseExistingKey(ctx, o.force);
9535
+ const region = o.region ?? "us";
9536
+ const label = (o.label ?? defaultClientLabel(ctx.env)).slice(0, 80);
9537
+ return finish(ctx, await claim2(ctx, region, { connect_code: code, client_label: label }), o);
9538
+ }
9539
+ var usdOf = (b) => b && typeof b.balance_usd === "number" ? b.balance_usd : null;
9540
+ var withKey = (ctx, key) => ({ ...ctx, env: { ...ctx.env, ST_API_KEY: key } });
9541
+ async function projectKey(ctx, store, a) {
9542
+ const key = store.getKey(a);
9543
+ if (!key || regionOfKey(key) !== a.region)
9544
+ return null;
9545
+ const folder = basename2(ctx.cwd) || "project";
9546
+ const name = `project: ${folder}`.slice(0, 80);
9547
+ let created;
9548
+ try {
9549
+ created = await api(withKey(ctx, key), "POST", "/v1/keys", {
9550
+ body: {
9551
+ name,
9552
+ child: true,
9553
+ scopes: PROJECT_SCOPES,
9554
+ lives_in: `env:${hostname()}:${ctx.cwd}`.slice(0, 300)
9555
+ }
9556
+ });
9557
+ } catch (e) {
9558
+ if (e instanceof CliError && ["invalid_api_key", "key_expired", "account_suspended"].includes(e.code)) {
9559
+ store.remove(a);
9560
+ return null;
9561
+ }
9562
+ if (e instanceof CliError && (e.code === "insufficient_scope" || e.code === "scope_not_allowed")) {
9563
+ writeEnv(ctx.cwd, { ST_API_KEY: key, ST_BASE_URL: a.base_url });
9564
+ ensureGitignored(ctx.cwd);
9565
+ store.touch(a);
9566
+ return { key, key_id: null, key_name: "this machine's saved key (it can't make project keys)" };
9567
+ }
9568
+ throw e;
9569
+ }
9570
+ writeEnv(ctx.cwd, { ST_API_KEY: created.api_key, ST_BASE_URL: a.base_url });
9571
+ ensureGitignored(ctx.cwd);
9572
+ store.touch(a);
9573
+ return { key: created.api_key, key_id: created.id, key_name: created.name };
9574
+ }
9575
+ var label = (a, balance) => balance == null ? a.account_name : `${a.account_name} ($${balance.toFixed(2)})`;
9576
+ async function reuseSavedAccount(ctx, o) {
9577
+ const store = storeOf(ctx);
9578
+ const saved = (store?.list() ?? []).filter((a) => !o.region || a.region === o.region);
9579
+ if (!store || saved.length === 0) {
9580
+ if (o.account)
9581
+ throw new CliError("no_saved_accounts", `No account is saved on this machine${o.region ? ` in ${o.region}` : ""}.`);
9582
+ return null;
9583
+ }
9584
+ let pick;
9585
+ if (o.account) {
9586
+ const want = o.account.trim().toLowerCase();
9587
+ pick = saved.find((a) => a.account_id.toLowerCase() === want) ?? saved.find((a) => a.account_name.toLowerCase() === want);
9588
+ if (!pick)
9589
+ throw new CliError("account_not_found", `No saved account named or numbered "${o.account}".`, undefined, {
9590
+ saved_accounts: saved.map((a) => ({ account_id: a.account_id, name: a.account_name, region: a.region }))
9591
+ });
9592
+ } else if (saved.length > 1) {
9593
+ const rows = await Promise.all(saved.map(async (a) => {
9594
+ const key = store.getKey(a);
9595
+ const b = key ? await api(withKey(ctx, key), "GET", "/v1/billing").catch(() => null) : null;
9596
+ return { a, balance: usdOf(b) };
9597
+ }));
9598
+ rows.sort((x, y) => (y.a.last_used_at ?? y.a.saved_at).localeCompare(x.a.last_used_at ?? x.a.saved_at));
9599
+ const names = rows.map((r) => label(r.a, r.balance));
9600
+ return {
9601
+ status: "choose_account",
9602
+ choose_account: rows.map((r) => ({
9603
+ account_id: r.a.account_id,
9604
+ name: r.a.account_name,
9605
+ region: r.a.region,
9606
+ balance_usd: r.balance,
9607
+ last_used: (r.a.last_used_at ?? r.a.saved_at).slice(0, 10)
9608
+ })),
9609
+ message_for_human: `Which ScaledThought account should this repo use: ${names.slice(0, -1).join(", ")} or ${names.at(-1)}? Or a new one for separate billing?`,
9610
+ next_steps: [
9611
+ "Ask your human message_for_human and end your turn. Nothing was created.",
9612
+ `Then run: ${NPX} init --account "<name or account_id>" (or ${NPX} init --new-account for a separate, new account).`
9613
+ ]
9614
+ };
9615
+ } else
9616
+ pick = saved[0];
9617
+ const made = await projectKey(ctx, store, pick);
9618
+ if (!made) {
9619
+ if (o.account)
9620
+ throw new CliError("account_not_found", `The key saved for ${pick.account_name} no longer works, so it was forgotten.`, `Connect it again with \`${NPX} login\`.`);
9621
+ return reuseSavedAccount(ctx, o);
9622
+ }
9623
+ const billing = await api(withKey(ctx, made.key), "GET", "/v1/billing").catch(() => null);
9624
+ return {
9625
+ already_initialized: false,
9626
+ reused_account: true,
9627
+ account_id: pick.account_id,
9628
+ account_name: pick.account_name,
9629
+ region: pick.region,
9630
+ base_url: pick.base_url,
9631
+ key_name: made.key_name,
9632
+ key_id: made.key_id,
9633
+ key_saved_to: KEY_SAVED_TO,
9634
+ saved_to_credential_store: true,
9635
+ note: `This folder uses ${pick.account_name}, the account saved on this machine, with its own project key. To switch: ${NPX} use "<name>". For a separate account: ${NPX} init --new-account.`,
9636
+ ...await liveSummary(withKey(ctx, made.key), pick.region, usdOf(billing))
9637
+ };
9638
+ }
9639
+ async function afterNewAccount(ctx, a, o) {
9640
+ const saved = saveToStore(ctx, {
9641
+ region: a.region,
9642
+ account_id: a.account_id,
9643
+ account_name: a.name?.trim() || `Account ${a.account_id.slice(-6)}`,
9644
+ base_url: a.base_url
9645
+ }, a.api_key, o.noSave);
9646
+ let existingCode = null;
9647
+ try {
9648
+ const r = await api(withKey(ctx, a.api_key), "POST", "/v1/device/codes", { body: { origin: "init", client_label: (o.label ?? defaultClientLabel(ctx.env)).slice(0, 80) } });
9649
+ if (typeof r.device_code === "string") {
9650
+ writeLoginState(ctx.cwd, {
9651
+ device_code: r.device_code,
9652
+ region: a.region,
9653
+ origin: "init",
9654
+ user_code: typeof r.user_code === "string" ? r.user_code : undefined,
9655
+ new_account_id: a.account_id,
9656
+ created_at: new Date().toISOString()
9657
+ });
9658
+ existingCode = typeof r.user_code === "string" ? r.user_code : null;
9659
+ }
9660
+ } catch {}
9661
+ return { saved, existingCode };
9662
+ }
9663
+ function existingAccountCode(cwd) {
9664
+ const s = readLoginState(cwd);
9665
+ return s?.origin === "init" && s.user_code ? s.user_code : null;
9666
+ }
9667
+ var existingAccountHint = (code) => `If you already have a ScaledThought account, choose "Use it instead" on that page and type the code ${code}: I'll use that account and nothing is charged.`;
9668
+ async function useAccount(ctx, o) {
9669
+ const store = storeOf(ctx);
9670
+ const saved = store?.list() ?? [];
9671
+ if (!store || saved.length === 0)
9672
+ throw new CliError("no_saved_accounts", "No account is saved on this machine.");
9673
+ const want = o.target.trim().toLowerCase();
9674
+ const pick = saved.find((a) => a.account_id.toLowerCase() === want) ?? saved.find((a) => a.account_name.toLowerCase() === want);
9675
+ if (!pick)
9676
+ throw new CliError("account_not_found", `No saved account named or numbered "${o.target}".`, undefined, {
9677
+ saved_accounts: saved.map((a) => ({ account_id: a.account_id, name: a.account_name, region: a.region }))
9678
+ });
9679
+ const oldKey = readDotEnv(ctx.cwd).ST_API_KEY;
9680
+ if (oldKey) {
9681
+ const cur = await api(withKey(ctx, oldKey), "GET", "/v1/account").catch(() => null);
9682
+ if (cur?.account_id === pick.account_id)
9683
+ return { status: "unchanged", account_id: pick.account_id, account_name: pick.account_name };
9684
+ }
9685
+ const made = await projectKey(ctx, store, pick);
9686
+ if (!made)
9687
+ throw new CliError("account_not_found", `The key saved for ${pick.account_name} no longer works, so it was forgotten.`, `Connect it again with \`${NPX} login --force\`.`);
9688
+ let oldRevoked = false;
9689
+ if (oldKey && !saved.some((a) => store.getKey(a) === oldKey)) {
9690
+ oldRevoked = await api(withKey(ctx, oldKey), "DELETE", "/v1/keys/self").then(() => true, () => false);
9691
+ }
9692
+ return {
9693
+ status: "switched",
9694
+ account_id: pick.account_id,
9695
+ account_name: pick.account_name,
9696
+ region: pick.region,
9697
+ key_name: made.key_name,
9698
+ key_saved_to: KEY_SAVED_TO,
9699
+ old_key_revoked: oldRevoked,
9700
+ ...ctx.env.ST_API_KEY ? { warnings: ["ST_API_KEY is set in the environment and overrides ./.env: unset it to use the new key."] } : {}
9701
+ };
9702
+ }
9703
+ async function connectedExisting(ctx, o = {}) {
9704
+ const s = readLoginState(ctx.cwd);
9705
+ if (s?.origin !== "init")
9706
+ return null;
9707
+ let c;
9708
+ try {
9709
+ c = await claim2(ctx, s.region, { device_code: s.device_code });
9710
+ } catch (e) {
9711
+ if (e instanceof CliError && ["expired_token", "access_denied", "already_claimed"].includes(e.code))
9712
+ clearLoginState(ctx.cwd);
9713
+ return null;
9714
+ }
9715
+ const done = finish(ctx, c, o);
9716
+ const store = storeOf(ctx);
9717
+ if (c.closed_account && store)
9718
+ store.remove({ region: s.region, account_id: c.closed_account });
9719
+ const billing = await api(withKey(ctx, c.api_key), "GET", "/v1/billing").catch(() => null);
9720
+ const balance = usdOf(billing);
9721
+ return {
9722
+ status: "connected_existing",
9723
+ account_id: c.account_id,
9724
+ account_name: c.account_name,
9725
+ balance_usd: balance,
9726
+ key_name: c.key_name,
9727
+ key_saved_to: KEY_SAVED_TO,
9728
+ saved_to_credential_store: done.saved_to_credential_store,
9729
+ closed_account: c.closed_account ? `${c.closed_account} (new, never paid)` : null,
9730
+ message_for_human: `You already had an account, so I've connected to it instead: ${c.account_name}${balance == null ? "" : `, $${balance.toFixed(2)} left`}. Nothing was charged.`
9731
+ };
9732
+ }
9733
+ async function readCodeFrom(stream) {
9734
+ let text = "";
9735
+ for await (const chunk of stream) {
9736
+ text += typeof chunk === "string" ? chunk : chunk.toString("utf8");
9737
+ if (text.includes(`
9738
+ `) || text.length > 4096)
9739
+ break;
9740
+ }
9741
+ return /\bstl_[A-Za-z0-9_-]+/.exec(text)?.[0] ?? (text.split(/\r?\n/)[0] ?? "").trim();
9742
+ }
9244
9743
 
9245
9744
  // src/commands.ts
9246
- var NPX = "npx -y scaledthought@latest";
9247
- var FIRST_TOPUP_USD = 5;
9248
- var TOPUP_COMMAND = `${NPX} billing topup --amount-usd ${FIRST_TOPUP_USD}`;
9249
- var WAIT_COMMAND = `${NPX} billing wait`;
9745
+ var NPX2 = "npx -y scaledthought@0.3";
9746
+ var FIRST_TOPUP_USD = 20;
9747
+ var TOPUP_COMMAND = `${NPX2} billing topup --amount-usd ${FIRST_TOPUP_USD}`;
9748
+ var WAIT_COMMAND = `${NPX2} billing wait`;
9250
9749
  var WAIT_PATIENCE_MIN = 15;
9251
9750
  var CREDIT_STEPS = [
9252
9751
  `Inference is prepaid and this account has no credits yet: completions return 402 insufficient_credits until your human pays. Do these first, in order:`,
9253
9752
  `1. Run: ${TOPUP_COMMAND}. It opens the checkout page in your human's browser and prints approval_url, topup_id and message_for_human. Creating it charges nothing: don't ask first.`,
9254
- `2. If opened_in_browser is true, the checkout page (where your human accepts the Terms and pays $${FIRST_TOPUP_USD} by card) is already in front of them: tell them so with message_for_human and, in the same turn, run: ${WAIT_COMMAND}. It returns status "paid" with the new balance as soon as they pay; on topup_pending run it again, for up to ${WAIT_PATIENCE_MIN} minutes in all. Don't wait for them to say they've paid.`,
9753
+ `2. If opened_in_browser is true, the funding page (where your human confirms $${FIRST_TOPUP_USD}, can turn on auto-reload, accepts the Terms and pays by card) is already in front of them: tell them so with message_for_human and, in the same turn, run: ${WAIT_COMMAND}. It returns status "paid" with the new balance as soon as they pay; on topup_pending run it again, for up to ${WAIT_PATIENCE_MIN} minutes in all. Don't wait for them to say they've paid.`,
9255
9754
  `3. If opened_in_browser is false (or it's still unpaid after ${WAIT_PATIENCE_MIN} minutes), end your turn with message_for_human: it holds the approval_url exactly as printed. When they're back, run ${WAIT_COMMAND}. Either way, don't retry the call and don't create another account.`,
9256
9755
  "4. Then make the call below."
9257
9756
  ];
9258
9757
  function nextSteps(model, needsCredits) {
9259
9758
  return [
9260
9759
  ...needsCredits ? CREDIT_STEPS : [],
9261
- model ? `Verify with one real completion: ${NPX} chat -m ${model} "Reply with OK" (or POST $ST_BASE_URL/chat/completions with that model)` : `No chat model is live in this region right now. Check again with: ${NPX} models --live`,
9262
- `Or let the router pick: ${NPX} chat -m auto "Reply with OK"`,
9760
+ model ? `Verify with one real completion: ${NPX2} chat -m ${model} "Reply with OK" (or POST $ST_BASE_URL/chat/completions with that model)` : `No chat model is live in this region right now. Check again with: ${NPX2} models --live`,
9761
+ `Or let the router pick: ${NPX2} chat -m auto "Reply with OK"`,
9263
9762
  "Use it from code: any OpenAI SDK with base_url=$ST_BASE_URL and api_key=$ST_API_KEY (both are in .env)",
9264
- `Optional, connect MCP: ${NPX} mcp`,
9763
+ `Optional, connect MCP: ${NPX2} mcp`,
9265
9764
  ...needsCredits ? [] : [`More credits when needed: ${TOPUP_COMMAND} returns an approval_url for your human (inference is prepaid)`]
9266
9765
  ];
9267
9766
  }
@@ -9281,7 +9780,7 @@ async function liveSummary(ctx, region, balanceUsd) {
9281
9780
  return {
9282
9781
  balance_usd: balanceUsd ?? 0,
9283
9782
  needs_credits: needsCredits,
9284
- next_command: needsCredits ? TOPUP_COMMAND : `${NPX} chat -m ${suggested ?? "auto"} "Reply with OK"`,
9783
+ next_command: needsCredits ? TOPUP_COMMAND : `${NPX2} chat -m ${suggested ?? "auto"} "Reply with OK"`,
9285
9784
  newest_models: live.slice(0, 5),
9286
9785
  suggested_model: suggested,
9287
9786
  next_steps: nextSteps(suggested, needsCredits)
@@ -9331,6 +9830,13 @@ async function init(ctx, opts) {
9331
9830
  ...ignored.length ? { warnings: [`${ignored.join(" and ")} apply only to a new account and were not sent.`] } : {}
9332
9831
  };
9333
9832
  }
9833
+ if (opts.account && opts.newAccount)
9834
+ throw new CliError("bad_argument", "Pass --account or --new-account, not both.");
9835
+ if (!opts.newAccount) {
9836
+ const reused = await reuseSavedAccount(ctx, { region: opts.region, account: opts.account });
9837
+ if (reused)
9838
+ return reused;
9839
+ }
9334
9840
  const region = opts.region ?? "us";
9335
9841
  const attribution = signupAttribution(region, opts);
9336
9842
  const acct = await api(ctx, "POST", "/v1/accounts", {
@@ -9346,6 +9852,7 @@ async function init(ctx, opts) {
9346
9852
  });
9347
9853
  writeEnv(ctx.cwd, { ST_API_KEY: acct.api_key, ST_BASE_URL: acct.base_url });
9348
9854
  ensureGitignored(ctx.cwd);
9855
+ const { saved, existingCode } = await afterNewAccount(ctx, { ...acct, name: opts.name }, { noSave: opts.noSave });
9349
9856
  const keyed = { ...ctx, env: { ...ctx.env, ST_API_KEY: acct.api_key } };
9350
9857
  const billing = await api(keyed, "GET", "/v1/billing").catch(() => null);
9351
9858
  return {
@@ -9354,6 +9861,11 @@ async function init(ctx, opts) {
9354
9861
  region: acct.region,
9355
9862
  base_url: acct.base_url,
9356
9863
  key_saved_to: ".env (ST_API_KEY, ST_BASE_URL)",
9864
+ saved_to_credential_store: saved !== false,
9865
+ ...saved ? {
9866
+ credential_store: `${saved}: other folders on this machine reuse this account (init there makes a project key). Opt out with --no-save.`
9867
+ } : {},
9868
+ ...existingCode ? { existing_account_code: existingCode, existing_account_hint: existingAccountHint(existingCode) } : {},
9357
9869
  terms: termsOf(acct),
9358
9870
  ...attribution.source ? { source: acct.source ?? attribution.source } : {},
9359
9871
  ...await liveSummary(keyed, acct.region, balanceOf(billing)),
@@ -9361,26 +9873,26 @@ async function init(ctx, opts) {
9361
9873
  };
9362
9874
  }
9363
9875
  function writeEnv(cwd, vars) {
9364
- const f = join3(cwd, ".env");
9876
+ const f = join5(cwd, ".env");
9365
9877
  const names = new Set(Object.keys(vars));
9366
- const kept = existsSync2(f) ? readFileSync3(f, "utf8").split(/\r?\n/).filter((l) => !names.has(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(l)?.[1] ?? "")) : [];
9878
+ const kept = existsSync4(f) ? readFileSync5(f, "utf8").split(/\r?\n/).filter((l) => !names.has(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(l)?.[1] ?? "")) : [];
9367
9879
  while (kept.length && kept[kept.length - 1].trim() === "")
9368
9880
  kept.pop();
9369
9881
  const lines = [...kept, ...Object.entries(vars).map(([k, v]) => `${k}=${v}`)];
9370
- writeFileSync(f, `${lines.join(`
9882
+ writeFileSync3(f, `${lines.join(`
9371
9883
  `)}
9372
9884
  `, { mode: 384 });
9373
- chmodSync(f, 384);
9885
+ chmodSync3(f, 384);
9374
9886
  }
9375
9887
  function ensureGitignored(cwd) {
9376
- const gi = join3(cwd, ".gitignore");
9377
- const text = existsSync2(gi) ? readFileSync3(gi, "utf8") : "";
9888
+ const gi = join5(cwd, ".gitignore");
9889
+ const text = existsSync4(gi) ? readFileSync5(gi, "utf8") : "";
9378
9890
  if (/^\/?\.env\*?\s*$/m.test(text))
9379
9891
  return;
9380
9892
  const sep = text === "" || text.endsWith(`
9381
9893
  `) ? "" : `
9382
9894
  `;
9383
- writeFileSync(gi, `${text}${sep}.env
9895
+ writeFileSync3(gi, `${text}${sep}.env
9384
9896
  `);
9385
9897
  }
9386
9898
  function isCatalogRow(m) {
@@ -9542,9 +10054,9 @@ async function chat(ctx, o) {
9542
10054
  };
9543
10055
  }
9544
10056
  var billingStatus = async (ctx) => withoutFreeFields(await api(ctx, "GET", "/v1/billing"));
9545
- var TOPUP_LIMITS = { min_usd: 5, max_usd: 2000, daily_max_usd: 2000, auto_reload_monthly_max_usd: 1e4 };
10057
+ var TOPUP_LIMITS = { min_usd: 20, max_usd: 2000, daily_max_usd: 2000 };
9546
10058
  function checkTopupLimits(amountUsd, autoReload) {
9547
- const { min_usd, max_usd, daily_max_usd, auto_reload_monthly_max_usd } = TOPUP_LIMITS;
10059
+ const { min_usd, max_usd, daily_max_usd } = TOPUP_LIMITS;
9548
10060
  const range = (flag, v, lo, hi) => {
9549
10061
  if (v < lo || v > hi)
9550
10062
  throw new CliError("bad_argument", `--${flag} must be between ${lo} and ${hi.toLocaleString("en-US")}, got ${v}.`, `Top-ups are $${min_usd} to $${max_usd.toLocaleString("en-US")} each, and at most $${daily_max_usd.toLocaleString("en-US")} per account per rolling 24 hours (auto-reloads included).`);
@@ -9553,9 +10065,9 @@ function checkTopupLimits(amountUsd, autoReload) {
9553
10065
  if (!autoReload)
9554
10066
  return;
9555
10067
  range("auto-reload-amount", autoReload.amount_usd, min_usd, max_usd);
9556
- range("auto-reload-below", autoReload.threshold_usd, 0.01, max_usd);
9557
- if (autoReload.monthly_limit_usd !== undefined)
9558
- range("auto-reload-monthly-max", autoReload.monthly_limit_usd, autoReload.amount_usd, auto_reload_monthly_max_usd);
10068
+ range("auto-reload-below", autoReload.threshold_usd, 5, max_usd);
10069
+ if (autoReload.monthly_limit_usd != null)
10070
+ range("auto-reload-monthly-max", autoReload.monthly_limit_usd, autoReload.amount_usd, Number.POSITIVE_INFINITY);
9559
10071
  }
9560
10072
  var TOPUP_DONE = new Set(["paid", "expired", "failed", "canceled"]);
9561
10073
  function topupNextSteps(topupId, amountUsd, opened, autoReload) {
@@ -9595,32 +10107,87 @@ async function billingTopup(ctx, o) {
9595
10107
  });
9596
10108
  const noOpen = o.noOpen || ctx.env.ST_NO_BROWSER === "1";
9597
10109
  const opened = !noOpen && !!created.approval_url && await (o.open ?? openInBrowser)(created.approval_url);
10110
+ const existing = existingAccountCode(ctx.cwd);
10111
+ const message = messageForHuman(created.approval_url, o.amountUsd, o.autoReload, opened);
9598
10112
  return {
9599
10113
  status: "pending",
9600
10114
  ...created,
9601
10115
  opened_in_browser: opened,
9602
- message_for_human: messageForHuman(created.approval_url, o.amountUsd, o.autoReload, opened),
10116
+ message_for_human: message && existing ? `${message}
10117
+
10118
+ ${existingAccountHint(existing)}` : message,
9603
10119
  next_steps: topupNextSteps(created.topup_id, o.amountUsd, opened, o.autoReload)
9604
10120
  };
9605
10121
  }
10122
+ async function billingAutoReloadSetup(ctx, o = {}) {
10123
+ const body = {
10124
+ ...o.thresholdUsd !== undefined ? { threshold_usd: o.thresholdUsd } : {},
10125
+ ...o.amountUsd !== undefined ? { amount_usd: o.amountUsd } : {}
10126
+ };
10127
+ const created = await api(ctx, "POST", "/v1/billing/auto-reload/setup", {
10128
+ body
10129
+ });
10130
+ const noOpen = o.noOpen || ctx.env.ST_NO_BROWSER === "1";
10131
+ const opened = !noOpen && !!created.approval_url && await (o.open ?? openInBrowser)(created.approval_url);
10132
+ const wait = `${WAIT_COMMAND} ${created.topup_id}`;
10133
+ return {
10134
+ status: "pending",
10135
+ ...created,
10136
+ opened_in_browser: opened,
10137
+ message_for_human: setupMessageForHuman(created.approval_url, created.auto_reload, opened),
10138
+ next_steps: [
10139
+ opened ? `The page is already open in your human's browser: don't open it again. Tell them with message_for_human, then in the same turn run: ${wait}. It returns status "paid" once the card is saved and auto-reload is on; on topup_pending run it again, for up to ${WAIT_PATIENCE_MIN} minutes in all.` : `Give your human message_for_human now and end your turn there: it holds the approval_url exactly as printed. When they're back, run: ${wait}.`,
10140
+ "Nothing is charged now. `billing auto-reload off` turns it off without approval."
10141
+ ]
10142
+ };
10143
+ }
9606
10144
  function messageForHuman(approvalUrl, amountUsd, autoReload, opened = false) {
9607
10145
  if (!approvalUrl)
9608
10146
  return null;
9609
- const reload = autoReload ? ` It also turns on auto-reload: $${autoReload.amount_usd} whenever the balance drops below $${autoReload.threshold_usd}${autoReload.monthly_limit_usd ? `, at most $${autoReload.monthly_limit_usd} a month` : ""}.` : "";
10147
+ const reload = autoReload ? `Auto-reload is pre-selected there: $${autoReload.amount_usd} whenever the balance drops below $${autoReload.threshold_usd}, so I don't stall when credits run low; untick it if you'd rather not.` : "I recommend turning on auto-reload there, so I don't stall when credits run low.";
10148
+ const rest = `${reload} Then you accept the Terms of Service and the Credits & Refunds policy and pay by card. Nothing is charged until you approve, and we email you a receipt.`;
10149
+ if (opened)
10150
+ return `To finish the setup, I've opened the ScaledThought funding page in your browser for $${amountUsd} of prepaid credits (USD); you can change the amount there. If it didn't open, use this link:
10151
+
10152
+ ${approvalUrl}
10153
+
10154
+ ${rest} I'm watching for the payment and will carry on as soon as it goes through.`;
10155
+ return `To finish the setup, please add $${amountUsd} of prepaid ScaledThought credits (USD) here (you can change the amount):
10156
+
10157
+ ${approvalUrl}
10158
+
10159
+ ${rest} Tell me when it's done and I'll continue.`;
10160
+ }
10161
+ function setupMessageForHuman(approvalUrl, terms, opened = false) {
10162
+ if (!approvalUrl)
10163
+ return null;
10164
+ const what = `turn on auto-reload: whenever the balance drops below $${terms.threshold_usd}, $${terms.amount_usd} of prepaid credits (USD) is added from a card you save, so I don't stall when credits run low. You can change those amounts on the page. Nothing is charged now, and I can turn it off any time.`;
9610
10165
  if (opened)
9611
- return `To finish the setup, I've opened the ScaledThought checkout page in your browser to buy $${amountUsd} of prepaid credits (USD). If it didn't open, use this link:
10166
+ return `I've opened a ScaledThought page in your browser to ${what} If it didn't open, use this link:
9612
10167
 
9613
10168
  ${approvalUrl}
9614
10169
 
9615
- On that page you accept the Terms of Service and the Credits & Refunds policy and pay by card. Nothing is charged until you approve.${reload} I'm watching for the payment and will carry on as soon as it goes through.`;
9616
- return `To finish the setup, please buy $${amountUsd} of prepaid ScaledThought credits (USD) here:
10170
+ I'm watching and will carry on as soon as it's set up.`;
10171
+ return `Please use this ScaledThought page to ${what}
9617
10172
 
9618
10173
  ${approvalUrl}
9619
10174
 
9620
- On that page you accept the Terms of Service and the Credits & Refunds policy and pay by card. Nothing is charged until you approve.${reload} Tell me when it's done and I'll continue.`;
10175
+ Tell me when it's done and I'll continue.`;
9621
10176
  }
9622
10177
  var WAIT_DEFAULT_S = 90;
9623
10178
  async function billingWait(ctx, o = {}) {
10179
+ try {
10180
+ return await billingWaitOnce(ctx, o);
10181
+ } catch (e) {
10182
+ if (e instanceof CliError && ["invalid_api_key", "topup_canceled", "account_suspended"].includes(e.code)) {
10183
+ const switched = await connectedExisting(ctx, { noSave: o.noSave });
10184
+ if (switched)
10185
+ return switched;
10186
+ }
10187
+ throw e;
10188
+ }
10189
+ }
10190
+ async function billingWaitOnce(ctx, o) {
9624
10191
  let id = o.topupId;
9625
10192
  if (id !== undefined && !/^top_[0-9A-Za-z]+$/.test(id))
9626
10193
  throw new CliError("bad_argument", `"${id}" is not a top-up id (top_...).`, "Use topup_id from `billing topup`.");
@@ -9655,7 +10222,7 @@ async function billingTopupAndWait(ctx, o) {
9655
10222
  let t = created;
9656
10223
  while (!TOPUP_DONE.has(t.status ?? "")) {
9657
10224
  if (Date.now() > deadline)
9658
- throw new CliError("topup_wait_timeout", `Top-up ${id} is still ${t.status}.`, `The link may still work. Check later with: ${NPX} billing status`, { topup_id: id });
10225
+ throw new CliError("topup_wait_timeout", `Top-up ${id} is still ${t.status}.`, `The link may still work. Check later with: ${NPX2} billing status`, { topup_id: id });
9659
10226
  await new Promise((r) => setTimeout(r, o.pollMs ?? 3000));
9660
10227
  t = await api(ctx, "GET", `/v1/billing/topups/${encodeURIComponent(id)}`);
9661
10228
  }
@@ -9666,7 +10233,7 @@ async function billingTopupAndWait(ctx, o) {
9666
10233
  var billingAutoReloadOff = (ctx) => api(ctx, "PATCH", "/v1/billing/auto-reload", { body: { enabled: false } });
9667
10234
  var billingTransactions = (ctx, o = {}) => api(ctx, "GET", `/v1/billing/transactions${o.cursor ? `?cursor=${encodeURIComponent(o.cursor)}` : ""}`);
9668
10235
  function billingUpgradeRemoved() {
9669
- throw new CliError("billing_upgrade_removed", "billing upgrade (monthly caps) was replaced by prepaid credits.", `Run \`${NPX} billing topup --amount-usd 5\` and send your human the approval_url. Optional auto-reload: add --auto-reload-below 2 --auto-reload-amount 10.`);
10236
+ throw new CliError("billing_upgrade_removed", "billing upgrade (monthly caps) was replaced by prepaid credits.", `Run \`${NPX2} billing topup --amount-usd 20\` and send your human the approval_url. They can turn on auto-reload on the same page.`);
9670
10237
  }
9671
10238
  var benchmarks = (ctx, id, region) => api(ctx, "GET", `/v1/models/${encodeURIComponent(id)}/benchmarks`, {
9672
10239
  auth: false,
@@ -9675,10 +10242,10 @@ var benchmarks = (ctx, id, region) => api(ctx, "GET", `/v1/models/${encodeURICom
9675
10242
  async function uploadJsonl(ctx, path, purpose) {
9676
10243
  const { key, region } = resolveKey(ctx);
9677
10244
  const file = resolve2(ctx.cwd, path);
9678
- if (!existsSync2(file))
10245
+ if (!existsSync4(file))
9679
10246
  throw new CliError("file_not_found", `No file at ${path}.`, "Pass the path to a .jsonl file.");
9680
10247
  const form = new FormData;
9681
- form.set("file", new Blob([readFileSync3(file)], { type: "application/jsonl" }), path.split("/").pop() ?? "data.jsonl");
10248
+ form.set("file", new Blob([readFileSync5(file)], { type: "application/jsonl" }), path.split("/").pop() ?? "data.jsonl");
9682
10249
  form.set("purpose", purpose);
9683
10250
  const res = await ctx.fetch(`${controlUrl(region, ctx.env)}/v1/files`, {
9684
10251
  method: "POST",
@@ -9733,9 +10300,9 @@ var VIDEO_WAIT_MS = 30 * 60000;
9733
10300
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
9734
10301
  function imageDataUrl(ctx, path) {
9735
10302
  const file = resolve2(ctx.cwd, path);
9736
- if (!existsSync2(file))
10303
+ if (!existsSync4(file))
9737
10304
  throw new CliError("file_not_found", `No file at ${path}.`, "Pass the path to a PNG, JPEG or WebP image.");
9738
- const data = readFileSync3(file);
10305
+ const data = readFileSync5(file);
9739
10306
  if (data.byteLength > 10 * 1024 * 1024)
9740
10307
  throw new CliError("input_reference_too_large", "Images are limited to 10 MiB.");
9741
10308
  const type = data[0] === 255 && data[1] === 216 ? "image/jpeg" : data[0] === 137 && data[1] === 80 ? "image/png" : data.subarray(8, 12).toString() === "WEBP" ? "image/webp" : null;
@@ -9771,7 +10338,7 @@ async function videoDownload(ctx, id, output, variant = "video") {
9771
10338
  throw await responseError(res);
9772
10339
  const data = Buffer.from(await res.arrayBuffer());
9773
10340
  const path = resolve2(ctx.cwd, output);
9774
- writeFileSync(path, data);
10341
+ writeFileSync3(path, data);
9775
10342
  return {
9776
10343
  id,
9777
10344
  variant,
@@ -9876,9 +10443,23 @@ function mcpSetup(ctx) {
9876
10443
  openapi: `${controlUrl(region, ctx.env)}/openapi.json`
9877
10444
  };
9878
10445
  }
10446
+ var DASH_PAGES = ["overview", "billing", "keys", "members", "connect"];
10447
+ async function dashboardOpen(ctx, o = {}) {
10448
+ if (o.page !== undefined && !DASH_PAGES.includes(o.page))
10449
+ throw new CliError("bad_argument", `--page must be one of ${DASH_PAGES.join(", ")}.`, "e.g. --page billing");
10450
+ const r = await api(ctx, "POST", "/v1/dashboard/link", { body: { ...o.page ? { page: o.page } : {}, ...o.email ? { email: o.email } : {} } });
10451
+ const noOpen = o.noOpen || ctx.env.ST_NO_BROWSER === "1";
10452
+ const opened = !noOpen && await (o.open ?? openInBrowser)(r.url);
10453
+ return {
10454
+ ...r,
10455
+ opened_in_browser: opened,
10456
+ message_for_human: opened ? r.message_for_human : r.message_for_human.replace("I've opened your ScaledThought dashboard.", `Your ScaledThought dashboard: ${r.url}`)
10457
+ };
10458
+ }
10459
+ var membersList = (ctx) => api(ctx, "GET", "/v1/members");
9879
10460
 
9880
10461
  // src/decide.ts
9881
- import { readFileSync as readFileSync4 } from "node:fs";
10462
+ import { readFileSync as readFileSync6 } from "node:fs";
9882
10463
  import { resolve as resolve3 } from "node:path";
9883
10464
  var DEFAULT_DECISION_MODEL = "open-jev-27b";
9884
10465
  var usage2 = (msg) => new CliError("usage_error", msg, 'Example: --question urgent:noul:"Is this urgent?" (see decide --help)');
@@ -9950,7 +10531,7 @@ function readState(ctx, o) {
9950
10531
  if (o.stateFile !== undefined) {
9951
10532
  let text;
9952
10533
  try {
9953
- text = readFileSync4(resolve3(ctx.cwd, o.stateFile), "utf8");
10534
+ text = readFileSync6(resolve3(ctx.cwd, o.stateFile), "utf8");
9954
10535
  } catch {
9955
10536
  throw usage2("--state-file could not be read.");
9956
10537
  }
@@ -9967,7 +10548,7 @@ function decisionRequest(ctx, o) {
9967
10548
  throw usage2("--file holds the whole request; don't combine it with --state or --question.");
9968
10549
  let body;
9969
10550
  try {
9970
- body = JSON.parse(readFileSync4(resolve3(ctx.cwd, o.file), "utf8"));
10551
+ body = JSON.parse(readFileSync6(resolve3(ctx.cwd, o.file), "utf8"));
9971
10552
  } catch {
9972
10553
  throw usage2("--file must be a readable JSON file.");
9973
10554
  }
@@ -10001,7 +10582,7 @@ async function decide2(ctx, body) {
10001
10582
  }
10002
10583
 
10003
10584
  // src/realtime.ts
10004
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
10585
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
10005
10586
  import { resolve as resolve4 } from "node:path";
10006
10587
  var SAMPLE_RATE = 24000;
10007
10588
  var MAX_INPUT_SECONDS = 300;
@@ -10066,7 +10647,7 @@ async function realtime(ctx, o) {
10066
10647
  throw new CliError("unsupported_runtime", "This command needs a WebSocket client (Node.js 22 or newer).", "Upgrade Node.js to 22+.");
10067
10648
  let pcm;
10068
10649
  try {
10069
- pcm = readWav(readFileSync5(resolve4(ctx.cwd, o.input)));
10650
+ pcm = readWav(readFileSync7(resolve4(ctx.cwd, o.input)));
10070
10651
  } catch (e) {
10071
10652
  if (e instanceof CliError)
10072
10653
  throw e;
@@ -10158,7 +10739,7 @@ async function realtime(ctx, o) {
10158
10739
  });
10159
10740
  const audio = Buffer.concat(out);
10160
10741
  const path = resolve4(ctx.cwd, o.output);
10161
- writeFileSync2(path, wavOf(audio));
10742
+ writeFileSync4(path, wavOf(audio));
10162
10743
  return {
10163
10744
  model: o.model,
10164
10745
  region,
@@ -10208,7 +10789,7 @@ function num(flag, v) {
10208
10789
  var init2 = defineCommand({
10209
10790
  meta: {
10210
10791
  name: "init",
10211
- description: "Create an account, save ST_API_KEY and ST_BASE_URL to ./.env, list live models and the next steps (a new account has no credits: top up first). Idempotent. " + "Creating an account accepts the Terms of Service (scaledthought.com/legal/terms) for the human you act for."
10792
+ description: "Create an account, save ST_API_KEY and ST_BASE_URL to ./.env, list live models and the next steps (a new account has no credits: top up first). Idempotent. " + "Creating an account accepts nothing for your human: they accept the Terms themselves when they buy credits."
10212
10793
  },
10213
10794
  args: {
10214
10795
  ...jsonFlag,
@@ -10222,6 +10803,19 @@ var init2 = defineCommand({
10222
10803
  "ad-click": {
10223
10804
  type: "string",
10224
10805
  description: "Signed ad-click token, from a prompt ending in `(ad click <token>)` that a human copied on a US campaign's landing page (optional; us region only). Opt out later with `account ad-measurement off`"
10806
+ },
10807
+ account: {
10808
+ type: "string",
10809
+ description: "Use this account saved on this machine (id or name, from choose_account) with a new project key, instead of creating one"
10810
+ },
10811
+ "new-account": {
10812
+ type: "boolean",
10813
+ description: "Create a new account even when one is saved on this machine (separate billing)"
10814
+ },
10815
+ save: {
10816
+ type: "boolean",
10817
+ default: true,
10818
+ description: "Save the account's key in this machine's credential store so other folders reuse it (--no-save to opt out)"
10225
10819
  }
10226
10820
  },
10227
10821
  run: ({ args }) => run(() => init(makeCtx(), {
@@ -10229,9 +10823,70 @@ var init2 = defineCommand({
10229
10823
  name: args.name,
10230
10824
  email: args.email,
10231
10825
  source: args.source,
10232
- adClick: args["ad-click"]
10826
+ adClick: args["ad-click"],
10827
+ account: args.account,
10828
+ newAccount: args["new-account"],
10829
+ noSave: args.save === false
10233
10830
  }), args)
10234
10831
  });
10832
+ var login2 = defineCommand({
10833
+ meta: {
10834
+ name: "login",
10835
+ description: "Connect this folder to an existing account: prints a code your human types on the dashboard (opened in their browser). Then `login --wait` saves a new key to ./.env. `login --code XXXX-XXXX` uses a connect line from the dashboard"
10836
+ },
10837
+ args: {
10838
+ ...jsonFlag,
10839
+ region: { type: "string", description: "The account's region: us (default) or ca" },
10840
+ open: { type: "boolean", default: true, description: "Open the connect page (--no-open only prints it)" },
10841
+ label: { type: "string", description: 'How this agent is named to the human (default "<tool> on <hostname>")' },
10842
+ wait: {
10843
+ type: "boolean",
10844
+ description: "Wait (about 90 s) for your human to approve the code, then save the key. Run again on authorization_pending"
10845
+ },
10846
+ code: {
10847
+ type: "string",
10848
+ description: "A connect line's code from the dashboard (stl_…): already approved, works once. `--code -` reads it from stdin"
10849
+ },
10850
+ force: { type: "boolean", description: "Replace the key already in ./.env" },
10851
+ save: {
10852
+ type: "boolean",
10853
+ default: true,
10854
+ description: "Save the key in this machine's credential store (--no-save to opt out)"
10855
+ },
10856
+ "timeout-s": { type: "string", description: "With --wait: seconds to wait (default 90, at most 600)" }
10857
+ },
10858
+ run: async ({ args }) => {
10859
+ const ctx = makeCtx();
10860
+ const noSave = args.save === false;
10861
+ const t = args["timeout-s"] === undefined ? undefined : num("timeout-s", args["timeout-s"]);
10862
+ if (t !== undefined && t > 600)
10863
+ throw new CliError("bad_argument", `--timeout-s must be at most 600, got ${t}.`, "Run login --wait again.");
10864
+ await run(async () => args.code ? loginWithCode(ctx, {
10865
+ code: args.code === "-" ? await readCodeFrom(process.stdin) : args.code,
10866
+ region: region(args.region),
10867
+ label: args.label,
10868
+ force: args.force,
10869
+ noSave
10870
+ }) : args.wait ? loginWait(ctx, { timeoutS: t, noSave, force: args.force }) : login(ctx, {
10871
+ region: region(args.region),
10872
+ label: args.label,
10873
+ noOpen: args.open === false,
10874
+ force: args.force,
10875
+ noSave
10876
+ }), args);
10877
+ }
10878
+ });
10879
+ var use = defineCommand({
10880
+ meta: {
10881
+ name: "use",
10882
+ description: "Point this folder at another account saved on this machine: a new project key in ./.env, and the folder's old project key revoked"
10883
+ },
10884
+ args: {
10885
+ ...jsonFlag,
10886
+ account: { type: "positional", required: true, description: "The account's name or id" }
10887
+ },
10888
+ run: ({ args }) => run(() => useAccount(makeCtx(), { target: args.account }), args)
10889
+ });
10235
10890
  var models = defineCommand({
10236
10891
  meta: { name: "models", description: "List models with prices and benchmarks, newest first; or show one" },
10237
10892
  args: {
@@ -10510,7 +11165,7 @@ function autoReloadArgs(args) {
10510
11165
  if (below === undefined && amount === undefined && max === undefined)
10511
11166
  return;
10512
11167
  if (below === undefined || amount === undefined)
10513
- throw new CliError("bad_argument", "Auto-reload needs both --auto-reload-below and --auto-reload-amount.", "e.g. --auto-reload-below 2 --auto-reload-amount 10 [--auto-reload-monthly-max 50]");
11168
+ throw new CliError("bad_argument", "Auto-reload needs both --auto-reload-below and --auto-reload-amount.", "e.g. --auto-reload-below 5 --auto-reload-amount 10");
10514
11169
  return { threshold_usd: below, amount_usd: amount, ...max !== undefined ? { monthly_limit_usd: max } : {} };
10515
11170
  }
10516
11171
  var billing = defineCommand({
@@ -10527,20 +11182,22 @@ var billing = defineCommand({
10527
11182
  topup: defineCommand({
10528
11183
  meta: {
10529
11184
  name: "topup",
10530
- description: "Buy prepaid credits: opens the checkout page in your human's browser (approval_url); then run `billing wait`, which returns once they pay. Optional auto-reload"
11185
+ description: "Buy prepaid credits: opens the funding page in your human's browser (approval_url), where they confirm the amount, can turn on auto-reload (recommended) and pay; then run `billing wait`, which returns once they pay"
10531
11186
  },
10532
11187
  args: {
10533
11188
  ...jsonFlag,
10534
11189
  "amount-usd": {
10535
11190
  type: "string",
10536
- required: true,
10537
- description: "Credits to buy in USD ($5 to $2,000; at most $2,000 per 24 h)"
11191
+ description: "Credits to buy in USD (default 20; $20 to $2,000, at most $2,000 per 24 h). Your human can change it"
11192
+ },
11193
+ "auto-reload-below": {
11194
+ type: "string",
11195
+ description: "Suggest auto-reload (pre-selected on the page) when the balance drops below this (USD, at least 5)"
10538
11196
  },
10539
- "auto-reload-below": { type: "string", description: "Auto-reload when the balance drops below this (USD)" },
10540
- "auto-reload-amount": { type: "string", description: "Amount of each auto-reload (USD)" },
11197
+ "auto-reload-amount": { type: "string", description: "Amount of each suggested auto-reload (USD)" },
10541
11198
  "auto-reload-monthly-max": {
10542
11199
  type: "string",
10543
- description: "Most auto-reload may charge per calendar month (USD, default 5x the amount)"
11200
+ description: "Optional: most auto-reload may charge per calendar month (USD; default: no monthly limit)"
10544
11201
  },
10545
11202
  open: {
10546
11203
  type: "boolean",
@@ -10553,7 +11210,7 @@ var billing = defineCommand({
10553
11210
  }
10554
11211
  },
10555
11212
  run: async ({ args }) => {
10556
- const amountUsd = num("amount-usd", args["amount-usd"]);
11213
+ const amountUsd = num("amount-usd", args["amount-usd"]) ?? 20;
10557
11214
  const autoReload = autoReloadArgs(args);
10558
11215
  checkTopupLimits(amountUsd, autoReload);
10559
11216
  const noOpen = args.open === false;
@@ -10581,8 +11238,35 @@ var billing = defineCommand({
10581
11238
  }
10582
11239
  }),
10583
11240
  "auto-reload": defineCommand({
10584
- meta: { name: "auto-reload", description: "Turn auto-reload off (turning it on or up needs a topup approval)" },
11241
+ meta: {
11242
+ name: "auto-reload",
11243
+ description: "Turn auto-reload on (a link your human approves; nothing is bought) or off (no approval)"
11244
+ },
10585
11245
  subCommands: {
11246
+ setup: defineCommand({
11247
+ meta: {
11248
+ name: "setup",
11249
+ description: "Opens a page in your human's browser where they save a card and turn auto-reload on, buying nothing; then run `billing wait`"
11250
+ },
11251
+ args: {
11252
+ ...jsonFlag,
11253
+ below: {
11254
+ type: "string",
11255
+ description: "Reload when the balance drops below this (USD, default and minimum 5)"
11256
+ },
11257
+ amount: { type: "string", description: "Credits each reload adds (USD, default 20)" },
11258
+ open: {
11259
+ type: "boolean",
11260
+ default: true,
11261
+ description: "Open the page in the browser (--no-open only prints approval_url)"
11262
+ }
11263
+ },
11264
+ run: ({ args }) => run(() => billingAutoReloadSetup(makeCtx(), {
11265
+ thresholdUsd: num("below", args.below),
11266
+ amountUsd: num("amount", args.amount),
11267
+ noOpen: args.open === false
11268
+ }), args)
11269
+ }),
10586
11270
  off: defineCommand({
10587
11271
  meta: { name: "off", description: "Disable auto-reload. No approval needed" },
10588
11272
  args: jsonFlag,
@@ -10808,6 +11492,31 @@ var mcp = defineCommand({
10808
11492
  args: jsonFlag,
10809
11493
  run: ({ args }) => run(() => mcpSetup(makeCtx()), args)
10810
11494
  });
11495
+ var dashboard = defineCommand({
11496
+ meta: {
11497
+ name: "dashboard",
11498
+ description: "Open your human's ScaledThought dashboard (billing, keys, members, connect an agent) in their browser. No typing: if they're not signed in, one click emails them a link"
11499
+ },
11500
+ args: {
11501
+ ...jsonFlag,
11502
+ page: { type: "string", description: "overview (default), billing, keys, members or connect" },
11503
+ email: {
11504
+ type: "string",
11505
+ description: "Set the owner's email (only before anyone has signed in; normally the first payer is the owner)"
11506
+ },
11507
+ open: { type: "boolean", default: true, description: "Open it in the browser (--no-open only prints the URL)" }
11508
+ },
11509
+ run: ({ args }) => run(() => dashboardOpen(makeCtx(), {
11510
+ page: args.page,
11511
+ email: args.email,
11512
+ noOpen: args.open === false
11513
+ }), args)
11514
+ });
11515
+ var members2 = defineCommand({
11516
+ meta: { name: "members", description: "Who can sign in to this account's dashboard (email, role, status)" },
11517
+ args: jsonFlag,
11518
+ run: ({ args }) => run(() => membersList(makeCtx()), args)
11519
+ });
10811
11520
  var root = defineCommand({
10812
11521
  meta: {
10813
11522
  name: "scaledthought",
@@ -10816,6 +11525,8 @@ var root = defineCommand({
10816
11525
  },
10817
11526
  subCommands: {
10818
11527
  init: init2,
11528
+ login: login2,
11529
+ use,
10819
11530
  models,
10820
11531
  benchmarks: benchmarks2,
10821
11532
  status: status2,
@@ -10833,7 +11544,9 @@ var root = defineCommand({
10833
11544
  video,
10834
11545
  webhooks,
10835
11546
  doctor: doctor2,
10836
- mcp
11547
+ mcp,
11548
+ dashboard,
11549
+ members: members2
10837
11550
  }
10838
11551
  });
10839
11552
  var NUMERIC_FLAGS = new Set([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scaledthought",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Headless open-model inference, run by your agent. Prepaid, pay per token. US or Canada.",
5
5
  "license": "MIT",
6
6
  "keywords": [