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.
- package/README.md +8 -8
- package/dist/index.js +774 -61
- 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@
|
|
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@
|
|
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@
|
|
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@
|
|
44
|
-
npx -y scaledthought@
|
|
45
|
-
npx -y scaledthought@
|
|
46
|
-
npx -y scaledthought@
|
|
47
|
-
npx -y scaledthought@
|
|
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.
|
|
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@
|
|
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: "
|
|
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
|
|
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
|
|
655
|
-
gone: "This endpoint was removed. For credits use `billing topup --amount-usd
|
|
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
|
|
909
|
-
import { join as
|
|
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 = `
|
|
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
|
|
9247
|
-
var FIRST_TOPUP_USD =
|
|
9248
|
-
var TOPUP_COMMAND = `${
|
|
9249
|
-
var WAIT_COMMAND = `${
|
|
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
|
|
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: ${
|
|
9262
|
-
`Or let the router pick: ${
|
|
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: ${
|
|
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 : `${
|
|
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 =
|
|
9876
|
+
const f = join5(cwd, ".env");
|
|
9365
9877
|
const names = new Set(Object.keys(vars));
|
|
9366
|
-
const kept =
|
|
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
|
-
|
|
9882
|
+
writeFileSync3(f, `${lines.join(`
|
|
9371
9883
|
`)}
|
|
9372
9884
|
`, { mode: 384 });
|
|
9373
|
-
|
|
9885
|
+
chmodSync3(f, 384);
|
|
9374
9886
|
}
|
|
9375
9887
|
function ensureGitignored(cwd) {
|
|
9376
|
-
const gi =
|
|
9377
|
-
const text =
|
|
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
|
-
|
|
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:
|
|
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
|
|
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,
|
|
9557
|
-
if (autoReload.monthly_limit_usd
|
|
9558
|
-
range("auto-reload-monthly-max", autoReload.monthly_limit_usd, autoReload.amount_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:
|
|
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 ? `
|
|
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 `
|
|
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
|
-
|
|
9616
|
-
return `
|
|
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
|
-
|
|
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: ${
|
|
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 \`${
|
|
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 (!
|
|
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([
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
10537
|
-
|
|
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-
|
|
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: "
|
|
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: {
|
|
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([
|