scaledthought 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -4
- package/dist/index.js +173 -50
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,8 +10,9 @@ Agents: the full setup protocol is at <https://scaledthought.com/llms.txt>.
|
|
|
10
10
|
npx -y scaledthought@latest init --region us # or: --region ca (data stays in Canada)
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
`init` creates
|
|
14
|
-
|
|
13
|
+
`init` creates an account, writes `ST_API_KEY` and `ST_BASE_URL` to `./.env` (mode 0600), adds `.env` to
|
|
14
|
+
`.gitignore`, and prints the account, its credit balance (`balance_usd`, `needs_credits`), the models live in your
|
|
15
|
+
region, `next_command` and `next_steps`.
|
|
15
16
|
Running it again is safe. If a key already exists it returns `"already_initialized": true` and the
|
|
16
17
|
account status instead of creating a second account.
|
|
17
18
|
|
|
@@ -25,6 +26,19 @@ ad platform). `account` and `account ad-measurement` print `ad_measurement` (tru
|
|
|
25
26
|
account could be sent: a `us` account that signed up with an ad click and hasn't opted out; always false in `ca`) and
|
|
26
27
|
`ad_measurement_opt_out` (the stored preference). Neither flag is a secret.
|
|
27
28
|
|
|
29
|
+
Inference is prepaid, and a new account has no credits: until your human pays, completions return
|
|
30
|
+
`402 insufficient_credits`. So the first thing after `init` is a top-up your human approves:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx -y scaledthought@latest billing topup --amount-usd 5 # prints approval_url and topup_id
|
|
34
|
+
# show your human the approval_url, exactly as printed; they pay by card
|
|
35
|
+
npx -y scaledthought@latest billing wait # returns once they've paid; on topup_pending run it again
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`billing wait` returns within about 90 seconds (`--timeout-s` to change it), under Claude Code's default 2-minute
|
|
39
|
+
shell timeout. Prefer it to `billing topup --wait`: an agent's shell tool usually shows a command's output only when it
|
|
40
|
+
ends, so the link would reach the human only after the payment it is waiting for.
|
|
41
|
+
|
|
28
42
|
```bash
|
|
29
43
|
npx -y scaledthought@latest models --live # models serving in your region now
|
|
30
44
|
npx -y scaledthought@latest chat -m auto "Reply with OK" # one real completion
|
|
@@ -33,7 +47,7 @@ npx -y scaledthought@latest account # plan, region, spend
|
|
|
33
47
|
npx -y scaledthought@latest account ad-measurement off # opt out of ad measurement
|
|
34
48
|
```
|
|
35
49
|
|
|
36
|
-
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|auto-reload|transactions`, `batches …`,
|
|
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 …`,
|
|
37
51
|
`fine-tune …`, `benchmarks <id>`, `status` (with your account's notices when a key is set), `doctor` (setup check),
|
|
38
52
|
`webhooks add <url> [--events …]|list|remove <id>` (signed callbacks), `transcribe <file>` (speech to text, `--translate` for English,
|
|
39
53
|
`--format srt|vtt|text|verbose_json`) and `speak -o out.mp3 "text"` (text to speech, streamed to the file; US
|
|
@@ -46,4 +60,4 @@ Use the key from any OpenAI SDK: `base_url = $ST_BASE_URL`, `api_key = $ST_API_K
|
|
|
46
60
|
- The key's prefix (`st_us_…` / `st_ca_…`) picks the region's hosts. Requests never cross regions.
|
|
47
61
|
- Errors look like `{"error":{"code":"…","message":"…","hint":"…"}}`. Exit code 2 means bad usage, 1 anything else.
|
|
48
62
|
|
|
49
|
-
|
|
63
|
+
Before the first credit purchase an account has lower limits: 60 requests/min per key and up to 5 keys.
|
package/dist/index.js
CHANGED
|
@@ -553,8 +553,8 @@ import { join } from "node:path";
|
|
|
553
553
|
// package.json
|
|
554
554
|
var package_default = {
|
|
555
555
|
name: "scaledthought",
|
|
556
|
-
version: "0.1
|
|
557
|
-
description: "Headless open-model inference, run by your agent.
|
|
556
|
+
version: "0.2.1",
|
|
557
|
+
description: "Headless open-model inference, run by your agent. Prepaid, pay per token. US or Canada.",
|
|
558
558
|
license: "MIT",
|
|
559
559
|
keywords: [
|
|
560
560
|
"llm",
|
|
@@ -631,6 +631,7 @@ function readDotEnv(cwd) {
|
|
|
631
631
|
}
|
|
632
632
|
return out;
|
|
633
633
|
}
|
|
634
|
+
var CREDITS_HINT = "This account has no credits for this request (inference is prepaid). Run `npx -y scaledthought@latest billing topup --amount-usd 5` (creating it charges nothing), end your turn giving your human its message_for_human (the approval_url), and once they say they've paid run `npx -y scaledthought@latest billing wait`, then retry. Don't retry before that or create another account.";
|
|
634
635
|
var HINTS = {
|
|
635
636
|
no_key: "Run `npx -y scaledthought@latest init --region us|ca` in this directory, or set ST_API_KEY.",
|
|
636
637
|
bad_key: "ST_API_KEY must look like st_{us|ca}_{live|test}_<40 chars>. Re-run init in a directory without one.",
|
|
@@ -641,17 +642,16 @@ var HINTS = {
|
|
|
641
642
|
expiry_exceeds_parent: "A key can't outlive the key that creates it. Pass a shorter `--expires-in`.",
|
|
642
643
|
budget_exceeds_parent: "A sub-agent key's daily budget can't exceed what its parent has left today. Pass a smaller `--budget-usd`.",
|
|
643
644
|
budget_exceeded: "This key's daily budget is spent. It resets at 00:00 UTC, or raise it with MCP budget_manage (accounts with credits).",
|
|
644
|
-
insufficient_credits:
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
credits_required_for_fine_tuning: "Fine-tuning is paid from prepaid credits, not the free allowance. Run `billing topup --amount-usd 5` and send your human the approval_url, then retry.",
|
|
645
|
+
insufficient_credits: CREDITS_HINT,
|
|
646
|
+
credits_required_for_batches: `Batches are paid from prepaid credits. ${CREDITS_HINT}`,
|
|
647
|
+
credits_required_for_fine_tuning: `Fine-tuning is paid from prepaid credits. ${CREDITS_HINT}`,
|
|
648
648
|
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
649
|
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: "
|
|
651
|
-
free_tier_key_limit: "
|
|
650
|
+
free_tier_limit: "Accounts that haven't bought credits have lower limits. The first credit purchase lifts them: run `billing topup --amount-usd 5` and show your human the approval_url.",
|
|
651
|
+
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
652
|
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
653
|
daily_topup_limit: "Top-ups are limited to $2,000 per account per rolling 24 hours (open links and auto-reloads included). Use a smaller amount (see available_usd) or wait.",
|
|
654
|
-
billing_upgrade_removed: "Run `billing topup --amount-usd 5` and
|
|
654
|
+
billing_upgrade_removed: "Run `billing topup --amount-usd 5` and show your human the approval_url.",
|
|
655
655
|
gone: "This endpoint was removed. For credits use `billing topup --amount-usd 5`.",
|
|
656
656
|
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
657
|
rate_limit_exceeded: "Wait the number of seconds in retry_after, then retry.",
|
|
@@ -756,6 +756,8 @@ async function responseError(res) {
|
|
|
756
756
|
const retry = res.headers.get("retry-after");
|
|
757
757
|
if (retry)
|
|
758
758
|
extra.retry_after = Number(retry);
|
|
759
|
+
if (!hint && res.status === 402 && !HINTS[code])
|
|
760
|
+
hint = CREDITS_HINT;
|
|
759
761
|
return new CliError(code, data.error?.message ?? (res.statusText || `HTTP ${res.status}`), hint, extra);
|
|
760
762
|
}
|
|
761
763
|
async function api(ctx, method, path, opts = {}) {
|
|
@@ -902,6 +904,7 @@ async function speak(ctx, o) {
|
|
|
902
904
|
}
|
|
903
905
|
|
|
904
906
|
// src/commands.ts
|
|
907
|
+
import { spawn } from "node:child_process";
|
|
905
908
|
import { chmodSync, existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
906
909
|
import { join as join3, resolve as resolve2 } from "node:path";
|
|
907
910
|
|
|
@@ -9220,7 +9223,7 @@ var GrowthResults = object({
|
|
|
9220
9223
|
decisions: array(GrowthDecision)
|
|
9221
9224
|
});
|
|
9222
9225
|
// ../../packages/schemas/src/legal.ts
|
|
9223
|
-
var TERMS_VERSION = "2026-10-
|
|
9226
|
+
var TERMS_VERSION = "2026-10-10";
|
|
9224
9227
|
var LEGAL_SITE = "https://scaledthought.com";
|
|
9225
9228
|
var LEGAL_PATHS = {
|
|
9226
9229
|
index: "/legal",
|
|
@@ -9238,23 +9241,28 @@ var REFUND_POLICY_URL = `${LEGAL_SITE}${LEGAL_PATHS.creditsAndRefunds}`;
|
|
|
9238
9241
|
var TERMS_NOTICE = `By creating this account you accept the ScaledThought Terms of Service (${TERMS_URL}) and Privacy Policy ` + `(${PRIVACY_URL}) on behalf of the person or organisation you act for, who is the customer.`;
|
|
9239
9242
|
|
|
9240
9243
|
// src/commands.ts
|
|
9241
|
-
var FREE_TIER = {
|
|
9242
|
-
usd_per_day: 1,
|
|
9243
|
-
allowance_days: 30,
|
|
9244
|
-
requests_per_min: 60,
|
|
9245
|
-
max_keys: 5,
|
|
9246
|
-
card_required: false
|
|
9247
|
-
};
|
|
9248
9244
|
var NPX = "npx -y scaledthought@latest";
|
|
9249
|
-
|
|
9245
|
+
var FIRST_TOPUP_USD = 5;
|
|
9246
|
+
var TOPUP_COMMAND = `${NPX} billing topup --amount-usd ${FIRST_TOPUP_USD}`;
|
|
9247
|
+
var WAIT_COMMAND = `${NPX} billing wait`;
|
|
9248
|
+
var CREDIT_STEPS = [
|
|
9249
|
+
`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
|
+
`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. End your turn with message_for_human (it says the checkout page is open and holds the approval_url exactly as printed, in case it didn't open): the one-time payment page where your human accepts the Terms and pays $${FIRST_TOPUP_USD} by card. Don't run anything that waits before they have it, don't retry the call and don't create another account.`,
|
|
9252
|
+
`3. When they say they've paid, run: ${WAIT_COMMAND}. It returns status "paid" with the new balance (at once if already paid); topup_pending means not yet.`,
|
|
9253
|
+
"4. Then make the call below."
|
|
9254
|
+
];
|
|
9255
|
+
function nextSteps(model, needsCredits) {
|
|
9250
9256
|
return [
|
|
9257
|
+
...needsCredits ? CREDIT_STEPS : [],
|
|
9251
9258
|
model ? `Verify with one real completion: ${NPX} chat -m ${model} "Reply with OK" (or POST $ST_BASE_URL/chat/completions with that model)` : `No chat model is live in this region right now. Check again with: ${NPX} models --live`,
|
|
9252
9259
|
`Or let the router pick: ${NPX} chat -m auto "Reply with OK"`,
|
|
9253
9260
|
"Use it from code: any OpenAI SDK with base_url=$ST_BASE_URL and api_key=$ST_API_KEY (both are in .env)",
|
|
9254
9261
|
`Optional, connect MCP: ${NPX} mcp`,
|
|
9255
|
-
|
|
9262
|
+
...needsCredits ? [] : [`More credits when needed: ${TOPUP_COMMAND} returns an approval_url for your human (inference is prepaid)`]
|
|
9256
9263
|
];
|
|
9257
9264
|
}
|
|
9265
|
+
var balanceOf = (b) => b && typeof b.balance_usd === "number" ? b.balance_usd : null;
|
|
9258
9266
|
function termsOf(acct = {}) {
|
|
9259
9267
|
return {
|
|
9260
9268
|
url: acct.terms_url ?? TERMS_URL,
|
|
@@ -9263,10 +9271,18 @@ function termsOf(acct = {}) {
|
|
|
9263
9271
|
notice: acct.terms_notice ?? TERMS_NOTICE
|
|
9264
9272
|
};
|
|
9265
9273
|
}
|
|
9266
|
-
async function liveSummary(ctx, region) {
|
|
9274
|
+
async function liveSummary(ctx, region, balanceUsd) {
|
|
9267
9275
|
const live = await listModels(ctx, { live: true, region });
|
|
9268
9276
|
const suggested = live.find((m) => m.category !== "embedding" && m.modality === "chat")?.id ?? null;
|
|
9269
|
-
|
|
9277
|
+
const needsCredits = (balanceUsd ?? 0) <= 0;
|
|
9278
|
+
return {
|
|
9279
|
+
balance_usd: balanceUsd ?? 0,
|
|
9280
|
+
needs_credits: needsCredits,
|
|
9281
|
+
next_command: needsCredits ? TOPUP_COMMAND : `${NPX} chat -m ${suggested ?? "auto"} "Reply with OK"`,
|
|
9282
|
+
newest_models: live.slice(0, 5),
|
|
9283
|
+
suggested_model: suggested,
|
|
9284
|
+
next_steps: nextSteps(suggested, needsCredits)
|
|
9285
|
+
};
|
|
9270
9286
|
}
|
|
9271
9287
|
function signupAttribution(region, opts) {
|
|
9272
9288
|
const warnings = [];
|
|
@@ -9302,13 +9318,13 @@ async function init(ctx, opts) {
|
|
|
9302
9318
|
throw new CliError("region_mismatch", `A ${region} key already exists (${where}) but --region ${opts.region} was requested. Nothing was created.`, `Accounts are pinned to one region. Use the existing ${region} account, or run init in a directory with no ST_API_KEY (and no ST_API_KEY in the environment) to create a separate ${opts.region} account.`, { existing_region: region, requested_region: opts.region });
|
|
9303
9319
|
const status = await account(ctx);
|
|
9304
9320
|
const ignored = [opts.source !== undefined && "--source", opts.adClick !== undefined && "--ad-click"].filter(Boolean);
|
|
9321
|
+
const shown = withoutFreeFields(status);
|
|
9305
9322
|
return {
|
|
9306
9323
|
already_initialized: true,
|
|
9307
|
-
...
|
|
9324
|
+
...shown,
|
|
9308
9325
|
key_saved_to: where,
|
|
9309
|
-
free_tier: FREE_TIER,
|
|
9310
9326
|
terms: termsOf(),
|
|
9311
|
-
...await liveSummary(ctx, region),
|
|
9327
|
+
...await liveSummary(ctx, region, balanceOf(status)),
|
|
9312
9328
|
...ignored.length ? { warnings: [`${ignored.join(" and ")} apply only to a new account and were not sent.`] } : {}
|
|
9313
9329
|
};
|
|
9314
9330
|
}
|
|
@@ -9328,17 +9344,16 @@ async function init(ctx, opts) {
|
|
|
9328
9344
|
writeEnv(ctx.cwd, { ST_API_KEY: acct.api_key, ST_BASE_URL: acct.base_url });
|
|
9329
9345
|
ensureGitignored(ctx.cwd);
|
|
9330
9346
|
const keyed = { ...ctx, env: { ...ctx.env, ST_API_KEY: acct.api_key } };
|
|
9347
|
+
const billing = await api(keyed, "GET", "/v1/billing").catch(() => null);
|
|
9331
9348
|
return {
|
|
9332
9349
|
already_initialized: false,
|
|
9333
9350
|
account_id: acct.account_id,
|
|
9334
9351
|
region: acct.region,
|
|
9335
|
-
plan: "free",
|
|
9336
9352
|
base_url: acct.base_url,
|
|
9337
9353
|
key_saved_to: ".env (ST_API_KEY, ST_BASE_URL)",
|
|
9338
|
-
free_tier: FREE_TIER,
|
|
9339
9354
|
terms: termsOf(acct),
|
|
9340
9355
|
...attribution.source ? { source: acct.source ?? attribution.source } : {},
|
|
9341
|
-
...await liveSummary(keyed, acct.region),
|
|
9356
|
+
...await liveSummary(keyed, acct.region, balanceOf(billing)),
|
|
9342
9357
|
...attribution.warnings.length ? { warnings: attribution.warnings } : {}
|
|
9343
9358
|
};
|
|
9344
9359
|
}
|
|
@@ -9409,9 +9424,20 @@ function unitPrices(p) {
|
|
|
9409
9424
|
};
|
|
9410
9425
|
}
|
|
9411
9426
|
var showModel = (ctx, id, region) => api(ctx, "GET", `/v1/models/${encodeURIComponent(id)}`, { auth: false, region: publicRegion(ctx, region) });
|
|
9427
|
+
function withoutFreeFields(o) {
|
|
9428
|
+
const out = {};
|
|
9429
|
+
for (const [k, v] of Object.entries(o)) {
|
|
9430
|
+
if (/^free_/.test(k))
|
|
9431
|
+
continue;
|
|
9432
|
+
if (k === "topup" && typeof v === "string" && /free/i.test(v))
|
|
9433
|
+
continue;
|
|
9434
|
+
out[k] = v;
|
|
9435
|
+
}
|
|
9436
|
+
return out;
|
|
9437
|
+
}
|
|
9412
9438
|
async function account(ctx) {
|
|
9413
|
-
const a = await api(ctx, "GET", "/v1/account");
|
|
9414
|
-
const fields = ["balance_usd", "
|
|
9439
|
+
const a = withoutFreeFields(await api(ctx, "GET", "/v1/account"));
|
|
9440
|
+
const fields = ["balance_usd", "auto_reload"];
|
|
9415
9441
|
if (fields.every((f) => (f in a)))
|
|
9416
9442
|
return a;
|
|
9417
9443
|
const b = await api(ctx, "GET", "/v1/billing").catch(() => null);
|
|
@@ -9512,7 +9538,7 @@ async function chat(ctx, o) {
|
|
|
9512
9538
|
usage: usageOut
|
|
9513
9539
|
};
|
|
9514
9540
|
}
|
|
9515
|
-
var billingStatus = (ctx) => api(ctx, "GET", "/v1/billing");
|
|
9541
|
+
var billingStatus = async (ctx) => withoutFreeFields(await api(ctx, "GET", "/v1/billing"));
|
|
9516
9542
|
var TOPUP_LIMITS = { min_usd: 5, max_usd: 2000, daily_max_usd: 2000, auto_reload_monthly_max_usd: 1e4 };
|
|
9517
9543
|
function checkTopupLimits(amountUsd, autoReload) {
|
|
9518
9544
|
const { min_usd, max_usd, daily_max_usd, auto_reload_monthly_max_usd } = TOPUP_LIMITS;
|
|
@@ -9529,20 +9555,91 @@ function checkTopupLimits(amountUsd, autoReload) {
|
|
|
9529
9555
|
range("auto-reload-monthly-max", autoReload.monthly_limit_usd, autoReload.amount_usd, auto_reload_monthly_max_usd);
|
|
9530
9556
|
}
|
|
9531
9557
|
var TOPUP_DONE = new Set(["paid", "expired", "failed", "canceled"]);
|
|
9532
|
-
function topupNextSteps(amountUsd, autoReload) {
|
|
9558
|
+
function topupNextSteps(topupId, amountUsd, opened, autoReload) {
|
|
9533
9559
|
return [
|
|
9534
|
-
|
|
9560
|
+
...opened ? ["The checkout page is already open in your human's browser: don't open it again or paste the link on its own."] : [],
|
|
9561
|
+
`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). 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. 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.`,
|
|
9535
9562
|
...autoReload ? [
|
|
9536
9563
|
`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` : ""}.`
|
|
9537
9564
|
] : [],
|
|
9538
|
-
`
|
|
9565
|
+
`When they say they've paid: ${WAIT_COMMAND}${topupId ? ` ${topupId}` : ""} (returns status "paid" with the balance, at once if already paid; topup_pending means not yet: tell them, don't make a new link).`,
|
|
9566
|
+
"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."
|
|
9539
9567
|
];
|
|
9540
9568
|
}
|
|
9569
|
+
function openInBrowser(url) {
|
|
9570
|
+
if (!/^https:\/\//.test(url))
|
|
9571
|
+
return Promise.resolve(false);
|
|
9572
|
+
const [bin, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", '""', url.replace(/&/g, "^&")]] : ["xdg-open", [url]];
|
|
9573
|
+
return new Promise((done) => {
|
|
9574
|
+
try {
|
|
9575
|
+
const child = spawn(bin, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
9576
|
+
child.once("error", () => done(false));
|
|
9577
|
+
child.once("spawn", () => {
|
|
9578
|
+
child.unref();
|
|
9579
|
+
done(true);
|
|
9580
|
+
});
|
|
9581
|
+
} catch {
|
|
9582
|
+
done(false);
|
|
9583
|
+
}
|
|
9584
|
+
});
|
|
9585
|
+
}
|
|
9541
9586
|
async function billingTopup(ctx, o) {
|
|
9542
9587
|
const created = await api(ctx, "POST", "/v1/billing/topup", {
|
|
9543
9588
|
body: { amount_usd: o.amountUsd, ...o.autoReload ? { auto_reload: o.autoReload } : {} }
|
|
9544
9589
|
});
|
|
9545
|
-
|
|
9590
|
+
const noOpen = o.noOpen || ctx.env.ST_NO_BROWSER === "1";
|
|
9591
|
+
const opened = !noOpen && !!created.approval_url && await (o.open ?? openInBrowser)(created.approval_url);
|
|
9592
|
+
return {
|
|
9593
|
+
status: "pending",
|
|
9594
|
+
...created,
|
|
9595
|
+
opened_in_browser: opened,
|
|
9596
|
+
message_for_human: messageForHuman(created.approval_url, o.amountUsd, o.autoReload, opened),
|
|
9597
|
+
next_steps: topupNextSteps(created.topup_id, o.amountUsd, opened, o.autoReload)
|
|
9598
|
+
};
|
|
9599
|
+
}
|
|
9600
|
+
function messageForHuman(approvalUrl, amountUsd, autoReload, opened = false) {
|
|
9601
|
+
if (!approvalUrl)
|
|
9602
|
+
return null;
|
|
9603
|
+
const reload = autoReload ? ` It also turns on auto-reload: $${autoReload.amount_usd} whenever the balance drops below $${autoReload.threshold_usd}${autoReload.monthly_limit_usd ? `, at most $${autoReload.monthly_limit_usd} a month` : ""}.` : "";
|
|
9604
|
+
if (opened)
|
|
9605
|
+
return `To finish the setup, I've opened the ScaledThought checkout page in your browser to buy $${amountUsd} of prepaid credits (USD). If it didn't open, use this link:
|
|
9606
|
+
|
|
9607
|
+
${approvalUrl}
|
|
9608
|
+
|
|
9609
|
+
On that page you accept the Terms of Service and the Credits & Refunds policy and pay by card. Nothing is charged until you approve.${reload} Tell me when it's done and I'll continue.`;
|
|
9610
|
+
return `To finish the setup, please buy $${amountUsd} of prepaid ScaledThought credits (USD) here:
|
|
9611
|
+
|
|
9612
|
+
${approvalUrl}
|
|
9613
|
+
|
|
9614
|
+
On that page you accept the Terms of Service and the Credits & Refunds policy and pay by card. Nothing is charged until you approve.${reload} Tell me when it's done and I'll continue.`;
|
|
9615
|
+
}
|
|
9616
|
+
var WAIT_DEFAULT_S = 90;
|
|
9617
|
+
async function billingWait(ctx, o = {}) {
|
|
9618
|
+
let id = o.topupId;
|
|
9619
|
+
if (id !== undefined && !/^top_[0-9A-Za-z]+$/.test(id))
|
|
9620
|
+
throw new CliError("bad_argument", `"${id}" is not a top-up id (top_...).`, "Use topup_id from `billing topup`.");
|
|
9621
|
+
if (!id) {
|
|
9622
|
+
const b = await api(ctx, "GET", "/v1/billing");
|
|
9623
|
+
const pending = (b.pending_topups ?? []).filter((t) => t.status === "pending" && t.approval_url);
|
|
9624
|
+
id = pending[0]?.topup_id;
|
|
9625
|
+
if (!id) {
|
|
9626
|
+
const paid = (b.recent_transactions ?? []).find((t) => t.topup_id)?.topup_id ?? null;
|
|
9627
|
+
if ((b.balance_usd ?? 0) > 0)
|
|
9628
|
+
return { status: "paid", topup_id: paid, billing: withoutFreeFields(b) };
|
|
9629
|
+
throw new CliError("no_pending_topup", "There is no top-up waiting for payment and the balance is $0.", `Run \`${TOPUP_COMMAND}\` and show your human the approval_url, then run \`${WAIT_COMMAND}\`.`);
|
|
9630
|
+
}
|
|
9631
|
+
}
|
|
9632
|
+
const deadline = Date.now() + (o.timeoutS ?? WAIT_DEFAULT_S) * 1000;
|
|
9633
|
+
for (;; ) {
|
|
9634
|
+
const t = await api(ctx, "GET", `/v1/billing/topups/${encodeURIComponent(id)}`);
|
|
9635
|
+
if (t.status === "paid")
|
|
9636
|
+
return { status: "paid", topup_id: id, billing: await billingStatus(ctx) };
|
|
9637
|
+
if (TOPUP_DONE.has(t.status ?? ""))
|
|
9638
|
+
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
|
+
if (Date.now() >= deadline)
|
|
9640
|
+
throw new CliError("topup_pending", `Top-up ${id} isn't paid yet.`, `Not paid yet. If your human doesn't have the approval_url (below) yet, end your turn now and give it to them. Otherwise tell them it's still unpaid and run \`${WAIT_COMMAND} ${id}\` again once they say they've paid. Don't create a new top-up.`, { topup_id: id, approval_url: t.approval_url ?? null });
|
|
9641
|
+
await new Promise((r) => setTimeout(r, o.pollMs ?? 3000));
|
|
9642
|
+
}
|
|
9546
9643
|
}
|
|
9547
9644
|
async function billingTopupAndWait(ctx, o) {
|
|
9548
9645
|
const created = await billingTopup(ctx, o);
|
|
@@ -9734,13 +9831,10 @@ async function doctor(ctx) {
|
|
|
9734
9831
|
next: err.hint ?? "Fix the error above, then run doctor again."
|
|
9735
9832
|
};
|
|
9736
9833
|
}
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
ok: credit,
|
|
9742
|
-
detail: `balance $${a.balance_usd ?? 0}, free left today $${a.free_remaining_today_usd ?? 0}`
|
|
9743
|
-
});
|
|
9834
|
+
const purchased = a.plan === "paid" ? "credits bought" : "no credit purchase yet";
|
|
9835
|
+
checks.push({ check: "account", ok: a.status === "active", detail: `${a.account_id} (${purchased}, ${a.status})` });
|
|
9836
|
+
const credit = (a.balance_usd ?? 0) > 0;
|
|
9837
|
+
checks.push({ check: "credit", ok: credit, detail: `balance $${a.balance_usd ?? 0}` });
|
|
9744
9838
|
const notices = a.notices ?? [];
|
|
9745
9839
|
return {
|
|
9746
9840
|
ok: checks.every((c) => c.ok) && notices.length === 0,
|
|
@@ -9750,10 +9844,10 @@ async function doctor(ctx) {
|
|
|
9750
9844
|
checks,
|
|
9751
9845
|
notices,
|
|
9752
9846
|
notice_text: a.notice_text ?? [],
|
|
9753
|
-
next: notices.length ? "Tell your human about each notice (notice_text)." : credit ? null :
|
|
9847
|
+
next: notices.length ? "Tell your human about each notice (notice_text)." : credit ? null : `No credits: run \`${TOPUP_COMMAND}\`, show your human the approval_url, then run \`${WAIT_COMMAND}\`.`
|
|
9754
9848
|
};
|
|
9755
9849
|
}
|
|
9756
|
-
var WEBHOOK_EVENTS = ["balance.low", "
|
|
9850
|
+
var WEBHOOK_EVENTS = ["balance.low", "key.expiring", "budget.exhausted"];
|
|
9757
9851
|
async function webhooksAdd(ctx, o) {
|
|
9758
9852
|
if (!/^https:\/\//i.test(o.url))
|
|
9759
9853
|
throw new CliError("bad_argument", "The webhook URL must be https://.", "Pass a public https endpoint.");
|
|
@@ -10108,7 +10202,7 @@ function num(flag, v) {
|
|
|
10108
10202
|
var init2 = defineCommand({
|
|
10109
10203
|
meta: {
|
|
10110
10204
|
name: "init",
|
|
10111
|
-
description: "Create
|
|
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 the Terms of Service (scaledthought.com/legal/terms) for the human you act for."
|
|
10112
10206
|
},
|
|
10113
10207
|
args: {
|
|
10114
10208
|
...jsonFlag,
|
|
@@ -10163,7 +10257,7 @@ var models = defineCommand({
|
|
|
10163
10257
|
});
|
|
10164
10258
|
var ACCOUNT_SUBCOMMANDS = new Set(["delete", "ad-measurement"]);
|
|
10165
10259
|
var account2 = defineCommand({
|
|
10166
|
-
meta: { name: "account", description: "Region, spend, credit balance and
|
|
10260
|
+
meta: { name: "account", description: "Region, spend, credit balance and auto-reload" },
|
|
10167
10261
|
args: jsonFlag,
|
|
10168
10262
|
run: ({ args, rawArgs }) => {
|
|
10169
10263
|
if (rawArgs.some((a) => ACCOUNT_SUBCOMMANDS.has(a)))
|
|
@@ -10419,7 +10513,7 @@ var billing = defineCommand({
|
|
|
10419
10513
|
status: defineCommand({
|
|
10420
10514
|
meta: {
|
|
10421
10515
|
name: "status",
|
|
10422
|
-
description: "
|
|
10516
|
+
description: "Credit balance, auto-reload, pending top-ups (with their approval links), recent transactions"
|
|
10423
10517
|
},
|
|
10424
10518
|
args: jsonFlag,
|
|
10425
10519
|
run: ({ args }) => run(() => billingStatus(makeCtx()), args)
|
|
@@ -10427,7 +10521,7 @@ var billing = defineCommand({
|
|
|
10427
10521
|
topup: defineCommand({
|
|
10428
10522
|
meta: {
|
|
10429
10523
|
name: "topup",
|
|
10430
|
-
description: "Buy prepaid credits:
|
|
10524
|
+
description: "Buy prepaid credits: opens the checkout page in your human's browser (approval_url), then run `billing wait`. Optional auto-reload"
|
|
10431
10525
|
},
|
|
10432
10526
|
args: {
|
|
10433
10527
|
...jsonFlag,
|
|
@@ -10442,13 +10536,42 @@ var billing = defineCommand({
|
|
|
10442
10536
|
type: "string",
|
|
10443
10537
|
description: "Most auto-reload may charge per calendar month (USD, default 5x the amount)"
|
|
10444
10538
|
},
|
|
10445
|
-
|
|
10539
|
+
open: {
|
|
10540
|
+
type: "boolean",
|
|
10541
|
+
default: true,
|
|
10542
|
+
description: "Open the checkout page in the browser (--no-open only prints approval_url)"
|
|
10543
|
+
},
|
|
10544
|
+
wait: {
|
|
10545
|
+
type: "boolean",
|
|
10546
|
+
description: "Poll until the human pays or the link expires or fails. Only if your human sees your terminal live: an agent's shell tool shows the link only after the command ends. Prefer `billing wait`"
|
|
10547
|
+
}
|
|
10446
10548
|
},
|
|
10447
10549
|
run: async ({ args }) => {
|
|
10448
10550
|
const amountUsd = num("amount-usd", args["amount-usd"]);
|
|
10449
10551
|
const autoReload = autoReloadArgs(args);
|
|
10450
10552
|
checkTopupLimits(amountUsd, autoReload);
|
|
10451
|
-
|
|
10553
|
+
const noOpen = args.open === false;
|
|
10554
|
+
await run(() => args.wait ? billingTopupAndWait(makeCtx(), { amountUsd, autoReload, noOpen, onPending: (t) => out(t, args) }) : billingTopup(makeCtx(), { amountUsd, autoReload, noOpen }), args);
|
|
10555
|
+
}
|
|
10556
|
+
}),
|
|
10557
|
+
wait: defineCommand({
|
|
10558
|
+
meta: {
|
|
10559
|
+
name: "wait",
|
|
10560
|
+
description: "Wait until your human pays a top-up you showed them (default: the newest pending one). Returns status paid; exits topup_pending after --timeout-s (run it again)"
|
|
10561
|
+
},
|
|
10562
|
+
args: {
|
|
10563
|
+
...jsonFlag,
|
|
10564
|
+
topup_id: { type: "positional", required: false, description: "topup_id from `billing topup` (optional)" },
|
|
10565
|
+
"timeout-s": {
|
|
10566
|
+
type: "string",
|
|
10567
|
+
description: `Seconds to wait before exiting with topup_pending (default ${WAIT_DEFAULT_S}, at most 600)`
|
|
10568
|
+
}
|
|
10569
|
+
},
|
|
10570
|
+
run: async ({ args }) => {
|
|
10571
|
+
const t = args["timeout-s"] === undefined ? undefined : num("timeout-s", args["timeout-s"]);
|
|
10572
|
+
if (t !== undefined && t > 600)
|
|
10573
|
+
throw new CliError("bad_argument", `--timeout-s must be at most 600, got ${t}.`, "Run wait again instead.");
|
|
10574
|
+
await run(() => billingWait(makeCtx(), { topupId: args.topup_id || undefined, timeoutS: t }), args);
|
|
10452
10575
|
}
|
|
10453
10576
|
}),
|
|
10454
10577
|
"auto-reload": defineCommand({
|
|
@@ -10644,7 +10767,7 @@ var doctor2 = defineCommand({
|
|
|
10644
10767
|
var webhooks = defineCommand({
|
|
10645
10768
|
meta: {
|
|
10646
10769
|
name: "webhooks",
|
|
10647
|
-
description: "Signed HTTPS callbacks: balance.low,
|
|
10770
|
+
description: "Signed HTTPS callbacks: balance.low, key.expiring, budget.exhausted"
|
|
10648
10771
|
},
|
|
10649
10772
|
subCommands: {
|
|
10650
10773
|
add: defineCommand({
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scaledthought",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Headless open-model inference, run by your agent.
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Headless open-model inference, run by your agent. Prepaid, pay per token. US or Canada.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"llm",
|