scaledthought 0.2.1 → 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 +785 -66
- 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) {
|
|
@@ -8964,6 +8975,8 @@ var EXPERIMENT_METRICS = [
|
|
|
8964
8975
|
"signups",
|
|
8965
8976
|
"activation_rate",
|
|
8966
8977
|
"paid_rate",
|
|
8978
|
+
"topup_request_rate",
|
|
8979
|
+
"topup_completion_rate",
|
|
8967
8980
|
"cost_per_activated",
|
|
8968
8981
|
"cost_per_payer",
|
|
8969
8982
|
"onboarding_success_rate",
|
|
@@ -9238,27 +9251,516 @@ var TERMS_URL = `${LEGAL_SITE}${LEGAL_PATHS.terms}`;
|
|
|
9238
9251
|
var PRIVACY_URL = `${LEGAL_SITE}${LEGAL_PATHS.privacy}`;
|
|
9239
9252
|
var ACCEPTABLE_USE_URL = `${LEGAL_SITE}${LEGAL_PATHS.acceptableUse}`;
|
|
9240
9253
|
var REFUND_POLICY_URL = `${LEGAL_SITE}${LEGAL_PATHS.creditsAndRefunds}`;
|
|
9241
|
-
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
|
+
}
|
|
9242
9743
|
|
|
9243
9744
|
// src/commands.ts
|
|
9244
|
-
var
|
|
9245
|
-
var FIRST_TOPUP_USD =
|
|
9246
|
-
var TOPUP_COMMAND = `${
|
|
9247
|
-
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`;
|
|
9749
|
+
var WAIT_PATIENCE_MIN = 15;
|
|
9248
9750
|
var CREDIT_STEPS = [
|
|
9249
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:`,
|
|
9250
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.`,
|
|
9251
|
-
`2.
|
|
9252
|
-
`3.
|
|
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.`,
|
|
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.`,
|
|
9253
9755
|
"4. Then make the call below."
|
|
9254
9756
|
];
|
|
9255
9757
|
function nextSteps(model, needsCredits) {
|
|
9256
9758
|
return [
|
|
9257
9759
|
...needsCredits ? CREDIT_STEPS : [],
|
|
9258
|
-
model ? `Verify with one real completion: ${
|
|
9259
|
-
`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"`,
|
|
9260
9762
|
"Use it from code: any OpenAI SDK with base_url=$ST_BASE_URL and api_key=$ST_API_KEY (both are in .env)",
|
|
9261
|
-
`Optional, connect MCP: ${
|
|
9763
|
+
`Optional, connect MCP: ${NPX2} mcp`,
|
|
9262
9764
|
...needsCredits ? [] : [`More credits when needed: ${TOPUP_COMMAND} returns an approval_url for your human (inference is prepaid)`]
|
|
9263
9765
|
];
|
|
9264
9766
|
}
|
|
@@ -9278,7 +9780,7 @@ async function liveSummary(ctx, region, balanceUsd) {
|
|
|
9278
9780
|
return {
|
|
9279
9781
|
balance_usd: balanceUsd ?? 0,
|
|
9280
9782
|
needs_credits: needsCredits,
|
|
9281
|
-
next_command: needsCredits ? TOPUP_COMMAND : `${
|
|
9783
|
+
next_command: needsCredits ? TOPUP_COMMAND : `${NPX2} chat -m ${suggested ?? "auto"} "Reply with OK"`,
|
|
9282
9784
|
newest_models: live.slice(0, 5),
|
|
9283
9785
|
suggested_model: suggested,
|
|
9284
9786
|
next_steps: nextSteps(suggested, needsCredits)
|
|
@@ -9328,6 +9830,13 @@ async function init(ctx, opts) {
|
|
|
9328
9830
|
...ignored.length ? { warnings: [`${ignored.join(" and ")} apply only to a new account and were not sent.`] } : {}
|
|
9329
9831
|
};
|
|
9330
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
|
+
}
|
|
9331
9840
|
const region = opts.region ?? "us";
|
|
9332
9841
|
const attribution = signupAttribution(region, opts);
|
|
9333
9842
|
const acct = await api(ctx, "POST", "/v1/accounts", {
|
|
@@ -9343,6 +9852,7 @@ async function init(ctx, opts) {
|
|
|
9343
9852
|
});
|
|
9344
9853
|
writeEnv(ctx.cwd, { ST_API_KEY: acct.api_key, ST_BASE_URL: acct.base_url });
|
|
9345
9854
|
ensureGitignored(ctx.cwd);
|
|
9855
|
+
const { saved, existingCode } = await afterNewAccount(ctx, { ...acct, name: opts.name }, { noSave: opts.noSave });
|
|
9346
9856
|
const keyed = { ...ctx, env: { ...ctx.env, ST_API_KEY: acct.api_key } };
|
|
9347
9857
|
const billing = await api(keyed, "GET", "/v1/billing").catch(() => null);
|
|
9348
9858
|
return {
|
|
@@ -9351,6 +9861,11 @@ async function init(ctx, opts) {
|
|
|
9351
9861
|
region: acct.region,
|
|
9352
9862
|
base_url: acct.base_url,
|
|
9353
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) } : {},
|
|
9354
9869
|
terms: termsOf(acct),
|
|
9355
9870
|
...attribution.source ? { source: acct.source ?? attribution.source } : {},
|
|
9356
9871
|
...await liveSummary(keyed, acct.region, balanceOf(billing)),
|
|
@@ -9358,26 +9873,26 @@ async function init(ctx, opts) {
|
|
|
9358
9873
|
};
|
|
9359
9874
|
}
|
|
9360
9875
|
function writeEnv(cwd, vars) {
|
|
9361
|
-
const f =
|
|
9876
|
+
const f = join5(cwd, ".env");
|
|
9362
9877
|
const names = new Set(Object.keys(vars));
|
|
9363
|
-
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] ?? "")) : [];
|
|
9364
9879
|
while (kept.length && kept[kept.length - 1].trim() === "")
|
|
9365
9880
|
kept.pop();
|
|
9366
9881
|
const lines = [...kept, ...Object.entries(vars).map(([k, v]) => `${k}=${v}`)];
|
|
9367
|
-
|
|
9882
|
+
writeFileSync3(f, `${lines.join(`
|
|
9368
9883
|
`)}
|
|
9369
9884
|
`, { mode: 384 });
|
|
9370
|
-
|
|
9885
|
+
chmodSync3(f, 384);
|
|
9371
9886
|
}
|
|
9372
9887
|
function ensureGitignored(cwd) {
|
|
9373
|
-
const gi =
|
|
9374
|
-
const text =
|
|
9888
|
+
const gi = join5(cwd, ".gitignore");
|
|
9889
|
+
const text = existsSync4(gi) ? readFileSync5(gi, "utf8") : "";
|
|
9375
9890
|
if (/^\/?\.env\*?\s*$/m.test(text))
|
|
9376
9891
|
return;
|
|
9377
9892
|
const sep = text === "" || text.endsWith(`
|
|
9378
9893
|
`) ? "" : `
|
|
9379
9894
|
`;
|
|
9380
|
-
|
|
9895
|
+
writeFileSync3(gi, `${text}${sep}.env
|
|
9381
9896
|
`);
|
|
9382
9897
|
}
|
|
9383
9898
|
function isCatalogRow(m) {
|
|
@@ -9539,9 +10054,9 @@ async function chat(ctx, o) {
|
|
|
9539
10054
|
};
|
|
9540
10055
|
}
|
|
9541
10056
|
var billingStatus = async (ctx) => withoutFreeFields(await api(ctx, "GET", "/v1/billing"));
|
|
9542
|
-
var TOPUP_LIMITS = { min_usd:
|
|
10057
|
+
var TOPUP_LIMITS = { min_usd: 20, max_usd: 2000, daily_max_usd: 2000 };
|
|
9543
10058
|
function checkTopupLimits(amountUsd, autoReload) {
|
|
9544
|
-
const { min_usd, max_usd, daily_max_usd
|
|
10059
|
+
const { min_usd, max_usd, daily_max_usd } = TOPUP_LIMITS;
|
|
9545
10060
|
const range = (flag, v, lo, hi) => {
|
|
9546
10061
|
if (v < lo || v > hi)
|
|
9547
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).`);
|
|
@@ -9550,19 +10065,22 @@ function checkTopupLimits(amountUsd, autoReload) {
|
|
|
9550
10065
|
if (!autoReload)
|
|
9551
10066
|
return;
|
|
9552
10067
|
range("auto-reload-amount", autoReload.amount_usd, min_usd, max_usd);
|
|
9553
|
-
range("auto-reload-below", autoReload.threshold_usd,
|
|
9554
|
-
if (autoReload.monthly_limit_usd
|
|
9555
|
-
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);
|
|
9556
10071
|
}
|
|
9557
10072
|
var TOPUP_DONE = new Set(["paid", "expired", "failed", "canceled"]);
|
|
9558
10073
|
function topupNextSteps(topupId, amountUsd, opened, autoReload) {
|
|
10074
|
+
const wait = `${WAIT_COMMAND}${topupId ? ` ${topupId}` : ""}`;
|
|
9559
10075
|
return [
|
|
9560
|
-
|
|
9561
|
-
`
|
|
10076
|
+
opened ? `The checkout page is already open in your human's browser: don't open it again. Tell them with message_for_human (it holds the approval_url in case it didn't open), then in the same turn run: ${wait}. It returns status "paid" with the 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. If it's still unpaid after that, end your turn with message_for_human.` : `Give your human message_for_human now and end your turn there: it holds the approval_url exactly as printed (a one-time payment link; don't shorten or reformat it). Don't run billing wait (or anything else that waits) before they have the link: they may not see your messages until your turn ends.`,
|
|
10077
|
+
`On that page they accept the Terms and the Credits & Refunds policy and pay $${amountUsd} by card; credits are added once the payment is confirmed. Nothing is charged without their approval.`,
|
|
9562
10078
|
...autoReload ? [
|
|
9563
10079
|
`Approving also saves the card for 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` : ""}.`
|
|
9564
10080
|
] : [],
|
|
9565
|
-
|
|
10081
|
+
...opened ? [] : [
|
|
10082
|
+
`When they say they've paid: ${wait} (returns status "paid" with the balance, at once if already paid; topup_pending means not yet: tell them, don't make a new link).`
|
|
10083
|
+
],
|
|
9566
10084
|
"Once paid, retry the call that returned 402. Don't create another top-up while this one is pending: the link stays valid until expires_at."
|
|
9567
10085
|
];
|
|
9568
10086
|
}
|
|
@@ -9589,32 +10107,87 @@ async function billingTopup(ctx, o) {
|
|
|
9589
10107
|
});
|
|
9590
10108
|
const noOpen = o.noOpen || ctx.env.ST_NO_BROWSER === "1";
|
|
9591
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);
|
|
9592
10112
|
return {
|
|
9593
10113
|
status: "pending",
|
|
9594
10114
|
...created,
|
|
9595
10115
|
opened_in_browser: opened,
|
|
9596
|
-
message_for_human:
|
|
10116
|
+
message_for_human: message && existing ? `${message}
|
|
10117
|
+
|
|
10118
|
+
${existingAccountHint(existing)}` : message,
|
|
9597
10119
|
next_steps: topupNextSteps(created.topup_id, o.amountUsd, opened, o.autoReload)
|
|
9598
10120
|
};
|
|
9599
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
|
+
}
|
|
9600
10144
|
function messageForHuman(approvalUrl, amountUsd, autoReload, opened = false) {
|
|
9601
10145
|
if (!approvalUrl)
|
|
9602
10146
|
return null;
|
|
9603
|
-
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.`;
|
|
9604
10149
|
if (opened)
|
|
9605
|
-
return `To finish the setup, I've opened the ScaledThought
|
|
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:
|
|
9606
10151
|
|
|
9607
10152
|
${approvalUrl}
|
|
9608
10153
|
|
|
9609
|
-
|
|
9610
|
-
return `To finish the setup, please
|
|
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):
|
|
9611
10156
|
|
|
9612
10157
|
${approvalUrl}
|
|
9613
10158
|
|
|
9614
|
-
|
|
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.`;
|
|
10165
|
+
if (opened)
|
|
10166
|
+
return `I've opened a ScaledThought page in your browser to ${what} If it didn't open, use this link:
|
|
10167
|
+
|
|
10168
|
+
${approvalUrl}
|
|
10169
|
+
|
|
10170
|
+
I'm watching and will carry on as soon as it's set up.`;
|
|
10171
|
+
return `Please use this ScaledThought page to ${what}
|
|
10172
|
+
|
|
10173
|
+
${approvalUrl}
|
|
10174
|
+
|
|
10175
|
+
Tell me when it's done and I'll continue.`;
|
|
9615
10176
|
}
|
|
9616
10177
|
var WAIT_DEFAULT_S = 90;
|
|
9617
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) {
|
|
9618
10191
|
let id = o.topupId;
|
|
9619
10192
|
if (id !== undefined && !/^top_[0-9A-Za-z]+$/.test(id))
|
|
9620
10193
|
throw new CliError("bad_argument", `"${id}" is not a top-up id (top_...).`, "Use topup_id from `billing topup`.");
|
|
@@ -9637,7 +10210,7 @@ async function billingWait(ctx, o = {}) {
|
|
|
9637
10210
|
if (TOPUP_DONE.has(t.status ?? ""))
|
|
9638
10211
|
throw new CliError(`topup_${t.status}`, t.status === "expired" ? `Top-up ${id} expired before it was paid.` : `Top-up ${id} was not paid (${t.status}${t.failure_code ? `: ${t.failure_code}` : ""}).`, `Run \`${TOPUP_COMMAND}\` again and show your human the new approval_url.`, { topup_id: id });
|
|
9639
10212
|
if (Date.now() >= deadline)
|
|
9640
|
-
throw new CliError("topup_pending", `Top-up ${id} isn't paid yet.`, `Not paid yet. If your human
|
|
10213
|
+
throw new CliError("topup_pending", `Top-up ${id} isn't paid yet.`, `Not paid yet. If the checkout page is open in your human's browser (billing topup printed opened_in_browser: true), run \`${WAIT_COMMAND} ${id}\` again right away, for up to ${WAIT_PATIENCE_MIN} minutes in all. If they don't have the approval_url (below), or it's still unpaid after that, end your turn and give it to them, then run it again when they're back. Don't create a new top-up.`, { topup_id: id, approval_url: t.approval_url ?? null });
|
|
9641
10214
|
await new Promise((r) => setTimeout(r, o.pollMs ?? 3000));
|
|
9642
10215
|
}
|
|
9643
10216
|
}
|
|
@@ -9649,7 +10222,7 @@ async function billingTopupAndWait(ctx, o) {
|
|
|
9649
10222
|
let t = created;
|
|
9650
10223
|
while (!TOPUP_DONE.has(t.status ?? "")) {
|
|
9651
10224
|
if (Date.now() > deadline)
|
|
9652
|
-
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 });
|
|
9653
10226
|
await new Promise((r) => setTimeout(r, o.pollMs ?? 3000));
|
|
9654
10227
|
t = await api(ctx, "GET", `/v1/billing/topups/${encodeURIComponent(id)}`);
|
|
9655
10228
|
}
|
|
@@ -9660,7 +10233,7 @@ async function billingTopupAndWait(ctx, o) {
|
|
|
9660
10233
|
var billingAutoReloadOff = (ctx) => api(ctx, "PATCH", "/v1/billing/auto-reload", { body: { enabled: false } });
|
|
9661
10234
|
var billingTransactions = (ctx, o = {}) => api(ctx, "GET", `/v1/billing/transactions${o.cursor ? `?cursor=${encodeURIComponent(o.cursor)}` : ""}`);
|
|
9662
10235
|
function billingUpgradeRemoved() {
|
|
9663
|
-
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.`);
|
|
9664
10237
|
}
|
|
9665
10238
|
var benchmarks = (ctx, id, region) => api(ctx, "GET", `/v1/models/${encodeURIComponent(id)}/benchmarks`, {
|
|
9666
10239
|
auth: false,
|
|
@@ -9669,10 +10242,10 @@ var benchmarks = (ctx, id, region) => api(ctx, "GET", `/v1/models/${encodeURICom
|
|
|
9669
10242
|
async function uploadJsonl(ctx, path, purpose) {
|
|
9670
10243
|
const { key, region } = resolveKey(ctx);
|
|
9671
10244
|
const file = resolve2(ctx.cwd, path);
|
|
9672
|
-
if (!
|
|
10245
|
+
if (!existsSync4(file))
|
|
9673
10246
|
throw new CliError("file_not_found", `No file at ${path}.`, "Pass the path to a .jsonl file.");
|
|
9674
10247
|
const form = new FormData;
|
|
9675
|
-
form.set("file", new Blob([
|
|
10248
|
+
form.set("file", new Blob([readFileSync5(file)], { type: "application/jsonl" }), path.split("/").pop() ?? "data.jsonl");
|
|
9676
10249
|
form.set("purpose", purpose);
|
|
9677
10250
|
const res = await ctx.fetch(`${controlUrl(region, ctx.env)}/v1/files`, {
|
|
9678
10251
|
method: "POST",
|
|
@@ -9727,9 +10300,9 @@ var VIDEO_WAIT_MS = 30 * 60000;
|
|
|
9727
10300
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
9728
10301
|
function imageDataUrl(ctx, path) {
|
|
9729
10302
|
const file = resolve2(ctx.cwd, path);
|
|
9730
|
-
if (!
|
|
10303
|
+
if (!existsSync4(file))
|
|
9731
10304
|
throw new CliError("file_not_found", `No file at ${path}.`, "Pass the path to a PNG, JPEG or WebP image.");
|
|
9732
|
-
const data =
|
|
10305
|
+
const data = readFileSync5(file);
|
|
9733
10306
|
if (data.byteLength > 10 * 1024 * 1024)
|
|
9734
10307
|
throw new CliError("input_reference_too_large", "Images are limited to 10 MiB.");
|
|
9735
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;
|
|
@@ -9765,7 +10338,7 @@ async function videoDownload(ctx, id, output, variant = "video") {
|
|
|
9765
10338
|
throw await responseError(res);
|
|
9766
10339
|
const data = Buffer.from(await res.arrayBuffer());
|
|
9767
10340
|
const path = resolve2(ctx.cwd, output);
|
|
9768
|
-
|
|
10341
|
+
writeFileSync3(path, data);
|
|
9769
10342
|
return {
|
|
9770
10343
|
id,
|
|
9771
10344
|
variant,
|
|
@@ -9870,9 +10443,23 @@ function mcpSetup(ctx) {
|
|
|
9870
10443
|
openapi: `${controlUrl(region, ctx.env)}/openapi.json`
|
|
9871
10444
|
};
|
|
9872
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");
|
|
9873
10460
|
|
|
9874
10461
|
// src/decide.ts
|
|
9875
|
-
import { readFileSync as
|
|
10462
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
9876
10463
|
import { resolve as resolve3 } from "node:path";
|
|
9877
10464
|
var DEFAULT_DECISION_MODEL = "open-jev-27b";
|
|
9878
10465
|
var usage2 = (msg) => new CliError("usage_error", msg, 'Example: --question urgent:noul:"Is this urgent?" (see decide --help)');
|
|
@@ -9944,7 +10531,7 @@ function readState(ctx, o) {
|
|
|
9944
10531
|
if (o.stateFile !== undefined) {
|
|
9945
10532
|
let text;
|
|
9946
10533
|
try {
|
|
9947
|
-
text =
|
|
10534
|
+
text = readFileSync6(resolve3(ctx.cwd, o.stateFile), "utf8");
|
|
9948
10535
|
} catch {
|
|
9949
10536
|
throw usage2("--state-file could not be read.");
|
|
9950
10537
|
}
|
|
@@ -9961,7 +10548,7 @@ function decisionRequest(ctx, o) {
|
|
|
9961
10548
|
throw usage2("--file holds the whole request; don't combine it with --state or --question.");
|
|
9962
10549
|
let body;
|
|
9963
10550
|
try {
|
|
9964
|
-
body = JSON.parse(
|
|
10551
|
+
body = JSON.parse(readFileSync6(resolve3(ctx.cwd, o.file), "utf8"));
|
|
9965
10552
|
} catch {
|
|
9966
10553
|
throw usage2("--file must be a readable JSON file.");
|
|
9967
10554
|
}
|
|
@@ -9995,7 +10582,7 @@ async function decide2(ctx, body) {
|
|
|
9995
10582
|
}
|
|
9996
10583
|
|
|
9997
10584
|
// src/realtime.ts
|
|
9998
|
-
import { readFileSync as
|
|
10585
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
9999
10586
|
import { resolve as resolve4 } from "node:path";
|
|
10000
10587
|
var SAMPLE_RATE = 24000;
|
|
10001
10588
|
var MAX_INPUT_SECONDS = 300;
|
|
@@ -10060,7 +10647,7 @@ async function realtime(ctx, o) {
|
|
|
10060
10647
|
throw new CliError("unsupported_runtime", "This command needs a WebSocket client (Node.js 22 or newer).", "Upgrade Node.js to 22+.");
|
|
10061
10648
|
let pcm;
|
|
10062
10649
|
try {
|
|
10063
|
-
pcm = readWav(
|
|
10650
|
+
pcm = readWav(readFileSync7(resolve4(ctx.cwd, o.input)));
|
|
10064
10651
|
} catch (e) {
|
|
10065
10652
|
if (e instanceof CliError)
|
|
10066
10653
|
throw e;
|
|
@@ -10152,7 +10739,7 @@ async function realtime(ctx, o) {
|
|
|
10152
10739
|
});
|
|
10153
10740
|
const audio = Buffer.concat(out);
|
|
10154
10741
|
const path = resolve4(ctx.cwd, o.output);
|
|
10155
|
-
|
|
10742
|
+
writeFileSync4(path, wavOf(audio));
|
|
10156
10743
|
return {
|
|
10157
10744
|
model: o.model,
|
|
10158
10745
|
region,
|
|
@@ -10202,7 +10789,7 @@ function num(flag, v) {
|
|
|
10202
10789
|
var init2 = defineCommand({
|
|
10203
10790
|
meta: {
|
|
10204
10791
|
name: "init",
|
|
10205
|
-
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."
|
|
10206
10793
|
},
|
|
10207
10794
|
args: {
|
|
10208
10795
|
...jsonFlag,
|
|
@@ -10216,6 +10803,19 @@ var init2 = defineCommand({
|
|
|
10216
10803
|
"ad-click": {
|
|
10217
10804
|
type: "string",
|
|
10218
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)"
|
|
10219
10819
|
}
|
|
10220
10820
|
},
|
|
10221
10821
|
run: ({ args }) => run(() => init(makeCtx(), {
|
|
@@ -10223,9 +10823,70 @@ var init2 = defineCommand({
|
|
|
10223
10823
|
name: args.name,
|
|
10224
10824
|
email: args.email,
|
|
10225
10825
|
source: args.source,
|
|
10226
|
-
adClick: args["ad-click"]
|
|
10826
|
+
adClick: args["ad-click"],
|
|
10827
|
+
account: args.account,
|
|
10828
|
+
newAccount: args["new-account"],
|
|
10829
|
+
noSave: args.save === false
|
|
10227
10830
|
}), args)
|
|
10228
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
|
+
});
|
|
10229
10890
|
var models = defineCommand({
|
|
10230
10891
|
meta: { name: "models", description: "List models with prices and benchmarks, newest first; or show one" },
|
|
10231
10892
|
args: {
|
|
@@ -10504,7 +11165,7 @@ function autoReloadArgs(args) {
|
|
|
10504
11165
|
if (below === undefined && amount === undefined && max === undefined)
|
|
10505
11166
|
return;
|
|
10506
11167
|
if (below === undefined || amount === undefined)
|
|
10507
|
-
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");
|
|
10508
11169
|
return { threshold_usd: below, amount_usd: amount, ...max !== undefined ? { monthly_limit_usd: max } : {} };
|
|
10509
11170
|
}
|
|
10510
11171
|
var billing = defineCommand({
|
|
@@ -10521,20 +11182,22 @@ var billing = defineCommand({
|
|
|
10521
11182
|
topup: defineCommand({
|
|
10522
11183
|
meta: {
|
|
10523
11184
|
name: "topup",
|
|
10524
|
-
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"
|
|
10525
11186
|
},
|
|
10526
11187
|
args: {
|
|
10527
11188
|
...jsonFlag,
|
|
10528
11189
|
"amount-usd": {
|
|
10529
11190
|
type: "string",
|
|
10530
|
-
|
|
10531
|
-
|
|
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)"
|
|
10532
11196
|
},
|
|
10533
|
-
"auto-reload-
|
|
10534
|
-
"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)" },
|
|
10535
11198
|
"auto-reload-monthly-max": {
|
|
10536
11199
|
type: "string",
|
|
10537
|
-
description: "
|
|
11200
|
+
description: "Optional: most auto-reload may charge per calendar month (USD; default: no monthly limit)"
|
|
10538
11201
|
},
|
|
10539
11202
|
open: {
|
|
10540
11203
|
type: "boolean",
|
|
@@ -10547,7 +11210,7 @@ var billing = defineCommand({
|
|
|
10547
11210
|
}
|
|
10548
11211
|
},
|
|
10549
11212
|
run: async ({ args }) => {
|
|
10550
|
-
const amountUsd = num("amount-usd", args["amount-usd"]);
|
|
11213
|
+
const amountUsd = num("amount-usd", args["amount-usd"]) ?? 20;
|
|
10551
11214
|
const autoReload = autoReloadArgs(args);
|
|
10552
11215
|
checkTopupLimits(amountUsd, autoReload);
|
|
10553
11216
|
const noOpen = args.open === false;
|
|
@@ -10575,8 +11238,35 @@ var billing = defineCommand({
|
|
|
10575
11238
|
}
|
|
10576
11239
|
}),
|
|
10577
11240
|
"auto-reload": defineCommand({
|
|
10578
|
-
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
|
+
},
|
|
10579
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
|
+
}),
|
|
10580
11270
|
off: defineCommand({
|
|
10581
11271
|
meta: { name: "off", description: "Disable auto-reload. No approval needed" },
|
|
10582
11272
|
args: jsonFlag,
|
|
@@ -10802,6 +11492,31 @@ var mcp = defineCommand({
|
|
|
10802
11492
|
args: jsonFlag,
|
|
10803
11493
|
run: ({ args }) => run(() => mcpSetup(makeCtx()), args)
|
|
10804
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
|
+
});
|
|
10805
11520
|
var root = defineCommand({
|
|
10806
11521
|
meta: {
|
|
10807
11522
|
name: "scaledthought",
|
|
@@ -10810,6 +11525,8 @@ var root = defineCommand({
|
|
|
10810
11525
|
},
|
|
10811
11526
|
subCommands: {
|
|
10812
11527
|
init: init2,
|
|
11528
|
+
login: login2,
|
|
11529
|
+
use,
|
|
10813
11530
|
models,
|
|
10814
11531
|
benchmarks: benchmarks2,
|
|
10815
11532
|
status: status2,
|
|
@@ -10827,7 +11544,9 @@ var root = defineCommand({
|
|
|
10827
11544
|
video,
|
|
10828
11545
|
webhooks,
|
|
10829
11546
|
doctor: doctor2,
|
|
10830
|
-
mcp
|
|
11547
|
+
mcp,
|
|
11548
|
+
dashboard,
|
|
11549
|
+
members: members2
|
|
10831
11550
|
}
|
|
10832
11551
|
});
|
|
10833
11552
|
var NUMERIC_FLAGS = new Set([
|