scaledthought 0.1.0 → 0.2.2
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 +179 -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.
|
|
557
|
-
description: "Headless open-model inference, run by your agent.
|
|
556
|
+
version: "0.2.2",
|
|
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) and give your human its message_for_human. If it printed opened_in_browser: true, run `npx -y scaledthought@latest billing wait` in the same turn (again on topup_pending, up to 15 minutes): it returns as soon as they pay. Otherwise end your turn with the message and run billing wait once they're back. Then retry. Don't retry before that or create another account.";
|
|
634
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
|
|
|
@@ -8961,6 +8964,8 @@ var EXPERIMENT_METRICS = [
|
|
|
8961
8964
|
"signups",
|
|
8962
8965
|
"activation_rate",
|
|
8963
8966
|
"paid_rate",
|
|
8967
|
+
"topup_request_rate",
|
|
8968
|
+
"topup_completion_rate",
|
|
8964
8969
|
"cost_per_activated",
|
|
8965
8970
|
"cost_per_payer",
|
|
8966
8971
|
"onboarding_success_rate",
|
|
@@ -9220,7 +9225,7 @@ var GrowthResults = object({
|
|
|
9220
9225
|
decisions: array(GrowthDecision)
|
|
9221
9226
|
});
|
|
9222
9227
|
// ../../packages/schemas/src/legal.ts
|
|
9223
|
-
var TERMS_VERSION = "2026-10-
|
|
9228
|
+
var TERMS_VERSION = "2026-10-10";
|
|
9224
9229
|
var LEGAL_SITE = "https://scaledthought.com";
|
|
9225
9230
|
var LEGAL_PATHS = {
|
|
9226
9231
|
index: "/legal",
|
|
@@ -9238,23 +9243,29 @@ var REFUND_POLICY_URL = `${LEGAL_SITE}${LEGAL_PATHS.creditsAndRefunds}`;
|
|
|
9238
9243
|
var TERMS_NOTICE = `By creating this account you accept the ScaledThought Terms of Service (${TERMS_URL}) and Privacy Policy ` + `(${PRIVACY_URL}) on behalf of the person or organisation you act for, who is the customer.`;
|
|
9239
9244
|
|
|
9240
9245
|
// 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
9246
|
var NPX = "npx -y scaledthought@latest";
|
|
9249
|
-
|
|
9247
|
+
var FIRST_TOPUP_USD = 5;
|
|
9248
|
+
var TOPUP_COMMAND = `${NPX} billing topup --amount-usd ${FIRST_TOPUP_USD}`;
|
|
9249
|
+
var WAIT_COMMAND = `${NPX} billing wait`;
|
|
9250
|
+
var WAIT_PATIENCE_MIN = 15;
|
|
9251
|
+
var CREDIT_STEPS = [
|
|
9252
|
+
`Inference is prepaid and this account has no credits yet: completions return 402 insufficient_credits until your human pays. Do these first, in order:`,
|
|
9253
|
+
`1. Run: ${TOPUP_COMMAND}. It opens the checkout page in your human's browser and prints approval_url, topup_id and message_for_human. Creating it charges nothing: don't ask first.`,
|
|
9254
|
+
`2. If opened_in_browser is true, the checkout page (where your human accepts the Terms and pays $${FIRST_TOPUP_USD} by card) is already in front of them: tell them so with message_for_human and, in the same turn, run: ${WAIT_COMMAND}. It returns status "paid" with the new balance as soon as they pay; on topup_pending run it again, for up to ${WAIT_PATIENCE_MIN} minutes in all. Don't wait for them to say they've paid.`,
|
|
9255
|
+
`3. If opened_in_browser is false (or it's still unpaid after ${WAIT_PATIENCE_MIN} minutes), end your turn with message_for_human: it holds the approval_url exactly as printed. When they're back, run ${WAIT_COMMAND}. Either way, don't retry the call and don't create another account.`,
|
|
9256
|
+
"4. Then make the call below."
|
|
9257
|
+
];
|
|
9258
|
+
function nextSteps(model, needsCredits) {
|
|
9250
9259
|
return [
|
|
9260
|
+
...needsCredits ? CREDIT_STEPS : [],
|
|
9251
9261
|
model ? `Verify with one real completion: ${NPX} chat -m ${model} "Reply with OK" (or POST $ST_BASE_URL/chat/completions with that model)` : `No chat model is live in this region right now. Check again with: ${NPX} models --live`,
|
|
9252
9262
|
`Or let the router pick: ${NPX} chat -m auto "Reply with OK"`,
|
|
9253
9263
|
"Use it from code: any OpenAI SDK with base_url=$ST_BASE_URL and api_key=$ST_API_KEY (both are in .env)",
|
|
9254
9264
|
`Optional, connect MCP: ${NPX} mcp`,
|
|
9255
|
-
|
|
9265
|
+
...needsCredits ? [] : [`More credits when needed: ${TOPUP_COMMAND} returns an approval_url for your human (inference is prepaid)`]
|
|
9256
9266
|
];
|
|
9257
9267
|
}
|
|
9268
|
+
var balanceOf = (b) => b && typeof b.balance_usd === "number" ? b.balance_usd : null;
|
|
9258
9269
|
function termsOf(acct = {}) {
|
|
9259
9270
|
return {
|
|
9260
9271
|
url: acct.terms_url ?? TERMS_URL,
|
|
@@ -9263,10 +9274,18 @@ function termsOf(acct = {}) {
|
|
|
9263
9274
|
notice: acct.terms_notice ?? TERMS_NOTICE
|
|
9264
9275
|
};
|
|
9265
9276
|
}
|
|
9266
|
-
async function liveSummary(ctx, region) {
|
|
9277
|
+
async function liveSummary(ctx, region, balanceUsd) {
|
|
9267
9278
|
const live = await listModels(ctx, { live: true, region });
|
|
9268
9279
|
const suggested = live.find((m) => m.category !== "embedding" && m.modality === "chat")?.id ?? null;
|
|
9269
|
-
|
|
9280
|
+
const needsCredits = (balanceUsd ?? 0) <= 0;
|
|
9281
|
+
return {
|
|
9282
|
+
balance_usd: balanceUsd ?? 0,
|
|
9283
|
+
needs_credits: needsCredits,
|
|
9284
|
+
next_command: needsCredits ? TOPUP_COMMAND : `${NPX} chat -m ${suggested ?? "auto"} "Reply with OK"`,
|
|
9285
|
+
newest_models: live.slice(0, 5),
|
|
9286
|
+
suggested_model: suggested,
|
|
9287
|
+
next_steps: nextSteps(suggested, needsCredits)
|
|
9288
|
+
};
|
|
9270
9289
|
}
|
|
9271
9290
|
function signupAttribution(region, opts) {
|
|
9272
9291
|
const warnings = [];
|
|
@@ -9302,13 +9321,13 @@ async function init(ctx, opts) {
|
|
|
9302
9321
|
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
9322
|
const status = await account(ctx);
|
|
9304
9323
|
const ignored = [opts.source !== undefined && "--source", opts.adClick !== undefined && "--ad-click"].filter(Boolean);
|
|
9324
|
+
const shown = withoutFreeFields(status);
|
|
9305
9325
|
return {
|
|
9306
9326
|
already_initialized: true,
|
|
9307
|
-
...
|
|
9327
|
+
...shown,
|
|
9308
9328
|
key_saved_to: where,
|
|
9309
|
-
free_tier: FREE_TIER,
|
|
9310
9329
|
terms: termsOf(),
|
|
9311
|
-
...await liveSummary(ctx, region),
|
|
9330
|
+
...await liveSummary(ctx, region, balanceOf(status)),
|
|
9312
9331
|
...ignored.length ? { warnings: [`${ignored.join(" and ")} apply only to a new account and were not sent.`] } : {}
|
|
9313
9332
|
};
|
|
9314
9333
|
}
|
|
@@ -9328,17 +9347,16 @@ async function init(ctx, opts) {
|
|
|
9328
9347
|
writeEnv(ctx.cwd, { ST_API_KEY: acct.api_key, ST_BASE_URL: acct.base_url });
|
|
9329
9348
|
ensureGitignored(ctx.cwd);
|
|
9330
9349
|
const keyed = { ...ctx, env: { ...ctx.env, ST_API_KEY: acct.api_key } };
|
|
9350
|
+
const billing = await api(keyed, "GET", "/v1/billing").catch(() => null);
|
|
9331
9351
|
return {
|
|
9332
9352
|
already_initialized: false,
|
|
9333
9353
|
account_id: acct.account_id,
|
|
9334
9354
|
region: acct.region,
|
|
9335
|
-
plan: "free",
|
|
9336
9355
|
base_url: acct.base_url,
|
|
9337
9356
|
key_saved_to: ".env (ST_API_KEY, ST_BASE_URL)",
|
|
9338
|
-
free_tier: FREE_TIER,
|
|
9339
9357
|
terms: termsOf(acct),
|
|
9340
9358
|
...attribution.source ? { source: acct.source ?? attribution.source } : {},
|
|
9341
|
-
...await liveSummary(keyed, acct.region),
|
|
9359
|
+
...await liveSummary(keyed, acct.region, balanceOf(billing)),
|
|
9342
9360
|
...attribution.warnings.length ? { warnings: attribution.warnings } : {}
|
|
9343
9361
|
};
|
|
9344
9362
|
}
|
|
@@ -9409,9 +9427,20 @@ function unitPrices(p) {
|
|
|
9409
9427
|
};
|
|
9410
9428
|
}
|
|
9411
9429
|
var showModel = (ctx, id, region) => api(ctx, "GET", `/v1/models/${encodeURIComponent(id)}`, { auth: false, region: publicRegion(ctx, region) });
|
|
9430
|
+
function withoutFreeFields(o) {
|
|
9431
|
+
const out = {};
|
|
9432
|
+
for (const [k, v] of Object.entries(o)) {
|
|
9433
|
+
if (/^free_/.test(k))
|
|
9434
|
+
continue;
|
|
9435
|
+
if (k === "topup" && typeof v === "string" && /free/i.test(v))
|
|
9436
|
+
continue;
|
|
9437
|
+
out[k] = v;
|
|
9438
|
+
}
|
|
9439
|
+
return out;
|
|
9440
|
+
}
|
|
9412
9441
|
async function account(ctx) {
|
|
9413
|
-
const a = await api(ctx, "GET", "/v1/account");
|
|
9414
|
-
const fields = ["balance_usd", "
|
|
9442
|
+
const a = withoutFreeFields(await api(ctx, "GET", "/v1/account"));
|
|
9443
|
+
const fields = ["balance_usd", "auto_reload"];
|
|
9415
9444
|
if (fields.every((f) => (f in a)))
|
|
9416
9445
|
return a;
|
|
9417
9446
|
const b = await api(ctx, "GET", "/v1/billing").catch(() => null);
|
|
@@ -9512,7 +9541,7 @@ async function chat(ctx, o) {
|
|
|
9512
9541
|
usage: usageOut
|
|
9513
9542
|
};
|
|
9514
9543
|
}
|
|
9515
|
-
var billingStatus = (ctx) => api(ctx, "GET", "/v1/billing");
|
|
9544
|
+
var billingStatus = async (ctx) => withoutFreeFields(await api(ctx, "GET", "/v1/billing"));
|
|
9516
9545
|
var TOPUP_LIMITS = { min_usd: 5, max_usd: 2000, daily_max_usd: 2000, auto_reload_monthly_max_usd: 1e4 };
|
|
9517
9546
|
function checkTopupLimits(amountUsd, autoReload) {
|
|
9518
9547
|
const { min_usd, max_usd, daily_max_usd, auto_reload_monthly_max_usd } = TOPUP_LIMITS;
|
|
@@ -9529,20 +9558,94 @@ function checkTopupLimits(amountUsd, autoReload) {
|
|
|
9529
9558
|
range("auto-reload-monthly-max", autoReload.monthly_limit_usd, autoReload.amount_usd, auto_reload_monthly_max_usd);
|
|
9530
9559
|
}
|
|
9531
9560
|
var TOPUP_DONE = new Set(["paid", "expired", "failed", "canceled"]);
|
|
9532
|
-
function topupNextSteps(amountUsd, autoReload) {
|
|
9561
|
+
function topupNextSteps(topupId, amountUsd, opened, autoReload) {
|
|
9562
|
+
const wait = `${WAIT_COMMAND}${topupId ? ` ${topupId}` : ""}`;
|
|
9533
9563
|
return [
|
|
9534
|
-
`
|
|
9564
|
+
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.`,
|
|
9565
|
+
`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.`,
|
|
9535
9566
|
...autoReload ? [
|
|
9536
9567
|
`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
9568
|
] : [],
|
|
9538
|
-
|
|
9569
|
+
...opened ? [] : [
|
|
9570
|
+
`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).`
|
|
9571
|
+
],
|
|
9572
|
+
"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
9573
|
];
|
|
9540
9574
|
}
|
|
9575
|
+
function openInBrowser(url) {
|
|
9576
|
+
if (!/^https:\/\//.test(url))
|
|
9577
|
+
return Promise.resolve(false);
|
|
9578
|
+
const [bin, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", '""', url.replace(/&/g, "^&")]] : ["xdg-open", [url]];
|
|
9579
|
+
return new Promise((done) => {
|
|
9580
|
+
try {
|
|
9581
|
+
const child = spawn(bin, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
9582
|
+
child.once("error", () => done(false));
|
|
9583
|
+
child.once("spawn", () => {
|
|
9584
|
+
child.unref();
|
|
9585
|
+
done(true);
|
|
9586
|
+
});
|
|
9587
|
+
} catch {
|
|
9588
|
+
done(false);
|
|
9589
|
+
}
|
|
9590
|
+
});
|
|
9591
|
+
}
|
|
9541
9592
|
async function billingTopup(ctx, o) {
|
|
9542
9593
|
const created = await api(ctx, "POST", "/v1/billing/topup", {
|
|
9543
9594
|
body: { amount_usd: o.amountUsd, ...o.autoReload ? { auto_reload: o.autoReload } : {} }
|
|
9544
9595
|
});
|
|
9545
|
-
|
|
9596
|
+
const noOpen = o.noOpen || ctx.env.ST_NO_BROWSER === "1";
|
|
9597
|
+
const opened = !noOpen && !!created.approval_url && await (o.open ?? openInBrowser)(created.approval_url);
|
|
9598
|
+
return {
|
|
9599
|
+
status: "pending",
|
|
9600
|
+
...created,
|
|
9601
|
+
opened_in_browser: opened,
|
|
9602
|
+
message_for_human: messageForHuman(created.approval_url, o.amountUsd, o.autoReload, opened),
|
|
9603
|
+
next_steps: topupNextSteps(created.topup_id, o.amountUsd, opened, o.autoReload)
|
|
9604
|
+
};
|
|
9605
|
+
}
|
|
9606
|
+
function messageForHuman(approvalUrl, amountUsd, autoReload, opened = false) {
|
|
9607
|
+
if (!approvalUrl)
|
|
9608
|
+
return null;
|
|
9609
|
+
const reload = autoReload ? ` It also turns on auto-reload: $${autoReload.amount_usd} whenever the balance drops below $${autoReload.threshold_usd}${autoReload.monthly_limit_usd ? `, at most $${autoReload.monthly_limit_usd} a month` : ""}.` : "";
|
|
9610
|
+
if (opened)
|
|
9611
|
+
return `To finish the setup, I've opened the ScaledThought checkout page in your browser to buy $${amountUsd} of prepaid credits (USD). If it didn't open, use this link:
|
|
9612
|
+
|
|
9613
|
+
${approvalUrl}
|
|
9614
|
+
|
|
9615
|
+
On that page you accept the Terms of Service and the Credits & Refunds policy and pay by card. Nothing is charged until you approve.${reload} I'm watching for the payment and will carry on as soon as it goes through.`;
|
|
9616
|
+
return `To finish the setup, please buy $${amountUsd} of prepaid ScaledThought credits (USD) here:
|
|
9617
|
+
|
|
9618
|
+
${approvalUrl}
|
|
9619
|
+
|
|
9620
|
+
On that page you accept the Terms of Service and the Credits & Refunds policy and pay by card. Nothing is charged until you approve.${reload} Tell me when it's done and I'll continue.`;
|
|
9621
|
+
}
|
|
9622
|
+
var WAIT_DEFAULT_S = 90;
|
|
9623
|
+
async function billingWait(ctx, o = {}) {
|
|
9624
|
+
let id = o.topupId;
|
|
9625
|
+
if (id !== undefined && !/^top_[0-9A-Za-z]+$/.test(id))
|
|
9626
|
+
throw new CliError("bad_argument", `"${id}" is not a top-up id (top_...).`, "Use topup_id from `billing topup`.");
|
|
9627
|
+
if (!id) {
|
|
9628
|
+
const b = await api(ctx, "GET", "/v1/billing");
|
|
9629
|
+
const pending = (b.pending_topups ?? []).filter((t) => t.status === "pending" && t.approval_url);
|
|
9630
|
+
id = pending[0]?.topup_id;
|
|
9631
|
+
if (!id) {
|
|
9632
|
+
const paid = (b.recent_transactions ?? []).find((t) => t.topup_id)?.topup_id ?? null;
|
|
9633
|
+
if ((b.balance_usd ?? 0) > 0)
|
|
9634
|
+
return { status: "paid", topup_id: paid, billing: withoutFreeFields(b) };
|
|
9635
|
+
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}\`.`);
|
|
9636
|
+
}
|
|
9637
|
+
}
|
|
9638
|
+
const deadline = Date.now() + (o.timeoutS ?? WAIT_DEFAULT_S) * 1000;
|
|
9639
|
+
for (;; ) {
|
|
9640
|
+
const t = await api(ctx, "GET", `/v1/billing/topups/${encodeURIComponent(id)}`);
|
|
9641
|
+
if (t.status === "paid")
|
|
9642
|
+
return { status: "paid", topup_id: id, billing: await billingStatus(ctx) };
|
|
9643
|
+
if (TOPUP_DONE.has(t.status ?? ""))
|
|
9644
|
+
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 });
|
|
9645
|
+
if (Date.now() >= deadline)
|
|
9646
|
+
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 });
|
|
9647
|
+
await new Promise((r) => setTimeout(r, o.pollMs ?? 3000));
|
|
9648
|
+
}
|
|
9546
9649
|
}
|
|
9547
9650
|
async function billingTopupAndWait(ctx, o) {
|
|
9548
9651
|
const created = await billingTopup(ctx, o);
|
|
@@ -9734,13 +9837,10 @@ async function doctor(ctx) {
|
|
|
9734
9837
|
next: err.hint ?? "Fix the error above, then run doctor again."
|
|
9735
9838
|
};
|
|
9736
9839
|
}
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
ok: credit,
|
|
9742
|
-
detail: `balance $${a.balance_usd ?? 0}, free left today $${a.free_remaining_today_usd ?? 0}`
|
|
9743
|
-
});
|
|
9840
|
+
const purchased = a.plan === "paid" ? "credits bought" : "no credit purchase yet";
|
|
9841
|
+
checks.push({ check: "account", ok: a.status === "active", detail: `${a.account_id} (${purchased}, ${a.status})` });
|
|
9842
|
+
const credit = (a.balance_usd ?? 0) > 0;
|
|
9843
|
+
checks.push({ check: "credit", ok: credit, detail: `balance $${a.balance_usd ?? 0}` });
|
|
9744
9844
|
const notices = a.notices ?? [];
|
|
9745
9845
|
return {
|
|
9746
9846
|
ok: checks.every((c) => c.ok) && notices.length === 0,
|
|
@@ -9750,10 +9850,10 @@ async function doctor(ctx) {
|
|
|
9750
9850
|
checks,
|
|
9751
9851
|
notices,
|
|
9752
9852
|
notice_text: a.notice_text ?? [],
|
|
9753
|
-
next: notices.length ? "Tell your human about each notice (notice_text)." : credit ? null :
|
|
9853
|
+
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
9854
|
};
|
|
9755
9855
|
}
|
|
9756
|
-
var WEBHOOK_EVENTS = ["balance.low", "
|
|
9856
|
+
var WEBHOOK_EVENTS = ["balance.low", "key.expiring", "budget.exhausted"];
|
|
9757
9857
|
async function webhooksAdd(ctx, o) {
|
|
9758
9858
|
if (!/^https:\/\//i.test(o.url))
|
|
9759
9859
|
throw new CliError("bad_argument", "The webhook URL must be https://.", "Pass a public https endpoint.");
|
|
@@ -10108,7 +10208,7 @@ function num(flag, v) {
|
|
|
10108
10208
|
var init2 = defineCommand({
|
|
10109
10209
|
meta: {
|
|
10110
10210
|
name: "init",
|
|
10111
|
-
description: "Create
|
|
10211
|
+
description: "Create an account, save ST_API_KEY and ST_BASE_URL to ./.env, list live models and the next steps (a new account has no credits: top up first). Idempotent. " + "Creating an account accepts the Terms of Service (scaledthought.com/legal/terms) for the human you act for."
|
|
10112
10212
|
},
|
|
10113
10213
|
args: {
|
|
10114
10214
|
...jsonFlag,
|
|
@@ -10163,7 +10263,7 @@ var models = defineCommand({
|
|
|
10163
10263
|
});
|
|
10164
10264
|
var ACCOUNT_SUBCOMMANDS = new Set(["delete", "ad-measurement"]);
|
|
10165
10265
|
var account2 = defineCommand({
|
|
10166
|
-
meta: { name: "account", description: "Region, spend, credit balance and
|
|
10266
|
+
meta: { name: "account", description: "Region, spend, credit balance and auto-reload" },
|
|
10167
10267
|
args: jsonFlag,
|
|
10168
10268
|
run: ({ args, rawArgs }) => {
|
|
10169
10269
|
if (rawArgs.some((a) => ACCOUNT_SUBCOMMANDS.has(a)))
|
|
@@ -10419,7 +10519,7 @@ var billing = defineCommand({
|
|
|
10419
10519
|
status: defineCommand({
|
|
10420
10520
|
meta: {
|
|
10421
10521
|
name: "status",
|
|
10422
|
-
description: "
|
|
10522
|
+
description: "Credit balance, auto-reload, pending top-ups (with their approval links), recent transactions"
|
|
10423
10523
|
},
|
|
10424
10524
|
args: jsonFlag,
|
|
10425
10525
|
run: ({ args }) => run(() => billingStatus(makeCtx()), args)
|
|
@@ -10427,7 +10527,7 @@ var billing = defineCommand({
|
|
|
10427
10527
|
topup: defineCommand({
|
|
10428
10528
|
meta: {
|
|
10429
10529
|
name: "topup",
|
|
10430
|
-
description: "Buy prepaid credits:
|
|
10530
|
+
description: "Buy prepaid credits: opens the checkout page in your human's browser (approval_url); then run `billing wait`, which returns once they pay. Optional auto-reload"
|
|
10431
10531
|
},
|
|
10432
10532
|
args: {
|
|
10433
10533
|
...jsonFlag,
|
|
@@ -10442,13 +10542,42 @@ var billing = defineCommand({
|
|
|
10442
10542
|
type: "string",
|
|
10443
10543
|
description: "Most auto-reload may charge per calendar month (USD, default 5x the amount)"
|
|
10444
10544
|
},
|
|
10445
|
-
|
|
10545
|
+
open: {
|
|
10546
|
+
type: "boolean",
|
|
10547
|
+
default: true,
|
|
10548
|
+
description: "Open the checkout page in the browser (--no-open only prints approval_url)"
|
|
10549
|
+
},
|
|
10550
|
+
wait: {
|
|
10551
|
+
type: "boolean",
|
|
10552
|
+
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`"
|
|
10553
|
+
}
|
|
10446
10554
|
},
|
|
10447
10555
|
run: async ({ args }) => {
|
|
10448
10556
|
const amountUsd = num("amount-usd", args["amount-usd"]);
|
|
10449
10557
|
const autoReload = autoReloadArgs(args);
|
|
10450
10558
|
checkTopupLimits(amountUsd, autoReload);
|
|
10451
|
-
|
|
10559
|
+
const noOpen = args.open === false;
|
|
10560
|
+
await run(() => args.wait ? billingTopupAndWait(makeCtx(), { amountUsd, autoReload, noOpen, onPending: (t) => out(t, args) }) : billingTopup(makeCtx(), { amountUsd, autoReload, noOpen }), args);
|
|
10561
|
+
}
|
|
10562
|
+
}),
|
|
10563
|
+
wait: defineCommand({
|
|
10564
|
+
meta: {
|
|
10565
|
+
name: "wait",
|
|
10566
|
+
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)"
|
|
10567
|
+
},
|
|
10568
|
+
args: {
|
|
10569
|
+
...jsonFlag,
|
|
10570
|
+
topup_id: { type: "positional", required: false, description: "topup_id from `billing topup` (optional)" },
|
|
10571
|
+
"timeout-s": {
|
|
10572
|
+
type: "string",
|
|
10573
|
+
description: `Seconds to wait before exiting with topup_pending (default ${WAIT_DEFAULT_S}, at most 600)`
|
|
10574
|
+
}
|
|
10575
|
+
},
|
|
10576
|
+
run: async ({ args }) => {
|
|
10577
|
+
const t = args["timeout-s"] === undefined ? undefined : num("timeout-s", args["timeout-s"]);
|
|
10578
|
+
if (t !== undefined && t > 600)
|
|
10579
|
+
throw new CliError("bad_argument", `--timeout-s must be at most 600, got ${t}.`, "Run wait again instead.");
|
|
10580
|
+
await run(() => billingWait(makeCtx(), { topupId: args.topup_id || undefined, timeoutS: t }), args);
|
|
10452
10581
|
}
|
|
10453
10582
|
}),
|
|
10454
10583
|
"auto-reload": defineCommand({
|
|
@@ -10644,7 +10773,7 @@ var doctor2 = defineCommand({
|
|
|
10644
10773
|
var webhooks = defineCommand({
|
|
10645
10774
|
meta: {
|
|
10646
10775
|
name: "webhooks",
|
|
10647
|
-
description: "Signed HTTPS callbacks: balance.low,
|
|
10776
|
+
description: "Signed HTTPS callbacks: balance.low, key.expiring, budget.exhausted"
|
|
10648
10777
|
},
|
|
10649
10778
|
subCommands: {
|
|
10650
10779
|
add: defineCommand({
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scaledthought",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Headless open-model inference, run by your agent.
|
|
3
|
+
"version": "0.2.2",
|
|
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",
|