scaledthought 0.2.2 → 0.3.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 +16 -8
- package/dist/index.js +1963 -297
- package/package.json +1 -1
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.1",
|
|
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
|
-
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
|
|
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. Need more than $2,000 a day? Invoiced prepayment: sales@scaledthought.com",
|
|
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) {
|
|
@@ -7887,6 +7898,179 @@ var AD_WRITE_CREDENTIAL_ENV = {
|
|
|
7887
7898
|
microsoft: "AD_WRITE_MICROSOFT_CREDENTIALS"
|
|
7888
7899
|
};
|
|
7889
7900
|
var AD_WRITE_CREDENTIAL_ENV_NAMES = Object.values(AD_WRITE_CREDENTIAL_ENV);
|
|
7901
|
+
// ../../packages/growth/src/portfolio.ts
|
|
7902
|
+
var PORTFOLIO_LIMITS = {
|
|
7903
|
+
weeklyBudgetMicros: 1e9,
|
|
7904
|
+
founders: ["alexey-scaled-throught", "josh-scaledthought"],
|
|
7905
|
+
effortDayMicros: 50000000
|
|
7906
|
+
};
|
|
7907
|
+
var FUNNEL_STAGES = ["click", "fetch", "signup", "activated", "paid"];
|
|
7908
|
+
var EXPERIMENT_KINDS = ["paid", "owned", "onboarding", "lifecycle", "community", "research"];
|
|
7909
|
+
var AGENT_ROLES = [
|
|
7910
|
+
"growth-lead",
|
|
7911
|
+
"analyst",
|
|
7912
|
+
"onboarding",
|
|
7913
|
+
"seo",
|
|
7914
|
+
"paid-ads",
|
|
7915
|
+
"landing",
|
|
7916
|
+
"lifecycle",
|
|
7917
|
+
"content",
|
|
7918
|
+
"community",
|
|
7919
|
+
"research",
|
|
7920
|
+
"integrations",
|
|
7921
|
+
"canada"
|
|
7922
|
+
];
|
|
7923
|
+
var EXPERIMENT_STATUSES = ["proposed", "approved", "running", "read", "killed", "scaled"];
|
|
7924
|
+
var EXPERIMENT_METRICS = [
|
|
7925
|
+
"ctr",
|
|
7926
|
+
"fetch_rate",
|
|
7927
|
+
"agent_fetches",
|
|
7928
|
+
"human_visits",
|
|
7929
|
+
"signups",
|
|
7930
|
+
"activation_rate",
|
|
7931
|
+
"paid_rate",
|
|
7932
|
+
"topup_request_rate",
|
|
7933
|
+
"topup_completion_rate",
|
|
7934
|
+
"cost_per_activated",
|
|
7935
|
+
"cost_per_payer",
|
|
7936
|
+
"onboarding_success_rate",
|
|
7937
|
+
"time_to_first_call",
|
|
7938
|
+
"npm_downloads",
|
|
7939
|
+
"github_stars",
|
|
7940
|
+
"registry_installs"
|
|
7941
|
+
];
|
|
7942
|
+
var SURFACE_TYPES = [
|
|
7943
|
+
"page",
|
|
7944
|
+
"copy",
|
|
7945
|
+
"code",
|
|
7946
|
+
"listing",
|
|
7947
|
+
"cli",
|
|
7948
|
+
"notice",
|
|
7949
|
+
"ad",
|
|
7950
|
+
"post",
|
|
7951
|
+
"email",
|
|
7952
|
+
"external",
|
|
7953
|
+
"price"
|
|
7954
|
+
];
|
|
7955
|
+
var TIER2_SURFACES = new Set(["ad", "post", "email", "price", "listing", "external"]);
|
|
7956
|
+
var SURFACE_RE = new RegExp(`^(${SURFACE_TYPES.join("|")}):[^\\s*]+\\*?$`);
|
|
7957
|
+
var EXPERIMENT_ID_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
7958
|
+
var CAMPAIGN_ID_RE = /^gcm_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
7959
|
+
var Day = string2().refine(isDay, "a UTC day, YYYY-MM-DD");
|
|
7960
|
+
var Bps = number2().int().min(0).max(1e4);
|
|
7961
|
+
var Micros = number2().int().min(0);
|
|
7962
|
+
var DecisionRule = discriminatedUnion("kind", [
|
|
7963
|
+
object({ kind: literal("p_better"), threshold: number2().min(0.5).max(0.999) }),
|
|
7964
|
+
object({ kind: literal("rate_at_least"), bps: Bps }),
|
|
7965
|
+
object({ kind: literal("cost_at_most"), micros: Micros })
|
|
7966
|
+
]);
|
|
7967
|
+
var ExperimentSpec = object({
|
|
7968
|
+
id: string2().max(64).regex(EXPERIMENT_ID_RE, "kebab-case, the file name without .yaml"),
|
|
7969
|
+
title: string2().min(5).max(120),
|
|
7970
|
+
hypothesis: string2().min(20).max(600),
|
|
7971
|
+
owner: _enum(AGENT_ROLES),
|
|
7972
|
+
kind: _enum(EXPERIMENT_KINDS),
|
|
7973
|
+
stage: _enum(FUNNEL_STAGES),
|
|
7974
|
+
tier: union([literal(0), literal(1), literal(2)]),
|
|
7975
|
+
status: _enum(EXPERIMENT_STATUSES),
|
|
7976
|
+
created: Day,
|
|
7977
|
+
surfaces: array(string2().regex(SURFACE_RE, "<type>:<target>, e.g. page:/docs/cursor")).min(1),
|
|
7978
|
+
codes: array(CodeSchema).default([]),
|
|
7979
|
+
metric: object({ primary: _enum(EXPERIMENT_METRICS), guardrail: _enum(EXPERIMENT_METRICS).optional() }),
|
|
7980
|
+
design: object({
|
|
7981
|
+
type: _enum(["ab", "single", "discovery", "synthetic"]),
|
|
7982
|
+
variants: array(string2().min(1).max(40)).optional(),
|
|
7983
|
+
arms: record(string2().min(1).max(40), array(CodeSchema).min(1)).optional()
|
|
7984
|
+
}),
|
|
7985
|
+
run: object({ from: Day, to: Day }).optional(),
|
|
7986
|
+
decision: object({ read_on: Day, min_n: number2().int().min(1), rule: DecisionRule }),
|
|
7987
|
+
budget: object({ weekly_micros: Micros }).default({ weekly_micros: 0 }),
|
|
7988
|
+
estimate: object({
|
|
7989
|
+
p_win_bps: Bps,
|
|
7990
|
+
lift_bps: number2().int().min(1).max(1e5),
|
|
7991
|
+
baseline_per_week: number2().min(0),
|
|
7992
|
+
effort_days: number2().min(0).max(30)
|
|
7993
|
+
}),
|
|
7994
|
+
approval: object({ by: string2().optional(), on: Day.optional(), note: string2().min(3).max(300).optional() }).strict().optional(),
|
|
7995
|
+
campaigns: array(string2().regex(CAMPAIGN_ID_RE, "a console campaign id, gcm_<ulid>")).max(10).default([]),
|
|
7996
|
+
result: object({
|
|
7997
|
+
read: Day,
|
|
7998
|
+
outcome: _enum(["win", "loss", "inconclusive", "stopped"]),
|
|
7999
|
+
summary: string2().min(10).max(1000)
|
|
8000
|
+
}).optional()
|
|
8001
|
+
}).strict();
|
|
8002
|
+
|
|
8003
|
+
// ../../packages/growth/src/campaigns.ts
|
|
8004
|
+
var KEYWORD_MATCH_TYPES = ["exact", "phrase", "broad"];
|
|
8005
|
+
var GOOGLE_AD_LIMITS = {
|
|
8006
|
+
headline: 30,
|
|
8007
|
+
description: 90,
|
|
8008
|
+
path: 15,
|
|
8009
|
+
headlinesMin: 3,
|
|
8010
|
+
headlinesMax: 15,
|
|
8011
|
+
descriptionsMin: 2,
|
|
8012
|
+
descriptionsMax: 4,
|
|
8013
|
+
keyword: 80,
|
|
8014
|
+
keywordWords: 10,
|
|
8015
|
+
adGroups: 20,
|
|
8016
|
+
keywordsPerAdGroup: 200,
|
|
8017
|
+
adsPerAdGroup: 3,
|
|
8018
|
+
negativeKeywords: 500
|
|
8019
|
+
};
|
|
8020
|
+
var CampaignId = string2().regex(CAMPAIGN_ID_RE, "a campaign id, gcm_<ulid>");
|
|
8021
|
+
var Line = (max, what) => string2().trim().min(1).refine((s) => [...s].length <= max, `${what}: at most ${max} characters`);
|
|
8022
|
+
var Keyword = object({
|
|
8023
|
+
text: Line(GOOGLE_AD_LIMITS.keyword, "keyword").refine((s) => s.split(/\s+/).length <= GOOGLE_AD_LIMITS.keywordWords, `keyword: at most ${GOOGLE_AD_LIMITS.keywordWords} words`),
|
|
8024
|
+
match: _enum(KEYWORD_MATCH_TYPES)
|
|
8025
|
+
}).strict();
|
|
8026
|
+
var SearchAd = object({
|
|
8027
|
+
headlines: array(Line(GOOGLE_AD_LIMITS.headline, "headline")).min(GOOGLE_AD_LIMITS.headlinesMin).max(GOOGLE_AD_LIMITS.headlinesMax),
|
|
8028
|
+
descriptions: array(Line(GOOGLE_AD_LIMITS.description, "description")).min(GOOGLE_AD_LIMITS.descriptionsMin).max(GOOGLE_AD_LIMITS.descriptionsMax),
|
|
8029
|
+
path1: Line(GOOGLE_AD_LIMITS.path, "path").optional(),
|
|
8030
|
+
path2: Line(GOOGLE_AD_LIMITS.path, "path").optional()
|
|
8031
|
+
}).strict().refine((a) => !a.path2 || a.path1, "path2 needs path1");
|
|
8032
|
+
var AdGroup2 = object({
|
|
8033
|
+
name: string2().trim().min(1).max(255),
|
|
8034
|
+
keywords: array(Keyword).min(1).max(GOOGLE_AD_LIMITS.keywordsPerAdGroup),
|
|
8035
|
+
ads: array(SearchAd).min(1).max(GOOGLE_AD_LIMITS.adsPerAdGroup),
|
|
8036
|
+
negative_keywords: array(Keyword).max(GOOGLE_AD_LIMITS.negativeKeywords).default([])
|
|
8037
|
+
}).strict();
|
|
8038
|
+
var CampaignContent = object({
|
|
8039
|
+
ad_groups: array(AdGroup2).min(1).max(GOOGLE_AD_LIMITS.adGroups),
|
|
8040
|
+
negative_keywords: array(Keyword).max(GOOGLE_AD_LIMITS.negativeKeywords).default([])
|
|
8041
|
+
}).strict();
|
|
8042
|
+
var CAMPAIGN_NETWORKS = ["search", "search_partners", "display"];
|
|
8043
|
+
var CampaignTargeting = object({
|
|
8044
|
+
locations: array(string2().trim().min(2).max(80)).min(1).max(50),
|
|
8045
|
+
location_mode: _enum(["presence", "presence_or_interest"]).default("presence"),
|
|
8046
|
+
languages: array(string2().trim().min(2).max(10)).min(1).max(20),
|
|
8047
|
+
networks: array(_enum(CAMPAIGN_NETWORKS)).min(1).max(3).default(["search"])
|
|
8048
|
+
}).strict();
|
|
8049
|
+
var Day2 = string2().refine(isDay, "a UTC day, YYYY-MM-DD");
|
|
8050
|
+
var Budget = number2().int().positive().max(PORTFOLIO_LIMITS.weeklyBudgetMicros);
|
|
8051
|
+
var CAMPAIGN_FIELD_SHAPE = {
|
|
8052
|
+
name: string2().trim().min(3).max(120),
|
|
8053
|
+
code: CodeSchema,
|
|
8054
|
+
experiment_id: string2().max(64).regex(EXPERIMENT_ID_RE).nullable(),
|
|
8055
|
+
ad_account_id: string2().trim().min(1).max(64).nullable(),
|
|
8056
|
+
final_url: string2().trim().max(200),
|
|
8057
|
+
daily_budget_micros: Budget.nullable(),
|
|
8058
|
+
weekly_budget_micros: Budget.nullable(),
|
|
8059
|
+
run_from: Day2,
|
|
8060
|
+
run_to: Day2,
|
|
8061
|
+
targeting: CampaignTargeting,
|
|
8062
|
+
content: CampaignContent,
|
|
8063
|
+
notes: string2().trim().max(4000).nullable()
|
|
8064
|
+
};
|
|
8065
|
+
var CampaignFields = object({
|
|
8066
|
+
...CAMPAIGN_FIELD_SHAPE,
|
|
8067
|
+
experiment_id: CAMPAIGN_FIELD_SHAPE.experiment_id.default(null),
|
|
8068
|
+
ad_account_id: CAMPAIGN_FIELD_SHAPE.ad_account_id.default(null),
|
|
8069
|
+
daily_budget_micros: CAMPAIGN_FIELD_SHAPE.daily_budget_micros.default(null),
|
|
8070
|
+
weekly_budget_micros: CAMPAIGN_FIELD_SHAPE.weekly_budget_micros.default(null),
|
|
8071
|
+
notes: CAMPAIGN_FIELD_SHAPE.notes.default(null)
|
|
8072
|
+
}).strict();
|
|
8073
|
+
var CampaignFieldsPatch = object(CAMPAIGN_FIELD_SHAPE).partial().strict();
|
|
7890
8074
|
// ../../packages/growth/src/sinks/google.ts
|
|
7891
8075
|
var GOOGLE_DATA_MANAGER_URL = "https://datamanager.googleapis.com/v1/events:ingest";
|
|
7892
8076
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
@@ -8197,35 +8381,9 @@ var PERMANENT_FIRST_PURCHASE_SKIPS = new Set([
|
|
|
8197
8381
|
"account_not_active"
|
|
8198
8382
|
]);
|
|
8199
8383
|
// ../../packages/growth/src/experiments.ts
|
|
8200
|
-
var EXPERIMENTS = {
|
|
8201
|
-
"first-paying-customers": {
|
|
8202
|
-
title: "First paying customers",
|
|
8203
|
-
hypothesis: "Developers who use coding agents will pay for open models when their agent sets everything up from one pasted prompt.",
|
|
8204
|
-
spend: { from: "2026-10-06", to: "2026-10-19" },
|
|
8205
|
-
tailDays: 7,
|
|
8206
|
-
payWindowDays: 14,
|
|
8207
|
-
activationDays: 7,
|
|
8208
|
-
passCostMicros: 20000000,
|
|
8209
|
-
stopCostMicros: 40000000,
|
|
8210
|
-
minPaidRateBps: 500,
|
|
8211
|
-
lowPaidRateBps: 200,
|
|
8212
|
-
minPayers: 12,
|
|
8213
|
-
verdictMinSpendMicros: 300000000,
|
|
8214
|
-
verdictMinActivations: 15,
|
|
8215
|
-
pauseCheckDay: 10,
|
|
8216
|
-
readMinMatured: 30,
|
|
8217
|
-
excludedAccountIds: [],
|
|
8218
|
-
messages: { A: "Agent", B: "Free", C: "Private" },
|
|
8219
|
-
platforms: ["reddit", "google", "x", "ethicalads"],
|
|
8220
|
-
plan: {
|
|
8221
|
-
fetchPerClickBps: 2000,
|
|
8222
|
-
activatedPerFetchBps: 7500,
|
|
8223
|
-
paidPerActivatedBps: 500,
|
|
8224
|
-
costPerPayerMicros: 333000000
|
|
8225
|
-
}
|
|
8226
|
-
}
|
|
8227
|
-
};
|
|
8384
|
+
var EXPERIMENTS = {};
|
|
8228
8385
|
var EXPERIMENT_KEYS = Object.keys(EXPERIMENTS);
|
|
8386
|
+
var DEFAULT_EXPERIMENT_KEY = EXPERIMENT_KEYS[0] ?? null;
|
|
8229
8387
|
var EXPERIMENT_PHASES = ["before", "spending", "reading_payment", "payment_read", "tail_read"];
|
|
8230
8388
|
// ../../packages/growth/src/go-hits.ts
|
|
8231
8389
|
var GO_HIT_KINDS = ["human", "agent"];
|
|
@@ -8286,13 +8444,13 @@ var EthicalAdsCredentials = object({
|
|
|
8286
8444
|
var credentials3 = (creds) => parseCredentialsJson(creds[ENV7], EthicalAdsCredentials);
|
|
8287
8445
|
var ethicalAdsApiAccount = (creds) => credentials3(creds).advertiser;
|
|
8288
8446
|
var Num3 = union([number2(), string2()]).nullable().optional();
|
|
8289
|
-
var
|
|
8447
|
+
var Day3 = object({ date: string2().optional(), index: string2().optional(), views: Num3, clicks: Num3, cost: Num3 });
|
|
8290
8448
|
var Report = object({
|
|
8291
|
-
days: array(
|
|
8449
|
+
days: array(Day3).default([]),
|
|
8292
8450
|
flights: array(object({
|
|
8293
8451
|
name: string2().nullable().optional(),
|
|
8294
8452
|
slug: string2(),
|
|
8295
|
-
report: object({ days: array(
|
|
8453
|
+
report: object({ days: array(Day3).default([]) }).nullable().optional()
|
|
8296
8454
|
})).default([])
|
|
8297
8455
|
});
|
|
8298
8456
|
var dayOf2 = (d) => (d.date ?? d.index ?? "").slice(0, 10);
|
|
@@ -8938,102 +9096,8 @@ var ethicalAds2 = withApi(ethicalAds, "ethicalads", pullEthicalAds, ethicalAdsAp
|
|
|
8938
9096
|
var microsoftAds2 = withApi(microsoftAds, "microsoft", pullMicrosoftAds, microsoftApiAccount);
|
|
8939
9097
|
var linkedInAds2 = withApi(linkedInAds, "linkedin", pullLinkedInAds, linkedInApiAccount);
|
|
8940
9098
|
var metaAds2 = withApi(metaAds, "meta", pullMetaAds, metaApiAccount);
|
|
8941
|
-
// ../../packages/growth/src/portfolio.ts
|
|
8942
|
-
var FUNNEL_STAGES = ["click", "fetch", "signup", "activated", "paid"];
|
|
8943
|
-
var EXPERIMENT_KINDS = ["paid", "owned", "onboarding", "lifecycle", "community", "research"];
|
|
8944
|
-
var AGENT_ROLES = [
|
|
8945
|
-
"growth-lead",
|
|
8946
|
-
"analyst",
|
|
8947
|
-
"onboarding",
|
|
8948
|
-
"seo",
|
|
8949
|
-
"paid-ads",
|
|
8950
|
-
"landing",
|
|
8951
|
-
"lifecycle",
|
|
8952
|
-
"content",
|
|
8953
|
-
"community",
|
|
8954
|
-
"research",
|
|
8955
|
-
"integrations",
|
|
8956
|
-
"canada"
|
|
8957
|
-
];
|
|
8958
|
-
var EXPERIMENT_STATUSES = ["proposed", "approved", "running", "read", "killed", "scaled"];
|
|
8959
|
-
var EXPERIMENT_METRICS = [
|
|
8960
|
-
"ctr",
|
|
8961
|
-
"fetch_rate",
|
|
8962
|
-
"agent_fetches",
|
|
8963
|
-
"human_visits",
|
|
8964
|
-
"signups",
|
|
8965
|
-
"activation_rate",
|
|
8966
|
-
"paid_rate",
|
|
8967
|
-
"topup_request_rate",
|
|
8968
|
-
"topup_completion_rate",
|
|
8969
|
-
"cost_per_activated",
|
|
8970
|
-
"cost_per_payer",
|
|
8971
|
-
"onboarding_success_rate",
|
|
8972
|
-
"time_to_first_call",
|
|
8973
|
-
"npm_downloads",
|
|
8974
|
-
"github_stars",
|
|
8975
|
-
"registry_installs"
|
|
8976
|
-
];
|
|
8977
|
-
var SURFACE_TYPES = [
|
|
8978
|
-
"page",
|
|
8979
|
-
"copy",
|
|
8980
|
-
"code",
|
|
8981
|
-
"listing",
|
|
8982
|
-
"cli",
|
|
8983
|
-
"notice",
|
|
8984
|
-
"ad",
|
|
8985
|
-
"post",
|
|
8986
|
-
"email",
|
|
8987
|
-
"external",
|
|
8988
|
-
"price"
|
|
8989
|
-
];
|
|
8990
|
-
var TIER2_SURFACES = new Set(["ad", "post", "email", "price", "listing", "external"]);
|
|
8991
|
-
var SURFACE_RE = new RegExp(`^(${SURFACE_TYPES.join("|")}):[^\\s*]+\\*?$`);
|
|
8992
|
-
var EXPERIMENT_ID_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
8993
|
-
var Day2 = string2().refine(isDay, "a UTC day, YYYY-MM-DD");
|
|
8994
|
-
var Bps = number2().int().min(0).max(1e4);
|
|
8995
|
-
var Micros = number2().int().min(0);
|
|
8996
|
-
var DecisionRule = discriminatedUnion("kind", [
|
|
8997
|
-
object({ kind: literal("p_better"), threshold: number2().min(0.5).max(0.999) }),
|
|
8998
|
-
object({ kind: literal("rate_at_least"), bps: Bps }),
|
|
8999
|
-
object({ kind: literal("cost_at_most"), micros: Micros })
|
|
9000
|
-
]);
|
|
9001
|
-
var ExperimentSpec = object({
|
|
9002
|
-
id: string2().max(64).regex(EXPERIMENT_ID_RE, "kebab-case, the file name without .yaml"),
|
|
9003
|
-
title: string2().min(5).max(120),
|
|
9004
|
-
hypothesis: string2().min(20).max(600),
|
|
9005
|
-
owner: _enum(AGENT_ROLES),
|
|
9006
|
-
kind: _enum(EXPERIMENT_KINDS),
|
|
9007
|
-
stage: _enum(FUNNEL_STAGES),
|
|
9008
|
-
tier: union([literal(0), literal(1), literal(2)]),
|
|
9009
|
-
status: _enum(EXPERIMENT_STATUSES),
|
|
9010
|
-
created: Day2,
|
|
9011
|
-
surfaces: array(string2().regex(SURFACE_RE, "<type>:<target>, e.g. page:/docs/cursor")).min(1),
|
|
9012
|
-
codes: array(CodeSchema).default([]),
|
|
9013
|
-
metric: object({ primary: _enum(EXPERIMENT_METRICS), guardrail: _enum(EXPERIMENT_METRICS).optional() }),
|
|
9014
|
-
design: object({
|
|
9015
|
-
type: _enum(["ab", "single", "discovery", "synthetic"]),
|
|
9016
|
-
variants: array(string2().min(1).max(40)).optional(),
|
|
9017
|
-
arms: record(string2().min(1).max(40), array(CodeSchema).min(1)).optional()
|
|
9018
|
-
}),
|
|
9019
|
-
run: object({ from: Day2, to: Day2 }).optional(),
|
|
9020
|
-
decision: object({ read_on: Day2, min_n: number2().int().min(1), rule: DecisionRule }),
|
|
9021
|
-
budget: object({ weekly_micros: Micros }).default({ weekly_micros: 0 }),
|
|
9022
|
-
estimate: object({
|
|
9023
|
-
p_win_bps: Bps,
|
|
9024
|
-
lift_bps: number2().int().min(1).max(1e5),
|
|
9025
|
-
baseline_per_week: number2().min(0),
|
|
9026
|
-
effort_days: number2().min(0).max(30)
|
|
9027
|
-
}),
|
|
9028
|
-
approval: object({ by: string2(), on: Day2 }).optional(),
|
|
9029
|
-
result: object({
|
|
9030
|
-
read: Day2,
|
|
9031
|
-
outcome: _enum(["win", "loss", "inconclusive", "stopped"]),
|
|
9032
|
-
summary: string2().min(10).max(1000)
|
|
9033
|
-
}).optional()
|
|
9034
|
-
}).strict();
|
|
9035
9099
|
// ../../packages/growth/src/results-schema.ts
|
|
9036
|
-
var
|
|
9100
|
+
var Day4 = string2().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
9037
9101
|
var Micros2 = number2().int();
|
|
9038
9102
|
var Count = number2().int().nonnegative();
|
|
9039
9103
|
var Bps2 = number2().int();
|
|
@@ -9065,7 +9129,7 @@ var CodeResultRow = ResultRow.extend({
|
|
|
9065
9129
|
channel: string2(),
|
|
9066
9130
|
message: string2().nullable(),
|
|
9067
9131
|
status: string2(),
|
|
9068
|
-
paused_on:
|
|
9132
|
+
paused_on: Day4.nullable(),
|
|
9069
9133
|
sample_ok: boolean2(),
|
|
9070
9134
|
pause_suggested: boolean2(),
|
|
9071
9135
|
leading: boolean2(),
|
|
@@ -9097,8 +9161,8 @@ var Scorecard = object({
|
|
|
9097
9161
|
real_use: object({ eligible: Count, returned: Count, rate_bps: Bps2.nullable() })
|
|
9098
9162
|
});
|
|
9099
9163
|
var Read = object({
|
|
9100
|
-
signups_from:
|
|
9101
|
-
signups_to:
|
|
9164
|
+
signups_from: Day4,
|
|
9165
|
+
signups_to: Day4,
|
|
9102
9166
|
scorecard: Scorecard,
|
|
9103
9167
|
by_message: array(ResultRow),
|
|
9104
9168
|
by_channel: array(ResultRow),
|
|
@@ -9120,7 +9184,7 @@ var RuleOutcome = _enum([
|
|
|
9120
9184
|
]);
|
|
9121
9185
|
var Rule = object({
|
|
9122
9186
|
id: RuleId,
|
|
9123
|
-
date:
|
|
9187
|
+
date: Day4,
|
|
9124
9188
|
status: RuleStatus,
|
|
9125
9189
|
outcome: RuleOutcome,
|
|
9126
9190
|
codes: array(string2()),
|
|
@@ -9129,7 +9193,7 @@ var Rule = object({
|
|
|
9129
9193
|
var PlatformFinality = object({
|
|
9130
9194
|
platform: string2(),
|
|
9131
9195
|
spend_micros: Micros2,
|
|
9132
|
-
final_through:
|
|
9196
|
+
final_through: Day4.nullable(),
|
|
9133
9197
|
last_import_at: string2().nullable(),
|
|
9134
9198
|
last_import_source: _enum(["csv", "api", "manual"]).nullable()
|
|
9135
9199
|
});
|
|
@@ -9154,13 +9218,13 @@ var Checks = object({
|
|
|
9154
9218
|
}),
|
|
9155
9219
|
spread: array(object({
|
|
9156
9220
|
code: string2(),
|
|
9157
|
-
paused_on:
|
|
9221
|
+
paused_on: Day4,
|
|
9158
9222
|
signups_after: Count,
|
|
9159
9223
|
verdict_changes: boolean2()
|
|
9160
9224
|
})),
|
|
9161
9225
|
spend: object({
|
|
9162
9226
|
final: boolean2(),
|
|
9163
|
-
final_through:
|
|
9227
|
+
final_through: Day4.nullable(),
|
|
9164
9228
|
platforms: array(PlatformFinality),
|
|
9165
9229
|
unattributed_micros: Micros2,
|
|
9166
9230
|
unattributed_rows: Count
|
|
@@ -9186,13 +9250,13 @@ var GrowthResults = object({
|
|
|
9186
9250
|
key: string2(),
|
|
9187
9251
|
title: string2(),
|
|
9188
9252
|
hypothesis: string2(),
|
|
9189
|
-
spend: object({ from:
|
|
9253
|
+
spend: object({ from: Day4, to: Day4 }),
|
|
9190
9254
|
dates: object({
|
|
9191
|
-
pause_check:
|
|
9192
|
-
cost_read:
|
|
9193
|
-
payment_read:
|
|
9194
|
-
tail_read:
|
|
9195
|
-
tail_signups_to:
|
|
9255
|
+
pause_check: Day4,
|
|
9256
|
+
cost_read: Day4,
|
|
9257
|
+
payment_read: Day4,
|
|
9258
|
+
tail_read: Day4,
|
|
9259
|
+
tail_signups_to: Day4
|
|
9196
9260
|
}),
|
|
9197
9261
|
thresholds: object({
|
|
9198
9262
|
pass_cost_micros: Micros2,
|
|
@@ -9213,7 +9277,7 @@ var GrowthResults = object({
|
|
|
9213
9277
|
cost_per_payer_micros: Micros2
|
|
9214
9278
|
})
|
|
9215
9279
|
}),
|
|
9216
|
-
as_of:
|
|
9280
|
+
as_of: Day4,
|
|
9217
9281
|
day: number2().int(),
|
|
9218
9282
|
phase: _enum(EXPERIMENT_PHASES),
|
|
9219
9283
|
read: Read,
|
|
@@ -9225,7 +9289,7 @@ var GrowthResults = object({
|
|
|
9225
9289
|
decisions: array(GrowthDecision)
|
|
9226
9290
|
});
|
|
9227
9291
|
// ../../packages/schemas/src/legal.ts
|
|
9228
|
-
var TERMS_VERSION = "2026-10-
|
|
9292
|
+
var TERMS_VERSION = "2026-10-11";
|
|
9229
9293
|
var LEGAL_SITE = "https://scaledthought.com";
|
|
9230
9294
|
var LEGAL_PATHS = {
|
|
9231
9295
|
index: "/legal",
|
|
@@ -9233,78 +9297,564 @@ var LEGAL_PATHS = {
|
|
|
9233
9297
|
privacy: "/legal/privacy",
|
|
9234
9298
|
acceptableUse: "/legal/acceptable-use",
|
|
9235
9299
|
creditsAndRefunds: "/legal/credits-and-refunds",
|
|
9236
|
-
subprocessors: "/legal/subprocessors",
|
|
9237
9300
|
security: "/legal/security"
|
|
9238
9301
|
};
|
|
9239
9302
|
var TERMS_URL = `${LEGAL_SITE}${LEGAL_PATHS.terms}`;
|
|
9240
9303
|
var PRIVACY_URL = `${LEGAL_SITE}${LEGAL_PATHS.privacy}`;
|
|
9241
9304
|
var ACCEPTABLE_USE_URL = `${LEGAL_SITE}${LEGAL_PATHS.acceptableUse}`;
|
|
9242
9305
|
var REFUND_POLICY_URL = `${LEGAL_SITE}${LEGAL_PATHS.creditsAndRefunds}`;
|
|
9243
|
-
var TERMS_NOTICE = `
|
|
9306
|
+
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}.`;
|
|
9244
9307
|
|
|
9245
|
-
// src/
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
model ? `Verify with one real completion: ${NPX} chat -m ${model} "Reply with OK" (or POST $ST_BASE_URL/chat/completions with that model)` : `No chat model is live in this region right now. Check again with: ${NPX} models --live`,
|
|
9262
|
-
`Or let the router pick: ${NPX} chat -m auto "Reply with OK"`,
|
|
9263
|
-
"Use it from code: any OpenAI SDK with base_url=$ST_BASE_URL and api_key=$ST_API_KEY (both are in .env)",
|
|
9264
|
-
`Optional, connect MCP: ${NPX} mcp`,
|
|
9265
|
-
...needsCredits ? [] : [`More credits when needed: ${TOPUP_COMMAND} returns an approval_url for your human (inference is prepaid)`]
|
|
9266
|
-
];
|
|
9308
|
+
// src/connect.ts
|
|
9309
|
+
import { chmodSync as chmodSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9310
|
+
import { hostname } from "node:os";
|
|
9311
|
+
import { basename as basename2, join as join4 } from "node:path";
|
|
9312
|
+
|
|
9313
|
+
// src/credentials.ts
|
|
9314
|
+
import { spawnSync } from "node:child_process";
|
|
9315
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
9316
|
+
import { join as join3 } from "node:path";
|
|
9317
|
+
var SERVICE = "scaledthought";
|
|
9318
|
+
var slot = (a) => `${a.region}:${a.account_id}`;
|
|
9319
|
+
var SLOT_RE = /^(us|ca):acct_[0-9A-Za-z]{1,40}$/;
|
|
9320
|
+
function writePrivate(file, data) {
|
|
9321
|
+
writeFileSync(file, `${JSON.stringify(data, null, 2)}
|
|
9322
|
+
`, { mode: 384 });
|
|
9323
|
+
chmodSync(file, 384);
|
|
9267
9324
|
}
|
|
9268
|
-
|
|
9269
|
-
|
|
9325
|
+
function readJson(file, fallback) {
|
|
9326
|
+
try {
|
|
9327
|
+
return existsSync2(file) ? JSON.parse(readFileSync3(file, "utf8")) : fallback;
|
|
9328
|
+
} catch {
|
|
9329
|
+
return fallback;
|
|
9330
|
+
}
|
|
9331
|
+
}
|
|
9332
|
+
function fileBackend(dir) {
|
|
9333
|
+
const file = join3(dir, "credentials.json");
|
|
9334
|
+
const all = () => readJson(file, {});
|
|
9270
9335
|
return {
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9336
|
+
where: "~/.config/scaledthought/credentials.json (0600)",
|
|
9337
|
+
get: (s) => all()[s] ?? null,
|
|
9338
|
+
set: (s, key) => {
|
|
9339
|
+
writePrivate(file, { ...all(), [s]: key });
|
|
9340
|
+
return true;
|
|
9341
|
+
},
|
|
9342
|
+
del: (s) => {
|
|
9343
|
+
const a = all();
|
|
9344
|
+
delete a[s];
|
|
9345
|
+
writePrivate(file, a);
|
|
9346
|
+
}
|
|
9275
9347
|
};
|
|
9276
9348
|
}
|
|
9277
|
-
|
|
9278
|
-
const live = await listModels(ctx, { live: true, region });
|
|
9279
|
-
const suggested = live.find((m) => m.category !== "embedding" && m.modality === "chat")?.id ?? null;
|
|
9280
|
-
const needsCredits = (balanceUsd ?? 0) <= 0;
|
|
9349
|
+
function keychainBackend() {
|
|
9281
9350
|
return {
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9351
|
+
where: "macOS Keychain (service scaledthought)",
|
|
9352
|
+
get: (s) => {
|
|
9353
|
+
const r = spawnSync("security", ["find-generic-password", "-s", SERVICE, "-a", s, "-w"], { encoding: "utf8" });
|
|
9354
|
+
return r.status === 0 ? r.stdout.trim() || null : null;
|
|
9355
|
+
},
|
|
9356
|
+
set: (s, key) => spawnSync("security", ["-i"], {
|
|
9357
|
+
input: `add-generic-password -U -s ${SERVICE} -a ${s} -l ScaledThought -w ${key}
|
|
9358
|
+
`,
|
|
9359
|
+
encoding: "utf8"
|
|
9360
|
+
}).status === 0,
|
|
9361
|
+
del: (s) => {
|
|
9362
|
+
spawnSync("security", ["delete-generic-password", "-s", SERVICE, "-a", s], { encoding: "utf8" });
|
|
9363
|
+
}
|
|
9288
9364
|
};
|
|
9289
9365
|
}
|
|
9290
|
-
function
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9366
|
+
function secretToolBackend() {
|
|
9367
|
+
return {
|
|
9368
|
+
where: "the Secret Service keyring (secret-tool, service scaledthought)",
|
|
9369
|
+
get: (s) => {
|
|
9370
|
+
const r = spawnSync("secret-tool", ["lookup", "service", SERVICE, "account", s], { encoding: "utf8" });
|
|
9371
|
+
return r.status === 0 ? r.stdout.trim() || null : null;
|
|
9372
|
+
},
|
|
9373
|
+
set: (s, key) => spawnSync("secret-tool", ["store", `--label=ScaledThought ${s}`, "service", SERVICE, "account", s], {
|
|
9374
|
+
input: key,
|
|
9375
|
+
encoding: "utf8"
|
|
9376
|
+
}).status === 0,
|
|
9377
|
+
del: (s) => {
|
|
9378
|
+
spawnSync("secret-tool", ["clear", "service", SERVICE, "account", s], { encoding: "utf8" });
|
|
9379
|
+
}
|
|
9380
|
+
};
|
|
9381
|
+
}
|
|
9382
|
+
var has = (bin) => spawnSync(bin, ["--help"], { stdio: "ignore" }).error === undefined;
|
|
9383
|
+
function storeAt(dir, os) {
|
|
9384
|
+
const indexFile = join3(dir, "accounts.json");
|
|
9385
|
+
const file = fileBackend(dir);
|
|
9386
|
+
const index = () => readJson(indexFile, []).filter((e) => SLOT_RE.test(slot(e)));
|
|
9387
|
+
const ensureDir = () => mkdirSync(dir, { recursive: true, mode: 448 });
|
|
9388
|
+
let where = os?.where ?? file.where;
|
|
9389
|
+
return {
|
|
9390
|
+
get where() {
|
|
9391
|
+
return where;
|
|
9392
|
+
},
|
|
9393
|
+
list: () => index().map(({ in: _in, ...a }) => a),
|
|
9394
|
+
getKey: (a) => {
|
|
9395
|
+
const e = index().find((x) => slot(x) === slot(a));
|
|
9396
|
+
if (!e)
|
|
9397
|
+
return null;
|
|
9398
|
+
return (e.in === "os" && os ? os.get(slot(a)) : file.get(slot(a))) ?? null;
|
|
9399
|
+
},
|
|
9400
|
+
save: (a, key) => {
|
|
9401
|
+
ensureDir();
|
|
9402
|
+
let into = "file";
|
|
9403
|
+
if (os?.set(slot(a), key))
|
|
9404
|
+
into = "os";
|
|
9405
|
+
else
|
|
9406
|
+
file.set(slot(a), key);
|
|
9407
|
+
where = into === "os" && os ? os.where : file.where;
|
|
9408
|
+
writePrivate(indexFile, [...index().filter((x) => slot(x) !== slot(a)), { ...a, in: into }]);
|
|
9409
|
+
},
|
|
9410
|
+
remove: (a) => {
|
|
9411
|
+
const e = index().find((x) => slot(x) === slot(a));
|
|
9412
|
+
if (!e)
|
|
9413
|
+
return;
|
|
9414
|
+
if (e.in === "os" && os)
|
|
9415
|
+
os.del(slot(a));
|
|
9416
|
+
else
|
|
9417
|
+
file.del(slot(a));
|
|
9418
|
+
writePrivate(indexFile, index().filter((x) => slot(x) !== slot(a)));
|
|
9419
|
+
},
|
|
9420
|
+
touch: (a) => {
|
|
9421
|
+
const all = index();
|
|
9422
|
+
const e = all.find((x) => slot(x) === slot(a));
|
|
9423
|
+
if (!e)
|
|
9424
|
+
return;
|
|
9425
|
+
e.last_used_at = new Date().toISOString();
|
|
9426
|
+
writePrivate(indexFile, all);
|
|
9427
|
+
}
|
|
9428
|
+
};
|
|
9429
|
+
}
|
|
9430
|
+
function machineStore(env, platform = process.platform) {
|
|
9431
|
+
const mode = env.ST_CREDENTIAL_STORE;
|
|
9432
|
+
const home = env.HOME || env.USERPROFILE;
|
|
9433
|
+
if (mode === "off" || !home)
|
|
9434
|
+
return null;
|
|
9435
|
+
const dir = join3(env.XDG_CONFIG_HOME || join3(home, ".config"), "scaledthought");
|
|
9436
|
+
let os = null;
|
|
9437
|
+
if (mode !== "file") {
|
|
9438
|
+
if (platform === "darwin" && existsSync2("/usr/bin/security"))
|
|
9439
|
+
os = keychainBackend();
|
|
9440
|
+
else if (platform === "linux" && env.DBUS_SESSION_BUS_ADDRESS && has("secret-tool"))
|
|
9441
|
+
os = secretToolBackend();
|
|
9442
|
+
}
|
|
9443
|
+
return storeAt(dir, os);
|
|
9444
|
+
}
|
|
9445
|
+
|
|
9446
|
+
// src/connect.ts
|
|
9447
|
+
var LOGIN_DIR = ".scaledthought";
|
|
9448
|
+
var LOGIN_FILE = join4(LOGIN_DIR, "login.json");
|
|
9449
|
+
var KEY_SAVED_TO = ".env (ST_API_KEY, ST_BASE_URL)";
|
|
9450
|
+
var CONNECT_CODE_RE = /^stl_[A-Za-z0-9_-]{22,64}$/;
|
|
9451
|
+
var PROJECT_SCOPES = ["inference", "read", "keys", "billing"];
|
|
9452
|
+
var storeOf = (ctx) => ctx.store !== undefined ? ctx.store : machineStore(ctx.env);
|
|
9453
|
+
function defaultClientLabel(env, host = hostname()) {
|
|
9454
|
+
const keys = Object.keys(env).filter((k) => env[k] !== undefined && env[k] !== "");
|
|
9455
|
+
const tool = env.CLAUDECODE ? "Claude Code" : keys.some((k) => k.startsWith("CODEX_")) ? "Codex" : keys.some((k) => k.startsWith("CURSOR_")) ? "Cursor" : "CLI";
|
|
9456
|
+
return `${tool} on ${host.replace(/\.local$/, "")}`.slice(0, 80);
|
|
9457
|
+
}
|
|
9458
|
+
function readLoginState(cwd) {
|
|
9459
|
+
const f = join4(cwd, LOGIN_FILE);
|
|
9460
|
+
if (!existsSync3(f))
|
|
9461
|
+
return null;
|
|
9462
|
+
try {
|
|
9463
|
+
const s = JSON.parse(readFileSync4(f, "utf8"));
|
|
9464
|
+
return typeof s.device_code === "string" && (s.region === "us" || s.region === "ca") ? s : null;
|
|
9465
|
+
} catch {
|
|
9466
|
+
return null;
|
|
9467
|
+
}
|
|
9468
|
+
}
|
|
9469
|
+
function writeLoginState(cwd, s) {
|
|
9470
|
+
mkdirSync2(join4(cwd, LOGIN_DIR), { recursive: true, mode: 448 });
|
|
9471
|
+
const f = join4(cwd, LOGIN_FILE);
|
|
9472
|
+
writeFileSync2(f, `${JSON.stringify(s)}
|
|
9473
|
+
`, { mode: 384 });
|
|
9474
|
+
chmodSync2(f, 384);
|
|
9475
|
+
ensureIgnored(cwd, `${LOGIN_DIR}/`);
|
|
9476
|
+
}
|
|
9477
|
+
var clearLoginState = (cwd) => rmSync(join4(cwd, LOGIN_FILE), { force: true });
|
|
9478
|
+
function ensureIgnored(cwd, entry) {
|
|
9479
|
+
const gi = join4(cwd, ".gitignore");
|
|
9480
|
+
const text = existsSync3(gi) ? readFileSync4(gi, "utf8") : "";
|
|
9481
|
+
const bare = entry.replace(/\/$/, "");
|
|
9482
|
+
if (text.split(/\r?\n/).some((l) => [entry, bare, `/${entry}`, `/${bare}`].includes(l.trim())))
|
|
9483
|
+
return;
|
|
9484
|
+
const sep = text === "" || text.endsWith(`
|
|
9485
|
+
`) ? "" : `
|
|
9486
|
+
`;
|
|
9487
|
+
writeFileSync2(gi, `${text}${sep}${entry}
|
|
9488
|
+
`);
|
|
9489
|
+
}
|
|
9490
|
+
function refuseExistingKey(ctx, force) {
|
|
9491
|
+
if (force)
|
|
9492
|
+
return;
|
|
9493
|
+
const where = ctx.env.ST_API_KEY ? "the ST_API_KEY environment variable" : readDotEnv(ctx.cwd).ST_API_KEY ? "./.env" : null;
|
|
9494
|
+
if (where)
|
|
9495
|
+
throw new CliError("key_exists", `This folder already has a ScaledThought key (${where}). Nothing was changed.`);
|
|
9496
|
+
}
|
|
9497
|
+
function saveToStore(ctx, a, key, noSave) {
|
|
9498
|
+
if (noSave)
|
|
9499
|
+
return false;
|
|
9500
|
+
const store = storeOf(ctx);
|
|
9501
|
+
if (!store)
|
|
9502
|
+
return false;
|
|
9503
|
+
try {
|
|
9504
|
+
store.save({ ...a, saved_at: new Date().toISOString() }, key);
|
|
9505
|
+
return store.where;
|
|
9506
|
+
} catch {
|
|
9507
|
+
return false;
|
|
9508
|
+
}
|
|
9509
|
+
}
|
|
9510
|
+
function finish(ctx, c, o) {
|
|
9511
|
+
writeEnv(ctx.cwd, { ST_API_KEY: c.api_key, ST_BASE_URL: c.base_url });
|
|
9512
|
+
ensureGitignored(ctx.cwd);
|
|
9513
|
+
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);
|
|
9514
|
+
clearLoginState(ctx.cwd);
|
|
9515
|
+
return {
|
|
9516
|
+
status: "connected",
|
|
9517
|
+
account_id: c.account_id,
|
|
9518
|
+
account_name: c.account_name,
|
|
9519
|
+
region: c.region,
|
|
9520
|
+
key_name: c.key_name,
|
|
9521
|
+
key_saved_to: KEY_SAVED_TO,
|
|
9522
|
+
saved_to_credential_store: saved !== false,
|
|
9523
|
+
...saved ? { credential_store: saved } : {}
|
|
9524
|
+
};
|
|
9525
|
+
}
|
|
9526
|
+
var claim2 = (ctx, region, body) => api(ctx, "POST", "/v1/device/token", { auth: false, region, body });
|
|
9527
|
+
async function login(ctx, o = {}) {
|
|
9528
|
+
refuseExistingKey(ctx, o.force);
|
|
9529
|
+
const region = o.region ?? "us";
|
|
9530
|
+
const label = (o.label ?? defaultClientLabel(ctx.env)).slice(0, 80);
|
|
9531
|
+
const r = await api(ctx, "POST", "/v1/device/codes", { auth: false, region, body: { client_label: label, origin: "agent" } });
|
|
9532
|
+
writeLoginState(ctx.cwd, {
|
|
9533
|
+
device_code: r.device_code,
|
|
9534
|
+
region,
|
|
9535
|
+
origin: "agent",
|
|
9536
|
+
user_code: r.user_code,
|
|
9537
|
+
created_at: new Date().toISOString()
|
|
9538
|
+
});
|
|
9539
|
+
const noOpen = o.noOpen || ctx.env.ST_NO_BROWSER === "1";
|
|
9540
|
+
const opened = !noOpen && await (o.open ?? openInBrowser)(r.verification_url);
|
|
9541
|
+
return {
|
|
9542
|
+
status: "pending",
|
|
9543
|
+
user_code: r.user_code,
|
|
9544
|
+
verification_url: r.verification_url,
|
|
9545
|
+
opened_in_browser: opened,
|
|
9546
|
+
expires_in: r.expires_in,
|
|
9547
|
+
client_label: label,
|
|
9548
|
+
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}.`,
|
|
9549
|
+
next_command: `${NPX} login --wait`,
|
|
9550
|
+
next_steps: [
|
|
9551
|
+
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.`,
|
|
9552
|
+
"Never put the code in a link: the human types it on the page."
|
|
9553
|
+
]
|
|
9554
|
+
};
|
|
9555
|
+
}
|
|
9556
|
+
async function loginWait(ctx, o = {}) {
|
|
9557
|
+
const s = readLoginState(ctx.cwd);
|
|
9558
|
+
if (s?.origin !== "agent")
|
|
9559
|
+
throw new CliError("no_login_pending", "No login is waiting in this folder.");
|
|
9560
|
+
refuseExistingKey(ctx, o.force);
|
|
9561
|
+
const deadline = Date.now() + (o.timeoutS ?? 90) * 1000;
|
|
9562
|
+
for (;; ) {
|
|
9563
|
+
try {
|
|
9564
|
+
return finish(ctx, await claim2(ctx, s.region, { device_code: s.device_code }), o);
|
|
9565
|
+
} catch (e) {
|
|
9566
|
+
if (!(e instanceof CliError))
|
|
9567
|
+
throw e;
|
|
9568
|
+
if (e.code !== "authorization_pending") {
|
|
9569
|
+
if (["expired_token", "access_denied", "already_claimed", "invalid_device_code"].includes(e.code))
|
|
9570
|
+
clearLoginState(ctx.cwd);
|
|
9571
|
+
throw e;
|
|
9572
|
+
}
|
|
9573
|
+
if (Date.now() >= deadline)
|
|
9574
|
+
throw new CliError("authorization_pending", "Your human hasn't approved the code yet.", undefined, {
|
|
9575
|
+
user_code: s.user_code ?? null
|
|
9576
|
+
});
|
|
9577
|
+
}
|
|
9578
|
+
await new Promise((r) => setTimeout(r, o.pollMs ?? 5000));
|
|
9579
|
+
}
|
|
9580
|
+
}
|
|
9581
|
+
async function loginWithCode(ctx, o) {
|
|
9582
|
+
const code = o.code.trim();
|
|
9583
|
+
if (!CONNECT_CODE_RE.test(code))
|
|
9584
|
+
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.");
|
|
9585
|
+
refuseExistingKey(ctx, o.force);
|
|
9586
|
+
const region = o.region ?? "us";
|
|
9587
|
+
const label = (o.label ?? defaultClientLabel(ctx.env)).slice(0, 80);
|
|
9588
|
+
return finish(ctx, await claim2(ctx, region, { connect_code: code, client_label: label }), o);
|
|
9589
|
+
}
|
|
9590
|
+
var usdOf = (b) => b && typeof b.balance_usd === "number" ? b.balance_usd : null;
|
|
9591
|
+
var withKey = (ctx, key) => ({ ...ctx, env: { ...ctx.env, ST_API_KEY: key } });
|
|
9592
|
+
async function projectKey(ctx, store, a) {
|
|
9593
|
+
const key = store.getKey(a);
|
|
9594
|
+
if (!key || regionOfKey(key) !== a.region)
|
|
9595
|
+
return null;
|
|
9596
|
+
const folder = basename2(ctx.cwd) || "project";
|
|
9597
|
+
const name = `project: ${folder}`.slice(0, 80);
|
|
9598
|
+
let created;
|
|
9599
|
+
try {
|
|
9600
|
+
created = await api(withKey(ctx, key), "POST", "/v1/keys", {
|
|
9601
|
+
body: {
|
|
9602
|
+
name,
|
|
9603
|
+
child: true,
|
|
9604
|
+
scopes: PROJECT_SCOPES,
|
|
9605
|
+
lives_in: `env:${hostname()}:${ctx.cwd}`.slice(0, 300)
|
|
9606
|
+
}
|
|
9607
|
+
});
|
|
9608
|
+
} catch (e) {
|
|
9609
|
+
if (e instanceof CliError && ["invalid_api_key", "key_expired", "account_suspended"].includes(e.code)) {
|
|
9610
|
+
store.remove(a);
|
|
9611
|
+
return null;
|
|
9612
|
+
}
|
|
9613
|
+
if (e instanceof CliError && (e.code === "insufficient_scope" || e.code === "scope_not_allowed")) {
|
|
9614
|
+
writeEnv(ctx.cwd, { ST_API_KEY: key, ST_BASE_URL: a.base_url });
|
|
9615
|
+
ensureGitignored(ctx.cwd);
|
|
9616
|
+
store.touch(a);
|
|
9617
|
+
return { key, key_id: null, key_name: "this machine's saved key (it can't make project keys)" };
|
|
9618
|
+
}
|
|
9619
|
+
throw e;
|
|
9620
|
+
}
|
|
9621
|
+
writeEnv(ctx.cwd, { ST_API_KEY: created.api_key, ST_BASE_URL: a.base_url });
|
|
9622
|
+
ensureGitignored(ctx.cwd);
|
|
9623
|
+
store.touch(a);
|
|
9624
|
+
return { key: created.api_key, key_id: created.id, key_name: created.name };
|
|
9625
|
+
}
|
|
9626
|
+
var label = (a, balance) => balance == null ? a.account_name : `${a.account_name} ($${balance.toFixed(2)})`;
|
|
9627
|
+
async function reuseSavedAccount(ctx, o) {
|
|
9628
|
+
const store = storeOf(ctx);
|
|
9629
|
+
const saved = (store?.list() ?? []).filter((a) => !o.region || a.region === o.region);
|
|
9630
|
+
if (!store || saved.length === 0) {
|
|
9631
|
+
if (o.account)
|
|
9632
|
+
throw new CliError("no_saved_accounts", `No account is saved on this machine${o.region ? ` in ${o.region}` : ""}.`);
|
|
9633
|
+
return null;
|
|
9634
|
+
}
|
|
9635
|
+
let pick;
|
|
9636
|
+
if (o.account) {
|
|
9637
|
+
const want = o.account.trim().toLowerCase();
|
|
9638
|
+
pick = saved.find((a) => a.account_id.toLowerCase() === want) ?? saved.find((a) => a.account_name.toLowerCase() === want);
|
|
9639
|
+
if (!pick)
|
|
9640
|
+
throw new CliError("account_not_found", `No saved account named or numbered "${o.account}".`, undefined, {
|
|
9641
|
+
saved_accounts: saved.map((a) => ({ account_id: a.account_id, name: a.account_name, region: a.region }))
|
|
9642
|
+
});
|
|
9643
|
+
} else if (saved.length > 1) {
|
|
9644
|
+
const rows = await Promise.all(saved.map(async (a) => {
|
|
9645
|
+
const key = store.getKey(a);
|
|
9646
|
+
const b = key ? await api(withKey(ctx, key), "GET", "/v1/billing").catch(() => null) : null;
|
|
9647
|
+
return { a, balance: usdOf(b) };
|
|
9648
|
+
}));
|
|
9649
|
+
rows.sort((x, y) => (y.a.last_used_at ?? y.a.saved_at).localeCompare(x.a.last_used_at ?? x.a.saved_at));
|
|
9650
|
+
const names = rows.map((r) => label(r.a, r.balance));
|
|
9651
|
+
return {
|
|
9652
|
+
status: "choose_account",
|
|
9653
|
+
choose_account: rows.map((r) => ({
|
|
9654
|
+
account_id: r.a.account_id,
|
|
9655
|
+
name: r.a.account_name,
|
|
9656
|
+
region: r.a.region,
|
|
9657
|
+
balance_usd: r.balance,
|
|
9658
|
+
last_used: (r.a.last_used_at ?? r.a.saved_at).slice(0, 10)
|
|
9659
|
+
})),
|
|
9660
|
+
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?`,
|
|
9661
|
+
next_steps: [
|
|
9662
|
+
"Ask your human message_for_human and end your turn. Nothing was created.",
|
|
9663
|
+
`Then run: ${NPX} init --account "<name or account_id>" (or ${NPX} init --new-account for a separate, new account).`
|
|
9664
|
+
]
|
|
9665
|
+
};
|
|
9666
|
+
} else
|
|
9667
|
+
pick = saved[0];
|
|
9668
|
+
const made = await projectKey(ctx, store, pick);
|
|
9669
|
+
if (!made) {
|
|
9670
|
+
if (o.account)
|
|
9671
|
+
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\`.`);
|
|
9672
|
+
return reuseSavedAccount(ctx, o);
|
|
9673
|
+
}
|
|
9674
|
+
const billing = await api(withKey(ctx, made.key), "GET", "/v1/billing").catch(() => null);
|
|
9675
|
+
return {
|
|
9676
|
+
already_initialized: false,
|
|
9677
|
+
reused_account: true,
|
|
9678
|
+
account_id: pick.account_id,
|
|
9679
|
+
account_name: pick.account_name,
|
|
9680
|
+
region: pick.region,
|
|
9681
|
+
base_url: pick.base_url,
|
|
9682
|
+
key_name: made.key_name,
|
|
9683
|
+
key_id: made.key_id,
|
|
9684
|
+
key_saved_to: KEY_SAVED_TO,
|
|
9685
|
+
saved_to_credential_store: true,
|
|
9686
|
+
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.`,
|
|
9687
|
+
...await liveSummary(withKey(ctx, made.key), pick.region, usdOf(billing))
|
|
9688
|
+
};
|
|
9689
|
+
}
|
|
9690
|
+
async function afterNewAccount(ctx, a, o) {
|
|
9691
|
+
const saved = saveToStore(ctx, {
|
|
9692
|
+
region: a.region,
|
|
9693
|
+
account_id: a.account_id,
|
|
9694
|
+
account_name: a.name?.trim() || `Account ${a.account_id.slice(-6)}`,
|
|
9695
|
+
base_url: a.base_url
|
|
9696
|
+
}, a.api_key, o.noSave);
|
|
9697
|
+
let existingCode = null;
|
|
9698
|
+
try {
|
|
9699
|
+
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) } });
|
|
9700
|
+
if (typeof r.device_code === "string") {
|
|
9701
|
+
writeLoginState(ctx.cwd, {
|
|
9702
|
+
device_code: r.device_code,
|
|
9703
|
+
region: a.region,
|
|
9704
|
+
origin: "init",
|
|
9705
|
+
user_code: typeof r.user_code === "string" ? r.user_code : undefined,
|
|
9706
|
+
new_account_id: a.account_id,
|
|
9707
|
+
created_at: new Date().toISOString()
|
|
9708
|
+
});
|
|
9709
|
+
existingCode = typeof r.user_code === "string" ? r.user_code : null;
|
|
9710
|
+
}
|
|
9711
|
+
} catch {}
|
|
9712
|
+
return { saved, existingCode };
|
|
9713
|
+
}
|
|
9714
|
+
function existingAccountCode(cwd) {
|
|
9715
|
+
const s = readLoginState(cwd);
|
|
9716
|
+
return s?.origin === "init" && s.user_code ? s.user_code : null;
|
|
9717
|
+
}
|
|
9718
|
+
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.`;
|
|
9719
|
+
async function useAccount(ctx, o) {
|
|
9720
|
+
const store = storeOf(ctx);
|
|
9721
|
+
const saved = store?.list() ?? [];
|
|
9722
|
+
if (!store || saved.length === 0)
|
|
9723
|
+
throw new CliError("no_saved_accounts", "No account is saved on this machine.");
|
|
9724
|
+
const want = o.target.trim().toLowerCase();
|
|
9725
|
+
const pick = saved.find((a) => a.account_id.toLowerCase() === want) ?? saved.find((a) => a.account_name.toLowerCase() === want);
|
|
9726
|
+
if (!pick)
|
|
9727
|
+
throw new CliError("account_not_found", `No saved account named or numbered "${o.target}".`, undefined, {
|
|
9728
|
+
saved_accounts: saved.map((a) => ({ account_id: a.account_id, name: a.account_name, region: a.region }))
|
|
9729
|
+
});
|
|
9730
|
+
const oldKey = readDotEnv(ctx.cwd).ST_API_KEY;
|
|
9731
|
+
if (oldKey) {
|
|
9732
|
+
const cur = await api(withKey(ctx, oldKey), "GET", "/v1/account").catch(() => null);
|
|
9733
|
+
if (cur?.account_id === pick.account_id)
|
|
9734
|
+
return { status: "unchanged", account_id: pick.account_id, account_name: pick.account_name };
|
|
9735
|
+
}
|
|
9736
|
+
const made = await projectKey(ctx, store, pick);
|
|
9737
|
+
if (!made)
|
|
9738
|
+
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\`.`);
|
|
9739
|
+
let oldRevoked = false;
|
|
9740
|
+
if (oldKey && !saved.some((a) => store.getKey(a) === oldKey)) {
|
|
9741
|
+
oldRevoked = await api(withKey(ctx, oldKey), "DELETE", "/v1/keys/self").then(() => true, () => false);
|
|
9742
|
+
}
|
|
9743
|
+
return {
|
|
9744
|
+
status: "switched",
|
|
9745
|
+
account_id: pick.account_id,
|
|
9746
|
+
account_name: pick.account_name,
|
|
9747
|
+
region: pick.region,
|
|
9748
|
+
key_name: made.key_name,
|
|
9749
|
+
key_saved_to: KEY_SAVED_TO,
|
|
9750
|
+
old_key_revoked: oldRevoked,
|
|
9751
|
+
...ctx.env.ST_API_KEY ? { warnings: ["ST_API_KEY is set in the environment and overrides ./.env: unset it to use the new key."] } : {}
|
|
9752
|
+
};
|
|
9753
|
+
}
|
|
9754
|
+
async function connectedExisting(ctx, o = {}) {
|
|
9755
|
+
const s = readLoginState(ctx.cwd);
|
|
9756
|
+
if (s?.origin !== "init")
|
|
9757
|
+
return null;
|
|
9758
|
+
let c;
|
|
9759
|
+
try {
|
|
9760
|
+
c = await claim2(ctx, s.region, { device_code: s.device_code });
|
|
9761
|
+
} catch (e) {
|
|
9762
|
+
if (e instanceof CliError && ["expired_token", "access_denied", "already_claimed"].includes(e.code))
|
|
9763
|
+
clearLoginState(ctx.cwd);
|
|
9764
|
+
return null;
|
|
9765
|
+
}
|
|
9766
|
+
const done = finish(ctx, c, o);
|
|
9767
|
+
const store = storeOf(ctx);
|
|
9768
|
+
if (c.closed_account && store)
|
|
9769
|
+
store.remove({ region: s.region, account_id: c.closed_account });
|
|
9770
|
+
const billing = await api(withKey(ctx, c.api_key), "GET", "/v1/billing").catch(() => null);
|
|
9771
|
+
const balance = usdOf(billing);
|
|
9772
|
+
return {
|
|
9773
|
+
status: "connected_existing",
|
|
9774
|
+
account_id: c.account_id,
|
|
9775
|
+
account_name: c.account_name,
|
|
9776
|
+
balance_usd: balance,
|
|
9777
|
+
key_name: c.key_name,
|
|
9778
|
+
key_saved_to: KEY_SAVED_TO,
|
|
9779
|
+
saved_to_credential_store: done.saved_to_credential_store,
|
|
9780
|
+
closed_account: c.closed_account ? `${c.closed_account} (new, never paid)` : null,
|
|
9781
|
+
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.`
|
|
9782
|
+
};
|
|
9783
|
+
}
|
|
9784
|
+
async function readCodeFrom(stream) {
|
|
9785
|
+
let text = "";
|
|
9786
|
+
for await (const chunk of stream) {
|
|
9787
|
+
text += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
9788
|
+
if (text.includes(`
|
|
9789
|
+
`) || text.length > 4096)
|
|
9790
|
+
break;
|
|
9791
|
+
}
|
|
9792
|
+
return /\bstl_[A-Za-z0-9_-]+/.exec(text)?.[0] ?? (text.split(/\r?\n/)[0] ?? "").trim();
|
|
9793
|
+
}
|
|
9794
|
+
|
|
9795
|
+
// src/commands.ts
|
|
9796
|
+
var NPX = "npx -y scaledthought@0.3";
|
|
9797
|
+
var FIRST_TOPUP_USD = 20;
|
|
9798
|
+
var TOPUP_COMMAND = `${NPX} billing topup --amount-usd ${FIRST_TOPUP_USD}`;
|
|
9799
|
+
var WAIT_COMMAND = `${NPX} billing wait`;
|
|
9800
|
+
var WAIT_PATIENCE_MIN = 15;
|
|
9801
|
+
var CREDIT_STEPS = [
|
|
9802
|
+
`Inference is prepaid and this account has no credits yet: completions return 402 insufficient_credits until your human pays. Do these first, in order:`,
|
|
9803
|
+
`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.`,
|
|
9804
|
+
`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.`,
|
|
9805
|
+
`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.`,
|
|
9806
|
+
"4. Then make the call below."
|
|
9807
|
+
];
|
|
9808
|
+
function nextSteps(model, needsCredits) {
|
|
9809
|
+
return [
|
|
9810
|
+
...needsCredits ? CREDIT_STEPS : [],
|
|
9811
|
+
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`,
|
|
9812
|
+
`Or let the router pick: ${NPX} chat -m auto "Reply with OK"`,
|
|
9813
|
+
"Use it from code: any OpenAI SDK with base_url=$ST_BASE_URL and api_key=$ST_API_KEY (both are in .env)",
|
|
9814
|
+
`Optional, connect MCP: ${NPX} mcp`,
|
|
9815
|
+
...needsCredits ? [] : [`More credits when needed: ${TOPUP_COMMAND} returns an approval_url for your human (inference is prepaid)`]
|
|
9816
|
+
];
|
|
9817
|
+
}
|
|
9818
|
+
var balanceOf = (b) => b && typeof b.balance_usd === "number" ? b.balance_usd : null;
|
|
9819
|
+
function termsOf(acct = {}) {
|
|
9820
|
+
return {
|
|
9821
|
+
url: acct.terms_url ?? TERMS_URL,
|
|
9822
|
+
version: acct.terms_version ?? TERMS_VERSION,
|
|
9823
|
+
refund_policy_url: REFUND_POLICY_URL,
|
|
9824
|
+
notice: acct.terms_notice ?? TERMS_NOTICE
|
|
9825
|
+
};
|
|
9826
|
+
}
|
|
9827
|
+
async function liveSummary(ctx, region, balanceUsd) {
|
|
9828
|
+
const live = await listModels(ctx, { live: true, region });
|
|
9829
|
+
const suggested = live.find((m) => m.category !== "embedding" && m.modality === "chat")?.id ?? null;
|
|
9830
|
+
const needsCredits = (balanceUsd ?? 0) <= 0;
|
|
9831
|
+
return {
|
|
9832
|
+
balance_usd: balanceUsd ?? 0,
|
|
9833
|
+
needs_credits: needsCredits,
|
|
9834
|
+
next_command: needsCredits ? TOPUP_COMMAND : `${NPX} chat -m ${suggested ?? "auto"} "Reply with OK"`,
|
|
9835
|
+
newest_models: live.slice(0, 5),
|
|
9836
|
+
suggested_model: suggested,
|
|
9837
|
+
next_steps: nextSteps(suggested, needsCredits)
|
|
9838
|
+
};
|
|
9839
|
+
}
|
|
9840
|
+
function signupAttribution(region, opts) {
|
|
9841
|
+
const warnings = [];
|
|
9842
|
+
let source;
|
|
9843
|
+
if (opts.source !== undefined) {
|
|
9844
|
+
source = normalizeCode(opts.source) ?? undefined;
|
|
9845
|
+
if (!source)
|
|
9846
|
+
warnings.push("--source was not a campaign code (like a-rd) and was not sent. The account is created without it.");
|
|
9847
|
+
}
|
|
9848
|
+
let adClick;
|
|
9849
|
+
if (opts.adClick !== undefined) {
|
|
9850
|
+
if (region !== "us")
|
|
9851
|
+
warnings.push("--ad-click is used only in the us region and was not sent.");
|
|
9852
|
+
else {
|
|
9853
|
+
const token = opts.adClick.trim().replace(/\)$/, "");
|
|
9854
|
+
if (!AD_CLICK_TOKEN_RE.test(token))
|
|
9855
|
+
warnings.push("--ad-click was not a valid ad-click token and was not sent.");
|
|
9856
|
+
else
|
|
9857
|
+
adClick = token;
|
|
9308
9858
|
}
|
|
9309
9859
|
}
|
|
9310
9860
|
return { source, ad_click: adClick, warnings };
|
|
@@ -9331,6 +9881,13 @@ async function init(ctx, opts) {
|
|
|
9331
9881
|
...ignored.length ? { warnings: [`${ignored.join(" and ")} apply only to a new account and were not sent.`] } : {}
|
|
9332
9882
|
};
|
|
9333
9883
|
}
|
|
9884
|
+
if (opts.account && opts.newAccount)
|
|
9885
|
+
throw new CliError("bad_argument", "Pass --account or --new-account, not both.");
|
|
9886
|
+
if (!opts.newAccount) {
|
|
9887
|
+
const reused = await reuseSavedAccount(ctx, { region: opts.region, account: opts.account });
|
|
9888
|
+
if (reused)
|
|
9889
|
+
return reused;
|
|
9890
|
+
}
|
|
9334
9891
|
const region = opts.region ?? "us";
|
|
9335
9892
|
const attribution = signupAttribution(region, opts);
|
|
9336
9893
|
const acct = await api(ctx, "POST", "/v1/accounts", {
|
|
@@ -9346,6 +9903,7 @@ async function init(ctx, opts) {
|
|
|
9346
9903
|
});
|
|
9347
9904
|
writeEnv(ctx.cwd, { ST_API_KEY: acct.api_key, ST_BASE_URL: acct.base_url });
|
|
9348
9905
|
ensureGitignored(ctx.cwd);
|
|
9906
|
+
const { saved, existingCode } = await afterNewAccount(ctx, { ...acct, name: opts.name }, { noSave: opts.noSave });
|
|
9349
9907
|
const keyed = { ...ctx, env: { ...ctx.env, ST_API_KEY: acct.api_key } };
|
|
9350
9908
|
const billing = await api(keyed, "GET", "/v1/billing").catch(() => null);
|
|
9351
9909
|
return {
|
|
@@ -9354,6 +9912,11 @@ async function init(ctx, opts) {
|
|
|
9354
9912
|
region: acct.region,
|
|
9355
9913
|
base_url: acct.base_url,
|
|
9356
9914
|
key_saved_to: ".env (ST_API_KEY, ST_BASE_URL)",
|
|
9915
|
+
saved_to_credential_store: saved !== false,
|
|
9916
|
+
...saved ? {
|
|
9917
|
+
credential_store: `${saved}: other folders on this machine reuse this account (init there makes a project key). Opt out with --no-save.`
|
|
9918
|
+
} : {},
|
|
9919
|
+
...existingCode ? { existing_account_code: existingCode, existing_account_hint: existingAccountHint(existingCode) } : {},
|
|
9357
9920
|
terms: termsOf(acct),
|
|
9358
9921
|
...attribution.source ? { source: acct.source ?? attribution.source } : {},
|
|
9359
9922
|
...await liveSummary(keyed, acct.region, balanceOf(billing)),
|
|
@@ -9361,26 +9924,26 @@ async function init(ctx, opts) {
|
|
|
9361
9924
|
};
|
|
9362
9925
|
}
|
|
9363
9926
|
function writeEnv(cwd, vars) {
|
|
9364
|
-
const f =
|
|
9927
|
+
const f = join5(cwd, ".env");
|
|
9365
9928
|
const names = new Set(Object.keys(vars));
|
|
9366
|
-
const kept =
|
|
9929
|
+
const kept = existsSync4(f) ? readFileSync5(f, "utf8").split(/\r?\n/).filter((l) => !names.has(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(l)?.[1] ?? "")) : [];
|
|
9367
9930
|
while (kept.length && kept[kept.length - 1].trim() === "")
|
|
9368
9931
|
kept.pop();
|
|
9369
9932
|
const lines = [...kept, ...Object.entries(vars).map(([k, v]) => `${k}=${v}`)];
|
|
9370
|
-
|
|
9933
|
+
writeFileSync3(f, `${lines.join(`
|
|
9371
9934
|
`)}
|
|
9372
9935
|
`, { mode: 384 });
|
|
9373
|
-
|
|
9936
|
+
chmodSync3(f, 384);
|
|
9374
9937
|
}
|
|
9375
9938
|
function ensureGitignored(cwd) {
|
|
9376
|
-
const gi =
|
|
9377
|
-
const text =
|
|
9939
|
+
const gi = join5(cwd, ".gitignore");
|
|
9940
|
+
const text = existsSync4(gi) ? readFileSync5(gi, "utf8") : "";
|
|
9378
9941
|
if (/^\/?\.env\*?\s*$/m.test(text))
|
|
9379
9942
|
return;
|
|
9380
9943
|
const sep = text === "" || text.endsWith(`
|
|
9381
9944
|
`) ? "" : `
|
|
9382
9945
|
`;
|
|
9383
|
-
|
|
9946
|
+
writeFileSync3(gi, `${text}${sep}.env
|
|
9384
9947
|
`);
|
|
9385
9948
|
}
|
|
9386
9949
|
function isCatalogRow(m) {
|
|
@@ -9542,9 +10105,9 @@ async function chat(ctx, o) {
|
|
|
9542
10105
|
};
|
|
9543
10106
|
}
|
|
9544
10107
|
var billingStatus = async (ctx) => withoutFreeFields(await api(ctx, "GET", "/v1/billing"));
|
|
9545
|
-
var TOPUP_LIMITS = { min_usd:
|
|
10108
|
+
var TOPUP_LIMITS = { min_usd: 20, max_usd: 2000, daily_max_usd: 2000 };
|
|
9546
10109
|
function checkTopupLimits(amountUsd, autoReload) {
|
|
9547
|
-
const { min_usd, max_usd, daily_max_usd
|
|
10110
|
+
const { min_usd, max_usd, daily_max_usd } = TOPUP_LIMITS;
|
|
9548
10111
|
const range = (flag, v, lo, hi) => {
|
|
9549
10112
|
if (v < lo || v > hi)
|
|
9550
10113
|
throw new CliError("bad_argument", `--${flag} must be between ${lo} and ${hi.toLocaleString("en-US")}, got ${v}.`, `Top-ups are $${min_usd} to $${max_usd.toLocaleString("en-US")} each, and at most $${daily_max_usd.toLocaleString("en-US")} per account per rolling 24 hours (auto-reloads included).`);
|
|
@@ -9553,9 +10116,9 @@ function checkTopupLimits(amountUsd, autoReload) {
|
|
|
9553
10116
|
if (!autoReload)
|
|
9554
10117
|
return;
|
|
9555
10118
|
range("auto-reload-amount", autoReload.amount_usd, min_usd, max_usd);
|
|
9556
|
-
range("auto-reload-below", autoReload.threshold_usd,
|
|
9557
|
-
if (autoReload.monthly_limit_usd
|
|
9558
|
-
range("auto-reload-monthly-max", autoReload.monthly_limit_usd, autoReload.amount_usd,
|
|
10119
|
+
range("auto-reload-below", autoReload.threshold_usd, 5, max_usd);
|
|
10120
|
+
if (autoReload.monthly_limit_usd != null)
|
|
10121
|
+
range("auto-reload-monthly-max", autoReload.monthly_limit_usd, autoReload.amount_usd, Number.POSITIVE_INFINITY);
|
|
9559
10122
|
}
|
|
9560
10123
|
var TOPUP_DONE = new Set(["paid", "expired", "failed", "canceled"]);
|
|
9561
10124
|
function topupNextSteps(topupId, amountUsd, opened, autoReload) {
|
|
@@ -9595,32 +10158,87 @@ async function billingTopup(ctx, o) {
|
|
|
9595
10158
|
});
|
|
9596
10159
|
const noOpen = o.noOpen || ctx.env.ST_NO_BROWSER === "1";
|
|
9597
10160
|
const opened = !noOpen && !!created.approval_url && await (o.open ?? openInBrowser)(created.approval_url);
|
|
10161
|
+
const existing = existingAccountCode(ctx.cwd);
|
|
10162
|
+
const message = messageForHuman(created.approval_url, o.amountUsd, o.autoReload, opened);
|
|
9598
10163
|
return {
|
|
9599
10164
|
status: "pending",
|
|
9600
10165
|
...created,
|
|
9601
10166
|
opened_in_browser: opened,
|
|
9602
|
-
message_for_human:
|
|
10167
|
+
message_for_human: message && existing ? `${message}
|
|
10168
|
+
|
|
10169
|
+
${existingAccountHint(existing)}` : message,
|
|
9603
10170
|
next_steps: topupNextSteps(created.topup_id, o.amountUsd, opened, o.autoReload)
|
|
9604
10171
|
};
|
|
9605
10172
|
}
|
|
10173
|
+
async function billingAutoReloadSetup(ctx, o = {}) {
|
|
10174
|
+
const body = {
|
|
10175
|
+
...o.thresholdUsd !== undefined ? { threshold_usd: o.thresholdUsd } : {},
|
|
10176
|
+
...o.amountUsd !== undefined ? { amount_usd: o.amountUsd } : {}
|
|
10177
|
+
};
|
|
10178
|
+
const created = await api(ctx, "POST", "/v1/billing/auto-reload/setup", {
|
|
10179
|
+
body
|
|
10180
|
+
});
|
|
10181
|
+
const noOpen = o.noOpen || ctx.env.ST_NO_BROWSER === "1";
|
|
10182
|
+
const opened = !noOpen && !!created.approval_url && await (o.open ?? openInBrowser)(created.approval_url);
|
|
10183
|
+
const wait = `${WAIT_COMMAND} ${created.topup_id}`;
|
|
10184
|
+
return {
|
|
10185
|
+
status: "pending",
|
|
10186
|
+
...created,
|
|
10187
|
+
opened_in_browser: opened,
|
|
10188
|
+
message_for_human: setupMessageForHuman(created.approval_url, created.auto_reload, opened),
|
|
10189
|
+
next_steps: [
|
|
10190
|
+
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}.`,
|
|
10191
|
+
"Nothing is charged now. `billing auto-reload off` turns it off without approval."
|
|
10192
|
+
]
|
|
10193
|
+
};
|
|
10194
|
+
}
|
|
9606
10195
|
function messageForHuman(approvalUrl, amountUsd, autoReload, opened = false) {
|
|
9607
10196
|
if (!approvalUrl)
|
|
9608
10197
|
return null;
|
|
9609
|
-
const reload = autoReload ? `
|
|
10198
|
+
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.";
|
|
10199
|
+
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.`;
|
|
9610
10200
|
if (opened)
|
|
9611
|
-
return `To finish the setup, I've opened the ScaledThought
|
|
10201
|
+
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:
|
|
9612
10202
|
|
|
9613
10203
|
${approvalUrl}
|
|
9614
10204
|
|
|
9615
|
-
|
|
9616
|
-
return `To finish the setup, please
|
|
10205
|
+
${rest} I'm watching for the payment and will carry on as soon as it goes through.`;
|
|
10206
|
+
return `To finish the setup, please add $${amountUsd} of prepaid ScaledThought credits (USD) here (you can change the amount):
|
|
9617
10207
|
|
|
9618
10208
|
${approvalUrl}
|
|
9619
10209
|
|
|
9620
|
-
|
|
10210
|
+
${rest} Tell me when it's done and I'll continue.`;
|
|
10211
|
+
}
|
|
10212
|
+
function setupMessageForHuman(approvalUrl, terms, opened = false) {
|
|
10213
|
+
if (!approvalUrl)
|
|
10214
|
+
return null;
|
|
10215
|
+
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.`;
|
|
10216
|
+
if (opened)
|
|
10217
|
+
return `I've opened a ScaledThought page in your browser to ${what} If it didn't open, use this link:
|
|
10218
|
+
|
|
10219
|
+
${approvalUrl}
|
|
10220
|
+
|
|
10221
|
+
I'm watching and will carry on as soon as it's set up.`;
|
|
10222
|
+
return `Please use this ScaledThought page to ${what}
|
|
10223
|
+
|
|
10224
|
+
${approvalUrl}
|
|
10225
|
+
|
|
10226
|
+
Tell me when it's done and I'll continue.`;
|
|
9621
10227
|
}
|
|
9622
10228
|
var WAIT_DEFAULT_S = 90;
|
|
9623
10229
|
async function billingWait(ctx, o = {}) {
|
|
10230
|
+
try {
|
|
10231
|
+
return await billingWaitOnce(ctx, o);
|
|
10232
|
+
} catch (e) {
|
|
10233
|
+
if (e instanceof CliError && ["invalid_api_key", "topup_canceled", "account_suspended"].includes(e.code)) {
|
|
10234
|
+
const switched = await connectedExisting(ctx, { noSave: o.noSave });
|
|
10235
|
+
if (switched)
|
|
10236
|
+
return switched;
|
|
10237
|
+
}
|
|
10238
|
+
throw e;
|
|
10239
|
+
}
|
|
10240
|
+
}
|
|
10241
|
+
async function billingWaitOnce(ctx, o) {
|
|
9624
10242
|
let id = o.topupId;
|
|
9625
10243
|
if (id !== undefined && !/^top_[0-9A-Za-z]+$/.test(id))
|
|
9626
10244
|
throw new CliError("bad_argument", `"${id}" is not a top-up id (top_...).`, "Use topup_id from `billing topup`.");
|
|
@@ -9666,7 +10284,7 @@ async function billingTopupAndWait(ctx, o) {
|
|
|
9666
10284
|
var billingAutoReloadOff = (ctx) => api(ctx, "PATCH", "/v1/billing/auto-reload", { body: { enabled: false } });
|
|
9667
10285
|
var billingTransactions = (ctx, o = {}) => api(ctx, "GET", `/v1/billing/transactions${o.cursor ? `?cursor=${encodeURIComponent(o.cursor)}` : ""}`);
|
|
9668
10286
|
function billingUpgradeRemoved() {
|
|
9669
|
-
throw new CliError("billing_upgrade_removed", "billing upgrade (monthly caps) was replaced by prepaid credits.", `Run \`${NPX} billing topup --amount-usd
|
|
10287
|
+
throw new CliError("billing_upgrade_removed", "billing upgrade (monthly caps) was replaced by prepaid credits.", `Run \`${NPX} billing topup --amount-usd 20\` and send your human the approval_url. They can turn on auto-reload on the same page.`);
|
|
9670
10288
|
}
|
|
9671
10289
|
var benchmarks = (ctx, id, region) => api(ctx, "GET", `/v1/models/${encodeURIComponent(id)}/benchmarks`, {
|
|
9672
10290
|
auth: false,
|
|
@@ -9675,10 +10293,10 @@ var benchmarks = (ctx, id, region) => api(ctx, "GET", `/v1/models/${encodeURICom
|
|
|
9675
10293
|
async function uploadJsonl(ctx, path, purpose) {
|
|
9676
10294
|
const { key, region } = resolveKey(ctx);
|
|
9677
10295
|
const file = resolve2(ctx.cwd, path);
|
|
9678
|
-
if (!
|
|
10296
|
+
if (!existsSync4(file))
|
|
9679
10297
|
throw new CliError("file_not_found", `No file at ${path}.`, "Pass the path to a .jsonl file.");
|
|
9680
10298
|
const form = new FormData;
|
|
9681
|
-
form.set("file", new Blob([
|
|
10299
|
+
form.set("file", new Blob([readFileSync5(file)], { type: "application/jsonl" }), path.split("/").pop() ?? "data.jsonl");
|
|
9682
10300
|
form.set("purpose", purpose);
|
|
9683
10301
|
const res = await ctx.fetch(`${controlUrl(region, ctx.env)}/v1/files`, {
|
|
9684
10302
|
method: "POST",
|
|
@@ -9733,9 +10351,9 @@ var VIDEO_WAIT_MS = 30 * 60000;
|
|
|
9733
10351
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
9734
10352
|
function imageDataUrl(ctx, path) {
|
|
9735
10353
|
const file = resolve2(ctx.cwd, path);
|
|
9736
|
-
if (!
|
|
10354
|
+
if (!existsSync4(file))
|
|
9737
10355
|
throw new CliError("file_not_found", `No file at ${path}.`, "Pass the path to a PNG, JPEG or WebP image.");
|
|
9738
|
-
const data =
|
|
10356
|
+
const data = readFileSync5(file);
|
|
9739
10357
|
if (data.byteLength > 10 * 1024 * 1024)
|
|
9740
10358
|
throw new CliError("input_reference_too_large", "Images are limited to 10 MiB.");
|
|
9741
10359
|
const type = data[0] === 255 && data[1] === 216 ? "image/jpeg" : data[0] === 137 && data[1] === 80 ? "image/png" : data.subarray(8, 12).toString() === "WEBP" ? "image/webp" : null;
|
|
@@ -9771,7 +10389,7 @@ async function videoDownload(ctx, id, output, variant = "video") {
|
|
|
9771
10389
|
throw await responseError(res);
|
|
9772
10390
|
const data = Buffer.from(await res.arrayBuffer());
|
|
9773
10391
|
const path = resolve2(ctx.cwd, output);
|
|
9774
|
-
|
|
10392
|
+
writeFileSync3(path, data);
|
|
9775
10393
|
return {
|
|
9776
10394
|
id,
|
|
9777
10395
|
variant,
|
|
@@ -9876,9 +10494,23 @@ function mcpSetup(ctx) {
|
|
|
9876
10494
|
openapi: `${controlUrl(region, ctx.env)}/openapi.json`
|
|
9877
10495
|
};
|
|
9878
10496
|
}
|
|
10497
|
+
var DASH_PAGES = ["overview", "billing", "keys", "members", "connect"];
|
|
10498
|
+
async function dashboardOpen(ctx, o = {}) {
|
|
10499
|
+
if (o.page !== undefined && !DASH_PAGES.includes(o.page))
|
|
10500
|
+
throw new CliError("bad_argument", `--page must be one of ${DASH_PAGES.join(", ")}.`, "e.g. --page billing");
|
|
10501
|
+
const r = await api(ctx, "POST", "/v1/dashboard/link", { body: { ...o.page ? { page: o.page } : {}, ...o.email ? { email: o.email } : {} } });
|
|
10502
|
+
const noOpen = o.noOpen || ctx.env.ST_NO_BROWSER === "1";
|
|
10503
|
+
const opened = !noOpen && await (o.open ?? openInBrowser)(r.url);
|
|
10504
|
+
return {
|
|
10505
|
+
...r,
|
|
10506
|
+
opened_in_browser: opened,
|
|
10507
|
+
message_for_human: opened ? r.message_for_human : r.message_for_human.replace("I've opened your ScaledThought dashboard.", `Your ScaledThought dashboard: ${r.url}`)
|
|
10508
|
+
};
|
|
10509
|
+
}
|
|
10510
|
+
var membersList = (ctx) => api(ctx, "GET", "/v1/members");
|
|
9879
10511
|
|
|
9880
10512
|
// src/decide.ts
|
|
9881
|
-
import { readFileSync as
|
|
10513
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
9882
10514
|
import { resolve as resolve3 } from "node:path";
|
|
9883
10515
|
var DEFAULT_DECISION_MODEL = "open-jev-27b";
|
|
9884
10516
|
var usage2 = (msg) => new CliError("usage_error", msg, 'Example: --question urgent:noul:"Is this urgent?" (see decide --help)');
|
|
@@ -9950,7 +10582,7 @@ function readState(ctx, o) {
|
|
|
9950
10582
|
if (o.stateFile !== undefined) {
|
|
9951
10583
|
let text;
|
|
9952
10584
|
try {
|
|
9953
|
-
text =
|
|
10585
|
+
text = readFileSync6(resolve3(ctx.cwd, o.stateFile), "utf8");
|
|
9954
10586
|
} catch {
|
|
9955
10587
|
throw usage2("--state-file could not be read.");
|
|
9956
10588
|
}
|
|
@@ -9967,7 +10599,7 @@ function decisionRequest(ctx, o) {
|
|
|
9967
10599
|
throw usage2("--file holds the whole request; don't combine it with --state or --question.");
|
|
9968
10600
|
let body;
|
|
9969
10601
|
try {
|
|
9970
|
-
body = JSON.parse(
|
|
10602
|
+
body = JSON.parse(readFileSync6(resolve3(ctx.cwd, o.file), "utf8"));
|
|
9971
10603
|
} catch {
|
|
9972
10604
|
throw usage2("--file must be a readable JSON file.");
|
|
9973
10605
|
}
|
|
@@ -10000,8 +10632,827 @@ async function decide2(ctx, body) {
|
|
|
10000
10632
|
return { ...out, region: res.headers.get("x-st-region"), request_id: res.headers.get("x-st-request-id") };
|
|
10001
10633
|
}
|
|
10002
10634
|
|
|
10635
|
+
// src/destinations/run.ts
|
|
10636
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
10637
|
+
var TIMEOUT_MS2 = 120000;
|
|
10638
|
+
var spawnRunner = (cmd, args, opts) => new Promise((resolve, reject) => {
|
|
10639
|
+
const child = spawn2(cmd, args, {
|
|
10640
|
+
cwd: opts.cwd,
|
|
10641
|
+
env: opts.env ? { ...process.env, ...opts.env } : process.env,
|
|
10642
|
+
shell: false,
|
|
10643
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
10644
|
+
windowsHide: true
|
|
10645
|
+
});
|
|
10646
|
+
let stdout = "";
|
|
10647
|
+
let stderr = "";
|
|
10648
|
+
const timer = setTimeout(() => child.kill("SIGTERM"), TIMEOUT_MS2);
|
|
10649
|
+
child.stdout.on("data", (d) => {
|
|
10650
|
+
stdout += d.toString("utf8");
|
|
10651
|
+
});
|
|
10652
|
+
child.stderr.on("data", (d) => {
|
|
10653
|
+
stderr += d.toString("utf8");
|
|
10654
|
+
});
|
|
10655
|
+
child.on("error", (e) => {
|
|
10656
|
+
clearTimeout(timer);
|
|
10657
|
+
reject(e);
|
|
10658
|
+
});
|
|
10659
|
+
child.on("close", (code) => {
|
|
10660
|
+
clearTimeout(timer);
|
|
10661
|
+
resolve({ code: code ?? 1, stdout, stderr });
|
|
10662
|
+
});
|
|
10663
|
+
child.stdin.on("error", () => {});
|
|
10664
|
+
child.stdin.end(opts.stdin ?? "");
|
|
10665
|
+
});
|
|
10666
|
+
var isMissing = (e) => e?.code === "ENOENT";
|
|
10667
|
+
function redact(text, secrets) {
|
|
10668
|
+
let out = text;
|
|
10669
|
+
for (const s of secrets)
|
|
10670
|
+
if (s)
|
|
10671
|
+
out = out.split(s).join("[redacted]");
|
|
10672
|
+
return out.replace(/st_(ca|us)_(live|test)_[0-9A-Za-z]{8,}/g, "[redacted]");
|
|
10673
|
+
}
|
|
10674
|
+
async function probe(io, p, args, stdin) {
|
|
10675
|
+
try {
|
|
10676
|
+
return await io.run(p.cmd, args, { cwd: io.cwd, stdin });
|
|
10677
|
+
} catch (e) {
|
|
10678
|
+
if (isMissing(e))
|
|
10679
|
+
throw missing(p);
|
|
10680
|
+
throw new CliError("destination_failed", `Could not start ${p.cmd}.`, p.install);
|
|
10681
|
+
}
|
|
10682
|
+
}
|
|
10683
|
+
async function exec(io, p, args, o = {}) {
|
|
10684
|
+
const r = await probe(io, p, args, o.stdin);
|
|
10685
|
+
if (r.code !== 0) {
|
|
10686
|
+
const detail = redact(r.stderr || r.stdout, [...o.secrets ?? [], o.stdin ?? ""]).trim().slice(-300);
|
|
10687
|
+
throw new CliError("destination_failed", `\`${o.shown ?? `${p.cmd} ${args.slice(0, 3).join(" ")}`}\` failed (exit code ${r.code}).`, p.login, detail ? { detail } : {});
|
|
10688
|
+
}
|
|
10689
|
+
return r.stdout;
|
|
10690
|
+
}
|
|
10691
|
+
function missing(p) {
|
|
10692
|
+
return new CliError("destination_cli_missing", `\`${p.cmd}\` isn't installed or isn't on PATH.`, p.install);
|
|
10693
|
+
}
|
|
10694
|
+
var SAFE = /^[A-Za-z0-9_.@:+=][A-Za-z0-9_.@:+=-]*$/;
|
|
10695
|
+
var SAFE_SLASH = /^[A-Za-z0-9_.@:+=][A-Za-z0-9_.@:+=/-]*$/;
|
|
10696
|
+
var isSafeValue = (v, o = {}) => typeof v === "string" && v.length <= 120 && (o.slash ? SAFE_SLASH : SAFE).test(v);
|
|
10697
|
+
function safeValue(flag, v, o = {}) {
|
|
10698
|
+
if (!isSafeValue(v, o))
|
|
10699
|
+
throw new CliError("bad_argument", `--${flag} "${v.slice(0, 60)}" isn't a valid name${o.slash ? "" : " (no slashes)"}.`, "Use letters, digits and . _ - : @ (it must not start with -).");
|
|
10700
|
+
return v;
|
|
10701
|
+
}
|
|
10702
|
+
|
|
10703
|
+
// src/destinations/types.ts
|
|
10704
|
+
var PUT_FLAGS = [
|
|
10705
|
+
"repo",
|
|
10706
|
+
"env",
|
|
10707
|
+
"app",
|
|
10708
|
+
"project",
|
|
10709
|
+
"worker",
|
|
10710
|
+
"secret",
|
|
10711
|
+
"aws-region",
|
|
10712
|
+
"locations",
|
|
10713
|
+
"namespace",
|
|
10714
|
+
"context",
|
|
10715
|
+
"config"
|
|
10716
|
+
];
|
|
10717
|
+
var keyName = (s) => s.length <= 80 ? s : `${s.slice(0, 79)}…`;
|
|
10718
|
+
|
|
10719
|
+
// src/destinations/aws.ts
|
|
10720
|
+
var AWS = {
|
|
10721
|
+
cmd: "aws",
|
|
10722
|
+
install: "Install the AWS CLI and configure it: https://aws.amazon.com/cli/, then `aws configure` or `aws sso login`.",
|
|
10723
|
+
login: "Check `aws sts get-caller-identity` and that you may manage Secrets Manager secrets in that region."
|
|
10724
|
+
};
|
|
10725
|
+
function target(region, secret) {
|
|
10726
|
+
return {
|
|
10727
|
+
livesIn: `aws:${region}/${secret}`,
|
|
10728
|
+
keyName: keyName(`aws: ${region} / ${secret}`),
|
|
10729
|
+
params: { region, secret }
|
|
10730
|
+
};
|
|
10731
|
+
}
|
|
10732
|
+
var FROM_STDIN = ["--secret-string", "file:///dev/stdin"];
|
|
10733
|
+
var aws = {
|
|
10734
|
+
id: "aws",
|
|
10735
|
+
label: "AWS Secrets Manager",
|
|
10736
|
+
flags: ["aws-region", "secret"],
|
|
10737
|
+
async resolve(f, io) {
|
|
10738
|
+
let region = f["aws-region"] ?? io.env.AWS_REGION ?? io.env.AWS_DEFAULT_REGION;
|
|
10739
|
+
if (!region)
|
|
10740
|
+
region = (await probe(io, AWS, ["configure", "get", "region"])).stdout.trim();
|
|
10741
|
+
if (!region)
|
|
10742
|
+
throw new CliError("bad_argument", "Which AWS region?", "Pass --aws-region, e.g. us-east-1.");
|
|
10743
|
+
const secret = safeValue("secret", f.secret ?? "scaledthought/api-key", { slash: true });
|
|
10744
|
+
return target(safeValue("aws-region", region), secret);
|
|
10745
|
+
},
|
|
10746
|
+
parse(rest) {
|
|
10747
|
+
const i = rest.indexOf("/");
|
|
10748
|
+
const region = rest.slice(0, i);
|
|
10749
|
+
const secret = rest.slice(i + 1);
|
|
10750
|
+
return i > 0 && isSafeValue(region) && isSafeValue(secret, { slash: true }) ? target(region, secret) : null;
|
|
10751
|
+
},
|
|
10752
|
+
async put(t, secret, io) {
|
|
10753
|
+
const { region, secret: name } = t.params;
|
|
10754
|
+
const exists = await probe(io, AWS, ["secretsmanager", "describe-secret", "--secret-id", name, "--region", region]);
|
|
10755
|
+
const args = exists.code === 0 ? ["secretsmanager", "put-secret-value", "--secret-id", name, ...FROM_STDIN, "--region", region] : ["secretsmanager", "create-secret", "--name", name, ...FROM_STDIN, "--region", region];
|
|
10756
|
+
const shown = `aws ${args.join(" ")}`;
|
|
10757
|
+
await exec(io, AWS, args, { stdin: secret, shown });
|
|
10758
|
+
return `${shown} (stdin)`;
|
|
10759
|
+
},
|
|
10760
|
+
next: (t) => `Read secret ${t.params.secret} in ${t.params.region} (AWSCURRENT); cached readers see it on refresh.`
|
|
10761
|
+
};
|
|
10762
|
+
|
|
10763
|
+
// src/destinations/cloudflare.ts
|
|
10764
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7 } from "node:fs";
|
|
10765
|
+
import { join as join6 } from "node:path";
|
|
10766
|
+
var WRANGLER = {
|
|
10767
|
+
cmd: "wrangler",
|
|
10768
|
+
install: "Install Wrangler and log in: npm i -g wrangler && wrangler login",
|
|
10769
|
+
login: "Check `wrangler whoami` (log in with `wrangler login`) and the Worker name."
|
|
10770
|
+
};
|
|
10771
|
+
function target2(worker, env) {
|
|
10772
|
+
return {
|
|
10773
|
+
livesIn: `cloudflare:${worker}${env ? `/${env}` : ""}`,
|
|
10774
|
+
keyName: keyName(`cloudflare: ${worker}${env ? ` / ${env}` : ""}`),
|
|
10775
|
+
params: { worker, ...env ? { env } : {} }
|
|
10776
|
+
};
|
|
10777
|
+
}
|
|
10778
|
+
function workerName(cwd) {
|
|
10779
|
+
for (const f of ["wrangler.toml", "wrangler.json", "wrangler.jsonc"]) {
|
|
10780
|
+
const p = join6(cwd, f);
|
|
10781
|
+
if (!existsSync5(p))
|
|
10782
|
+
continue;
|
|
10783
|
+
const text = readFileSync7(p, "utf8");
|
|
10784
|
+
const m = f.endsWith(".toml") ? /^\s*name\s*=\s*["']([^"']+)["']/m.exec(text) : /"name"\s*:\s*"([^"]+)"/.exec(text);
|
|
10785
|
+
if (m)
|
|
10786
|
+
return m[1];
|
|
10787
|
+
}
|
|
10788
|
+
return;
|
|
10789
|
+
}
|
|
10790
|
+
var cloudflare = {
|
|
10791
|
+
id: "cloudflare",
|
|
10792
|
+
label: "Cloudflare Workers",
|
|
10793
|
+
flags: ["worker", "env"],
|
|
10794
|
+
async resolve(f, io) {
|
|
10795
|
+
const worker = f.worker ?? workerName(io.cwd);
|
|
10796
|
+
if (!worker)
|
|
10797
|
+
throw new CliError("bad_argument", "Which Worker?", "Pass --worker <name> (or run where wrangler.toml is).");
|
|
10798
|
+
return target2(safeValue("worker", worker), f.env ? safeValue("env", f.env) : undefined);
|
|
10799
|
+
},
|
|
10800
|
+
parse(rest) {
|
|
10801
|
+
const [worker, env, ...more] = rest.split("/");
|
|
10802
|
+
if (!isSafeValue(worker) || more.length || env && !isSafeValue(env))
|
|
10803
|
+
return null;
|
|
10804
|
+
return target2(worker, env || undefined);
|
|
10805
|
+
},
|
|
10806
|
+
async put(t, secret, io) {
|
|
10807
|
+
const args = ["secret", "put", "ST_API_KEY", "--name", t.params.worker];
|
|
10808
|
+
if (t.params.env)
|
|
10809
|
+
args.push("--env", t.params.env);
|
|
10810
|
+
const shown = `wrangler ${args.join(" ")}`;
|
|
10811
|
+
await exec(io, WRANGLER, args, { stdin: secret, shown });
|
|
10812
|
+
return `${shown} (stdin)`;
|
|
10813
|
+
},
|
|
10814
|
+
next: () => "The Worker reads env.ST_API_KEY; Cloudflare deploys the new secret at once."
|
|
10815
|
+
};
|
|
10816
|
+
|
|
10817
|
+
// src/destinations/doppler.ts
|
|
10818
|
+
var DOPPLER = {
|
|
10819
|
+
cmd: "doppler",
|
|
10820
|
+
install: "Install the Doppler CLI and log in: https://docs.doppler.com/docs/install-cli, then `doppler login`.",
|
|
10821
|
+
login: "Check `doppler me` and the project and config names (`doppler setup` links a directory)."
|
|
10822
|
+
};
|
|
10823
|
+
function target3(project, config) {
|
|
10824
|
+
return {
|
|
10825
|
+
livesIn: `doppler:${project}/${config}`,
|
|
10826
|
+
keyName: keyName(`doppler: ${project} / ${config}`),
|
|
10827
|
+
params: { project, config }
|
|
10828
|
+
};
|
|
10829
|
+
}
|
|
10830
|
+
var doppler = {
|
|
10831
|
+
id: "doppler",
|
|
10832
|
+
label: "Doppler",
|
|
10833
|
+
flags: ["project", "config"],
|
|
10834
|
+
async resolve(f, io) {
|
|
10835
|
+
const get = async (what) => (await probe(io, DOPPLER, ["configure", "get", what, "--plain"])).stdout.trim();
|
|
10836
|
+
const project = f.project ?? await get("project");
|
|
10837
|
+
const config = f.config ?? await get("config");
|
|
10838
|
+
if (!project || !config)
|
|
10839
|
+
throw new CliError("bad_argument", "Which Doppler project and config?", "Pass --project and --config (or run `doppler setup` here).");
|
|
10840
|
+
return target3(safeValue("project", project), safeValue("config", config));
|
|
10841
|
+
},
|
|
10842
|
+
parse(rest) {
|
|
10843
|
+
const [project, config, ...more] = rest.split("/");
|
|
10844
|
+
return isSafeValue(project) && isSafeValue(config) && !more.length ? target3(project, config) : null;
|
|
10845
|
+
},
|
|
10846
|
+
async put(t, secret, io) {
|
|
10847
|
+
const args = ["secrets", "set", "ST_API_KEY", "--project", t.params.project, "--config", t.params.config];
|
|
10848
|
+
const shown = `doppler ${args.join(" ")}`;
|
|
10849
|
+
await exec(io, DOPPLER, args, { stdin: secret, shown });
|
|
10850
|
+
return `${shown} (stdin)`;
|
|
10851
|
+
},
|
|
10852
|
+
next: () => "Apps started with `doppler run` get it on their next start."
|
|
10853
|
+
};
|
|
10854
|
+
|
|
10855
|
+
// src/destinations/fly.ts
|
|
10856
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8 } from "node:fs";
|
|
10857
|
+
import { join as join7 } from "node:path";
|
|
10858
|
+
var FLY = {
|
|
10859
|
+
cmd: "fly",
|
|
10860
|
+
install: "Install flyctl and log in: https://fly.io/docs/flyctl/install/, then `fly auth login`.",
|
|
10861
|
+
login: "Check `fly auth whoami` (log in with `fly auth login`) and the app name."
|
|
10862
|
+
};
|
|
10863
|
+
var FLYCTL = { ...FLY, cmd: "flyctl" };
|
|
10864
|
+
var target4 = (app) => ({ livesIn: `fly:${app}`, keyName: keyName(`fly: ${app}`), params: { app } });
|
|
10865
|
+
function appFromToml(cwd) {
|
|
10866
|
+
const f = join7(cwd, "fly.toml");
|
|
10867
|
+
if (!existsSync6(f))
|
|
10868
|
+
return;
|
|
10869
|
+
return /^\s*app\s*=\s*["']([^"']+)["']/m.exec(readFileSync8(f, "utf8"))?.[1];
|
|
10870
|
+
}
|
|
10871
|
+
async function run(io, args, stdin, shown) {
|
|
10872
|
+
try {
|
|
10873
|
+
await exec(io, FLY, args, { stdin, shown });
|
|
10874
|
+
} catch (e) {
|
|
10875
|
+
if (e.code !== "destination_cli_missing")
|
|
10876
|
+
throw e;
|
|
10877
|
+
await exec(io, FLYCTL, args, { stdin, shown });
|
|
10878
|
+
}
|
|
10879
|
+
}
|
|
10880
|
+
var fly = {
|
|
10881
|
+
id: "fly",
|
|
10882
|
+
label: "Fly.io",
|
|
10883
|
+
flags: ["app"],
|
|
10884
|
+
async resolve(f, io) {
|
|
10885
|
+
const app = f.app ?? appFromToml(io.cwd);
|
|
10886
|
+
if (!app)
|
|
10887
|
+
throw new CliError("bad_argument", "Which Fly app?", "Pass --app <name> (or run where fly.toml is).");
|
|
10888
|
+
return target4(safeValue("app", app));
|
|
10889
|
+
},
|
|
10890
|
+
parse: (rest) => isSafeValue(rest) ? target4(rest) : null,
|
|
10891
|
+
async put(t, secret, io) {
|
|
10892
|
+
const args = ["secrets", "import", "-a", t.params.app];
|
|
10893
|
+
const shown = `fly ${args.join(" ")}`;
|
|
10894
|
+
await run(io, args, `ST_API_KEY=${secret}
|
|
10895
|
+
`, shown);
|
|
10896
|
+
return `${shown} (stdin)`;
|
|
10897
|
+
},
|
|
10898
|
+
next: () => "Fly restarts the app's machines with the new secret."
|
|
10899
|
+
};
|
|
10900
|
+
|
|
10901
|
+
// src/destinations/gcp.ts
|
|
10902
|
+
var GCLOUD = {
|
|
10903
|
+
cmd: "gcloud",
|
|
10904
|
+
install: "Install the Google Cloud CLI and log in: https://cloud.google.com/sdk/docs/install, then `gcloud auth login`.",
|
|
10905
|
+
login: "Check `gcloud auth list` and that you may manage Secret Manager secrets in the project."
|
|
10906
|
+
};
|
|
10907
|
+
var SECRET_RE = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,254}$/;
|
|
10908
|
+
function target5(project, secret, locations) {
|
|
10909
|
+
return {
|
|
10910
|
+
livesIn: `gcp:${project}/${secret}`,
|
|
10911
|
+
keyName: keyName(`gcp: ${project} / ${secret}`),
|
|
10912
|
+
params: { project, secret, ...locations ? { locations } : {} }
|
|
10913
|
+
};
|
|
10914
|
+
}
|
|
10915
|
+
var gcp = {
|
|
10916
|
+
id: "gcp",
|
|
10917
|
+
label: "Google Cloud Secret Manager",
|
|
10918
|
+
flags: ["project", "secret", "locations"],
|
|
10919
|
+
async resolve(f, io) {
|
|
10920
|
+
let project = f.project;
|
|
10921
|
+
if (!project) {
|
|
10922
|
+
project = (await exec(io, GCLOUD, ["config", "get-value", "project"])).trim();
|
|
10923
|
+
if (!project || project === "(unset)")
|
|
10924
|
+
throw new CliError("bad_argument", "No Google Cloud project is set.", "Pass --project <id>.");
|
|
10925
|
+
}
|
|
10926
|
+
const secret = f.secret ?? "st-api-key";
|
|
10927
|
+
if (!SECRET_RE.test(secret))
|
|
10928
|
+
throw new CliError("bad_argument", "--secret may use letters, digits, _ and - only (Secret Manager ids).");
|
|
10929
|
+
return target5(safeValue("project", project), secret, f.locations ? safeValue("locations", f.locations.replace(/\s/g, "")) : undefined);
|
|
10930
|
+
},
|
|
10931
|
+
parse(rest) {
|
|
10932
|
+
const [project, secret, ...more] = rest.split("/");
|
|
10933
|
+
return isSafeValue(project) && secret && SECRET_RE.test(secret) && !more.length ? target5(project, secret) : null;
|
|
10934
|
+
},
|
|
10935
|
+
async put(t, secret, io) {
|
|
10936
|
+
const { project, secret: name } = t.params;
|
|
10937
|
+
const exists = await probe(io, GCLOUD, ["secrets", "describe", name, "--project", project, "--format=value(name)"]);
|
|
10938
|
+
const args = exists.code === 0 ? ["secrets", "versions", "add", name, "--project", project, "--data-file=-"] : [
|
|
10939
|
+
"secrets",
|
|
10940
|
+
"create",
|
|
10941
|
+
name,
|
|
10942
|
+
"--project",
|
|
10943
|
+
project,
|
|
10944
|
+
"--data-file=-",
|
|
10945
|
+
...t.params.locations ? ["--replication-policy=user-managed", `--locations=${t.params.locations}`] : ["--replication-policy=automatic"]
|
|
10946
|
+
];
|
|
10947
|
+
const shown = `gcloud ${args.join(" ")}`;
|
|
10948
|
+
await exec(io, GCLOUD, args, { stdin: secret, shown });
|
|
10949
|
+
return `${shown} (stdin)`;
|
|
10950
|
+
},
|
|
10951
|
+
next: (t) => `Read secret ${t.params.secret}, version latest (anything pinned to a version needs updating).`
|
|
10952
|
+
};
|
|
10953
|
+
|
|
10954
|
+
// src/destinations/github.ts
|
|
10955
|
+
var GH = {
|
|
10956
|
+
cmd: "gh",
|
|
10957
|
+
install: "Install the GitHub CLI and log in: https://cli.github.com, then `gh auth login`.",
|
|
10958
|
+
login: "Check `gh auth status` (log in with `gh auth login`) and that you can manage the repo's secrets."
|
|
10959
|
+
};
|
|
10960
|
+
var APPS = { actions: "Actions", codespaces: "Codespaces", dependabot: "Dependabot" };
|
|
10961
|
+
var REPO_RE = /^[A-Za-z0-9_.][A-Za-z0-9_.-]*\/[A-Za-z0-9_.-]+$/;
|
|
10962
|
+
var isApp = (v) => v !== undefined && Object.hasOwn(APPS, v);
|
|
10963
|
+
function target6(repo, app, env) {
|
|
10964
|
+
return {
|
|
10965
|
+
livesIn: `github:${repo}/${app}${env ? `/${env}` : ""}`,
|
|
10966
|
+
keyName: keyName(`github: ${repo} (${APPS[app]}${env ? `, ${env}` : ""})`),
|
|
10967
|
+
params: { repo, app, ...env ? { env } : {} }
|
|
10968
|
+
};
|
|
10969
|
+
}
|
|
10970
|
+
var github = {
|
|
10971
|
+
id: "github",
|
|
10972
|
+
label: "GitHub",
|
|
10973
|
+
flags: ["repo", "app", "env"],
|
|
10974
|
+
async resolve(f, io) {
|
|
10975
|
+
const app = f.app ?? "actions";
|
|
10976
|
+
if (!isApp(app))
|
|
10977
|
+
throw new CliError("bad_argument", "--app must be actions, codespaces or dependabot.");
|
|
10978
|
+
if (f.env && app !== "actions")
|
|
10979
|
+
throw new CliError("bad_argument", "--env (a deployment environment) works only with --app actions.");
|
|
10980
|
+
const repo = f.repo ?? (await exec(io, GH, ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])).trim();
|
|
10981
|
+
if (!REPO_RE.test(repo))
|
|
10982
|
+
throw new CliError("bad_argument", "--repo must look like owner/name.", "Pass --repo owner/name.");
|
|
10983
|
+
return target6(repo, app, f.env ? safeValue("env", f.env) : undefined);
|
|
10984
|
+
},
|
|
10985
|
+
parse(rest) {
|
|
10986
|
+
const [owner, name, app, env, ...more] = rest.split("/");
|
|
10987
|
+
if (!owner || !name || !isApp(app) || more.length || !REPO_RE.test(`${owner}/${name}`))
|
|
10988
|
+
return null;
|
|
10989
|
+
if (env && !isSafeValue(env))
|
|
10990
|
+
return null;
|
|
10991
|
+
return target6(`${owner}/${name}`, app, env || undefined);
|
|
10992
|
+
},
|
|
10993
|
+
async put(t, secret, io) {
|
|
10994
|
+
const args = ["secret", "set", "ST_API_KEY", "--repo", t.params.repo];
|
|
10995
|
+
if (t.params.app !== "actions")
|
|
10996
|
+
args.push("--app", t.params.app);
|
|
10997
|
+
if (t.params.env)
|
|
10998
|
+
args.push("--env", t.params.env);
|
|
10999
|
+
const shown = `gh ${args.join(" ")}`;
|
|
11000
|
+
await exec(io, GH, args, { stdin: secret, shown });
|
|
11001
|
+
return `${shown} (stdin)`;
|
|
11002
|
+
},
|
|
11003
|
+
next: () => "Workflows read it as secrets.ST_API_KEY from their next run."
|
|
11004
|
+
};
|
|
11005
|
+
|
|
11006
|
+
// src/destinations/k8s.ts
|
|
11007
|
+
var KUBECTL = {
|
|
11008
|
+
cmd: "kubectl",
|
|
11009
|
+
install: "Install kubectl and point it at your cluster: https://kubernetes.io/docs/tasks/tools/",
|
|
11010
|
+
login: "Check `kubectl config current-context` and that you may create Secrets in the namespace."
|
|
11011
|
+
};
|
|
11012
|
+
var SECRET_RE2 = /^[a-z0-9]([-a-z0-9.]{0,251}[a-z0-9])?$/;
|
|
11013
|
+
function target7(context, namespace, secret) {
|
|
11014
|
+
return {
|
|
11015
|
+
livesIn: `k8s:${context}/${namespace}/${secret}`,
|
|
11016
|
+
keyName: keyName(`k8s: ${namespace}/${secret} (${context})`),
|
|
11017
|
+
params: { context, namespace, secret }
|
|
11018
|
+
};
|
|
11019
|
+
}
|
|
11020
|
+
var k8s = {
|
|
11021
|
+
id: "k8s",
|
|
11022
|
+
label: "Kubernetes",
|
|
11023
|
+
flags: ["context", "namespace", "secret"],
|
|
11024
|
+
async resolve(f, io) {
|
|
11025
|
+
const context = f.context ?? (await exec(io, KUBECTL, ["config", "current-context"])).trim();
|
|
11026
|
+
if (!context)
|
|
11027
|
+
throw new CliError("bad_argument", "No kubectl context is set.", "Pass --context <name>.");
|
|
11028
|
+
let namespace = f.namespace;
|
|
11029
|
+
if (!namespace) {
|
|
11030
|
+
const r = await probe(io, KUBECTL, ["config", "view", "--minify", "-o", "jsonpath={..namespace}"]);
|
|
11031
|
+
namespace = r.code === 0 && r.stdout.trim() ? r.stdout.trim() : "default";
|
|
11032
|
+
}
|
|
11033
|
+
const secret = f.secret ?? "scaledthought";
|
|
11034
|
+
if (!SECRET_RE2.test(secret))
|
|
11035
|
+
throw new CliError("bad_argument", "--secret must be a lowercase DNS name, e.g. scaledthought.");
|
|
11036
|
+
return target7(safeValue("context", context, { slash: true }), safeValue("namespace", namespace), secret);
|
|
11037
|
+
},
|
|
11038
|
+
parse(rest) {
|
|
11039
|
+
const parts = rest.split("/");
|
|
11040
|
+
if (parts.length < 3)
|
|
11041
|
+
return null;
|
|
11042
|
+
const secret = parts.pop();
|
|
11043
|
+
const namespace = parts.pop();
|
|
11044
|
+
const context = parts.join("/");
|
|
11045
|
+
if (!isSafeValue(context, { slash: true }) || !isSafeValue(namespace) || !SECRET_RE2.test(secret))
|
|
11046
|
+
return null;
|
|
11047
|
+
return target7(context, namespace, secret);
|
|
11048
|
+
},
|
|
11049
|
+
async put(t, secret, io) {
|
|
11050
|
+
const where = ["--namespace", t.params.namespace, "--context", t.params.context];
|
|
11051
|
+
const create = ["create", "secret", "generic", t.params.secret, "--from-file=ST_API_KEY=/dev/stdin"];
|
|
11052
|
+
const manifest = await exec(io, KUBECTL, [...create, "--dry-run=client", "-o", "yaml", ...where], {
|
|
11053
|
+
stdin: secret,
|
|
11054
|
+
shown: `kubectl create secret generic ${t.params.secret}`
|
|
11055
|
+
});
|
|
11056
|
+
const apply = ["apply", "--server-side", "--force-conflicts", "--field-manager=scaledthought", "-f", "-", ...where];
|
|
11057
|
+
await exec(io, KUBECTL, apply, {
|
|
11058
|
+
stdin: manifest,
|
|
11059
|
+
secrets: [secret, Buffer.from(secret).toString("base64")],
|
|
11060
|
+
shown: "kubectl apply --server-side -f -"
|
|
11061
|
+
});
|
|
11062
|
+
return `kubectl ${create.join(" ")} --dry-run=client -o yaml | kubectl apply --server-side -f - (stdin)`;
|
|
11063
|
+
},
|
|
11064
|
+
next: (t) => `Pods read Secret ${t.params.secret} when they start: restart the ones that use it (kubectl rollout restart deployment/<name> -n ${t.params.namespace}).`
|
|
11065
|
+
};
|
|
11066
|
+
|
|
11067
|
+
// src/destinations/vercel.ts
|
|
11068
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9 } from "node:fs";
|
|
11069
|
+
import { join as join8 } from "node:path";
|
|
11070
|
+
var VERCEL = {
|
|
11071
|
+
cmd: "vercel",
|
|
11072
|
+
install: "Install the Vercel CLI and log in: npm i -g vercel && vercel login",
|
|
11073
|
+
login: "Check `vercel whoami` (log in with `vercel login`) and that this directory is linked (`vercel link`)."
|
|
11074
|
+
};
|
|
11075
|
+
function target8(project, env) {
|
|
11076
|
+
return {
|
|
11077
|
+
livesIn: `vercel:${project}/${env}`,
|
|
11078
|
+
keyName: keyName(`vercel: ${project} / ${env}`),
|
|
11079
|
+
params: { project, env }
|
|
11080
|
+
};
|
|
11081
|
+
}
|
|
11082
|
+
function linkedProject(cwd) {
|
|
11083
|
+
const f = join8(cwd, ".vercel", "project.json");
|
|
11084
|
+
if (!existsSync7(f))
|
|
11085
|
+
return null;
|
|
11086
|
+
try {
|
|
11087
|
+
const j = JSON.parse(readFileSync9(f, "utf8"));
|
|
11088
|
+
return { name: j.projectName, id: j.projectId };
|
|
11089
|
+
} catch {
|
|
11090
|
+
return null;
|
|
11091
|
+
}
|
|
11092
|
+
}
|
|
11093
|
+
var NOT_LINKED = "Run this in the app's directory after `vercel link`.";
|
|
11094
|
+
var vercel = {
|
|
11095
|
+
id: "vercel",
|
|
11096
|
+
label: "Vercel",
|
|
11097
|
+
flags: ["project", "env"],
|
|
11098
|
+
async resolve(f, io) {
|
|
11099
|
+
const env = safeValue("env", f.env ?? "production");
|
|
11100
|
+
const linked = linkedProject(io.cwd);
|
|
11101
|
+
if (!linked)
|
|
11102
|
+
throw new CliError("not_linked", "This directory isn't linked to a Vercel project.", NOT_LINKED);
|
|
11103
|
+
const project = f.project ?? linked.name ?? linked.id;
|
|
11104
|
+
if (!project)
|
|
11105
|
+
throw new CliError("not_linked", "Can't tell which Vercel project this is.", "Pass --project.");
|
|
11106
|
+
if (f.project && linked.name && f.project !== linked.name)
|
|
11107
|
+
throw new CliError("not_linked", `This directory is linked to Vercel project "${linked.name}", not "${f.project}".`, NOT_LINKED);
|
|
11108
|
+
return target8(safeValue("project", project), env);
|
|
11109
|
+
},
|
|
11110
|
+
parse(rest) {
|
|
11111
|
+
const [project, env, ...more] = rest.split("/");
|
|
11112
|
+
return isSafeValue(project) && isSafeValue(env) && !more.length ? target8(project, env) : null;
|
|
11113
|
+
},
|
|
11114
|
+
async put(t, secret, io, mode) {
|
|
11115
|
+
const linked = linkedProject(io.cwd);
|
|
11116
|
+
if (!linked || linked.name !== t.params.project && linked.id !== t.params.project)
|
|
11117
|
+
throw new CliError("not_linked", `Vercel project "${t.params.project}" isn't the one linked in this directory.`, `Run \`keys rotate <id> --push\` in the directory linked to "${t.params.project}".`);
|
|
11118
|
+
if (mode === "replace")
|
|
11119
|
+
await probe(io, VERCEL, ["env", "rm", "ST_API_KEY", t.params.env, "--yes"]);
|
|
11120
|
+
const args = ["env", "add", "ST_API_KEY", t.params.env];
|
|
11121
|
+
const shown = `vercel ${args.join(" ")}`;
|
|
11122
|
+
await exec(io, VERCEL, args, { stdin: secret, shown });
|
|
11123
|
+
return `${shown} (stdin)`;
|
|
11124
|
+
},
|
|
11125
|
+
next: (t) => `Vercel reads the new value on the next deployment: ${t.params.env === "production" ? "vercel --prod" : "vercel"}`
|
|
11126
|
+
};
|
|
11127
|
+
|
|
11128
|
+
// src/destinations/index.ts
|
|
11129
|
+
var DESTINATIONS = Object.fromEntries([github, vercel, fly, cloudflare, gcp, aws, k8s, doppler].map((d) => [d.id, d]));
|
|
11130
|
+
function destination(id) {
|
|
11131
|
+
const d = Object.hasOwn(DESTINATIONS, id) ? DESTINATIONS[id] : undefined;
|
|
11132
|
+
if (!d)
|
|
11133
|
+
throw new CliError("unknown_destination", `Unknown destination "${id}".`, `Use one of: ${Object.keys(DESTINATIONS).join(", ")}.`);
|
|
11134
|
+
return d;
|
|
11135
|
+
}
|
|
11136
|
+
function fromLivesIn(livesIn) {
|
|
11137
|
+
if (!livesIn)
|
|
11138
|
+
return null;
|
|
11139
|
+
const i = livesIn.indexOf(":");
|
|
11140
|
+
if (i <= 0)
|
|
11141
|
+
return null;
|
|
11142
|
+
const id = livesIn.slice(0, i);
|
|
11143
|
+
const dest = Object.hasOwn(DESTINATIONS, id) ? DESTINATIONS[id] : undefined;
|
|
11144
|
+
const target = dest?.parse(livesIn.slice(i + 1));
|
|
11145
|
+
return dest && target ? { dest, target } : null;
|
|
11146
|
+
}
|
|
11147
|
+
|
|
11148
|
+
// src/everywhere.ts
|
|
11149
|
+
import { chmodSync as chmodSync4, existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
|
|
11150
|
+
import { homedir, hostname as hostname2 } from "node:os";
|
|
11151
|
+
import { dirname as dirname2, join as join9 } from "node:path";
|
|
11152
|
+
var io = (ctx) => ({ run: ctx.runner ?? spawnRunner, cwd: ctx.cwd, env: ctx.env });
|
|
11153
|
+
async function mintChild(ctx, o) {
|
|
11154
|
+
const k = await api(ctx, "POST", "/v1/keys", {
|
|
11155
|
+
body: {
|
|
11156
|
+
name: o.name,
|
|
11157
|
+
child: true,
|
|
11158
|
+
lives_in: o.livesIn,
|
|
11159
|
+
daily_budget_usd: o.budgetUsd,
|
|
11160
|
+
scopes: o.scopes,
|
|
11161
|
+
expires_in: o.expiresIn
|
|
11162
|
+
}
|
|
11163
|
+
});
|
|
11164
|
+
if (!k.api_key || !KEY_RE.test(k.api_key))
|
|
11165
|
+
throw new CliError("bad_response", "The server didn't return the new key.", "Retry; if it persists, report it.");
|
|
11166
|
+
return k;
|
|
11167
|
+
}
|
|
11168
|
+
async function revokeMinted(ctx, id) {
|
|
11169
|
+
return api(ctx, "DELETE", `/v1/keys/${encodeURIComponent(id)}`).then(() => true, () => false);
|
|
11170
|
+
}
|
|
11171
|
+
async function failAfterMint(ctx, keyId, e) {
|
|
11172
|
+
const revoked = await revokeMinted(ctx, keyId);
|
|
11173
|
+
const err = e instanceof CliError ? e : new CliError("destination_failed", "Storing the key failed.", "Retry; if it persists, report it.");
|
|
11174
|
+
throw new CliError(err.code, `${err.message} ${revoked ? "The new key was revoked; nothing else changed." : `Revoking the new key ${keyId} failed: run \`keys revoke ${keyId}\`.`}`, err.hint, { ...err.extra, key_id: keyId, key_revoked: revoked });
|
|
11175
|
+
}
|
|
11176
|
+
function checkFlags(dest, flags) {
|
|
11177
|
+
const extra = Object.keys(flags).filter((f) => flags[f] !== undefined && !dest.flags.includes(f));
|
|
11178
|
+
if (extra.length)
|
|
11179
|
+
throw new CliError("usage_error", `keys put ${dest.id} doesn't take --${extra.join(", --")}.`, `It takes: ${dest.flags.map((f) => `--${f}`).join(", ") || "no destination flags"}.`);
|
|
11180
|
+
}
|
|
11181
|
+
async function keysPut(ctx, destId, o) {
|
|
11182
|
+
const dest = destination(destId);
|
|
11183
|
+
checkFlags(dest, o.flags);
|
|
11184
|
+
const { region } = resolveKey(ctx);
|
|
11185
|
+
const target = await dest.resolve(o.flags, io(ctx));
|
|
11186
|
+
const k = await mintChild(ctx, {
|
|
11187
|
+
name: o.name ?? target.keyName,
|
|
11188
|
+
livesIn: target.livesIn,
|
|
11189
|
+
budgetUsd: o.budgetUsd,
|
|
11190
|
+
scopes: o.scopes,
|
|
11191
|
+
expiresIn: o.expiresIn
|
|
11192
|
+
});
|
|
11193
|
+
let storedWith;
|
|
11194
|
+
try {
|
|
11195
|
+
storedWith = await dest.put(target, k.api_key, io(ctx), "new");
|
|
11196
|
+
} catch (e) {
|
|
11197
|
+
return failAfterMint(ctx, k.id, e);
|
|
11198
|
+
}
|
|
11199
|
+
return {
|
|
11200
|
+
key_name: k.name,
|
|
11201
|
+
key_id: k.id,
|
|
11202
|
+
key_prefix: k.prefix,
|
|
11203
|
+
lives_in: target.livesIn,
|
|
11204
|
+
stored_with: storedWith,
|
|
11205
|
+
daily_budget_usd: k.daily_budget_usd,
|
|
11206
|
+
base_url: inferenceUrl(region, ctx.env),
|
|
11207
|
+
next: `${dest.next?.(target) ?? ""} The app reads ST_API_KEY; its base URL is ${inferenceUrl(region, ctx.env)} (not a secret: set ST_BASE_URL as a plain variable if your code uses it).`.trim()
|
|
11208
|
+
};
|
|
11209
|
+
}
|
|
11210
|
+
async function keysRotatePush(ctx, o) {
|
|
11211
|
+
const grace = o.grace ?? "24h";
|
|
11212
|
+
const { data } = await api(ctx, "GET", "/v1/keys");
|
|
11213
|
+
let keys;
|
|
11214
|
+
if (o.all)
|
|
11215
|
+
keys = data.filter((k) => k.status === "active");
|
|
11216
|
+
else {
|
|
11217
|
+
const k = data.find((x) => x.id === o.id);
|
|
11218
|
+
if (!k)
|
|
11219
|
+
throw new CliError("key_not_found", `No key ${o.id} on this account.`, "See `keys list`.");
|
|
11220
|
+
keys = [k];
|
|
11221
|
+
}
|
|
11222
|
+
const rotated = [];
|
|
11223
|
+
const skipped = [];
|
|
11224
|
+
const failed = [];
|
|
11225
|
+
const next = new Set;
|
|
11226
|
+
for (const k of keys) {
|
|
11227
|
+
const found = fromLivesIn(k.lives_in);
|
|
11228
|
+
if (!found) {
|
|
11229
|
+
const tool = /^tool:([a-z0-9-]+):/.exec(k.lives_in ?? "")?.[1];
|
|
11230
|
+
skipped.push({
|
|
11231
|
+
name: k.name,
|
|
11232
|
+
key_id: k.id,
|
|
11233
|
+
lives_in: k.lives_in ?? null,
|
|
11234
|
+
reason: !k.lives_in ? "no lives_in (not put anywhere by keys put)" : tool ? `a tool's config: run \`connect ${tool}\` again on that machine, then revoke this key` : k.lives_in.startsWith("env:") ? "a folder's ./.env (a project key from init, login or use): rotate it from that folder with `keys rotate`" : "lives_in isn't a destination keys put knows"
|
|
11235
|
+
});
|
|
11236
|
+
continue;
|
|
11237
|
+
}
|
|
11238
|
+
if (k.status !== "active") {
|
|
11239
|
+
skipped.push({ name: k.name, key_id: k.id, lives_in: k.lives_in ?? null, reason: `key is ${k.status}` });
|
|
11240
|
+
continue;
|
|
11241
|
+
}
|
|
11242
|
+
let r;
|
|
11243
|
+
try {
|
|
11244
|
+
r = await api(ctx, "POST", `/v1/keys/${encodeURIComponent(k.id)}/rotate`, { body: { grace } });
|
|
11245
|
+
} catch (e) {
|
|
11246
|
+
failed.push({ name: k.name, key_id: k.id, error: errorOf(e) });
|
|
11247
|
+
continue;
|
|
11248
|
+
}
|
|
11249
|
+
if (!r.api_key) {
|
|
11250
|
+
failed.push({ name: k.name, key_id: r.id, error: errorOf(null) });
|
|
11251
|
+
continue;
|
|
11252
|
+
}
|
|
11253
|
+
const stops = { old_key_stops: `in ${grace}`, old_key_expires_at: r.rotated_from?.expires_at ?? "" };
|
|
11254
|
+
try {
|
|
11255
|
+
const pushed = await found.dest.put(found.target, r.api_key, io(ctx), "replace");
|
|
11256
|
+
rotated.push({ name: r.name, key_id: r.id, pushed, ...stops });
|
|
11257
|
+
const n = found.dest.next?.(found.target);
|
|
11258
|
+
if (n)
|
|
11259
|
+
next.add(n);
|
|
11260
|
+
} catch (e) {
|
|
11261
|
+
const err = errorOf(e);
|
|
11262
|
+
failed.push({
|
|
11263
|
+
name: r.name,
|
|
11264
|
+
key_id: r.id,
|
|
11265
|
+
...stops,
|
|
11266
|
+
error: {
|
|
11267
|
+
...err,
|
|
11268
|
+
hint: `${err.hint ?? ""} The old key keeps working ${stops.old_key_stops}. Fix that, then run \`keys rotate ${r.id} --push --grace 0\`.`.trim()
|
|
11269
|
+
}
|
|
11270
|
+
});
|
|
11271
|
+
}
|
|
11272
|
+
}
|
|
11273
|
+
return {
|
|
11274
|
+
rotated,
|
|
11275
|
+
skipped,
|
|
11276
|
+
...failed.length ? { failed } : {},
|
|
11277
|
+
...next.size ? { next: [...next] } : {}
|
|
11278
|
+
};
|
|
11279
|
+
}
|
|
11280
|
+
function errorOf(e) {
|
|
11281
|
+
if (e instanceof CliError)
|
|
11282
|
+
return { code: e.code, message: e.message, hint: e.hint ?? null };
|
|
11283
|
+
return { code: "internal_error", message: "Unexpected error.", hint: "Retry; if it persists, report it." };
|
|
11284
|
+
}
|
|
11285
|
+
var CONNECT_TOOLS = ["opencode", "aider", "continue", "cursor"];
|
|
11286
|
+
function machineName(raw = hostname2()) {
|
|
11287
|
+
const n = raw.replace(/\.(local|lan|home)$/i, "").replace(/[^A-Za-z0-9.-]/g, "-").slice(0, 40);
|
|
11288
|
+
return n || "this-machine";
|
|
11289
|
+
}
|
|
11290
|
+
async function liveChatModels(ctx) {
|
|
11291
|
+
const { region } = resolveKey(ctx);
|
|
11292
|
+
const res = await api(ctx, "GET", "/v1/models?modality=chat", {
|
|
11293
|
+
auth: false,
|
|
11294
|
+
region
|
|
11295
|
+
}).catch(() => ({ data: [] }));
|
|
11296
|
+
return (res.data ?? []).filter((m) => typeof m.id === "string" && m.live_in_region !== false && (m.modality ?? "chat") === "chat").map((m) => ({ id: m.id, context: typeof m.context === "number" ? m.context : null }));
|
|
11297
|
+
}
|
|
11298
|
+
function writePrivate2(path, text) {
|
|
11299
|
+
mkdirSync3(dirname2(path), { recursive: true, mode: 448 });
|
|
11300
|
+
writeFileSync4(path, text, { mode: 384 });
|
|
11301
|
+
chmodSync4(path, 384);
|
|
11302
|
+
}
|
|
11303
|
+
function readJsonObject(path, what) {
|
|
11304
|
+
if (!existsSync8(path))
|
|
11305
|
+
return {};
|
|
11306
|
+
try {
|
|
11307
|
+
const v = JSON.parse(readFileSync10(path, "utf8"));
|
|
11308
|
+
if (v && typeof v === "object" && !Array.isArray(v))
|
|
11309
|
+
return v;
|
|
11310
|
+
} catch {}
|
|
11311
|
+
throw new CliError("config_unreadable", `${what} isn't plain JSON (comments or a syntax error). Nothing was changed.`, `Fix or move ${path}, then run connect again.`);
|
|
11312
|
+
}
|
|
11313
|
+
var PROVIDER_ID = "scaledthought";
|
|
11314
|
+
async function connect(ctx, tool, o = {}) {
|
|
11315
|
+
if (!CONNECT_TOOLS.includes(tool))
|
|
11316
|
+
throw new CliError("unknown_tool", `Unknown tool "${tool}".`, `Use one of: ${CONNECT_TOOLS.join(", ")}.`);
|
|
11317
|
+
const { region } = resolveKey(ctx);
|
|
11318
|
+
const baseUrl = inferenceUrl(region, ctx.env);
|
|
11319
|
+
const models = await liveChatModels(ctx);
|
|
11320
|
+
const model = o.model ?? models[0]?.id ?? "auto";
|
|
11321
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(model))
|
|
11322
|
+
throw new CliError("bad_argument", "--model isn't a model id.", "Pick one from `models --live`.");
|
|
11323
|
+
const host = machineName();
|
|
11324
|
+
const keyName = `${tool}: ${host}`;
|
|
11325
|
+
const livesIn = `tool:${tool}:${host}`;
|
|
11326
|
+
const t = tool;
|
|
11327
|
+
if (t === "cursor" || t === "continue")
|
|
11328
|
+
return instructions(t, baseUrl, model, models, keyName);
|
|
11329
|
+
if (t === "aider") {
|
|
11330
|
+
const existing = readDotEnv(ctx.cwd);
|
|
11331
|
+
if (existing.OPENAI_API_KEY && !KEY_RE.test(existing.OPENAI_API_KEY))
|
|
11332
|
+
throw new CliError("env_conflict", "./.env already has an OPENAI_API_KEY that isn't a ScaledThought key. Nothing was changed.", "Aider reads OPENAI_API_KEY from .env, so this would replace it. Run connect aider in another directory, or remove that line first.");
|
|
11333
|
+
const k = await mintChild(ctx, { name: keyName, livesIn, budgetUsd: o.budgetUsd });
|
|
11334
|
+
try {
|
|
11335
|
+
writeEnv(ctx.cwd, { OPENAI_API_BASE: baseUrl, OPENAI_API_KEY: k.api_key, AIDER_MODEL: `openai/${model}` });
|
|
11336
|
+
ensureGitignored(ctx.cwd);
|
|
11337
|
+
} catch (e) {
|
|
11338
|
+
return failAfterMint(ctx, k.id, e);
|
|
11339
|
+
}
|
|
11340
|
+
return {
|
|
11341
|
+
tool,
|
|
11342
|
+
key_name: k.name,
|
|
11343
|
+
key_prefix: k.prefix,
|
|
11344
|
+
lives_in: livesIn,
|
|
11345
|
+
daily_budget_usd: k.daily_budget_usd,
|
|
11346
|
+
configured: ".env (OPENAI_API_BASE, OPENAI_API_KEY, AIDER_MODEL), mode 0600, git-ignored",
|
|
11347
|
+
model: `openai/${model}`,
|
|
11348
|
+
models: ["auto", ...models.map((m) => m.id)],
|
|
11349
|
+
next: "Run `aider` in this directory. Aider may warn that it doesn't know the model's context window; that's harmless."
|
|
11350
|
+
};
|
|
11351
|
+
}
|
|
11352
|
+
const dataHome = ctx.env.XDG_DATA_HOME || join9(ctx.env.HOME || homedir(), ".local", "share");
|
|
11353
|
+
const authPath = join9(dataHome, "opencode", "auth.json");
|
|
11354
|
+
const configPath = join9(ctx.cwd, "opencode.json");
|
|
11355
|
+
const config = readJsonObject(configPath, "opencode.json");
|
|
11356
|
+
const auth = readJsonObject(authPath, authPath);
|
|
11357
|
+
const providers = config.provider && typeof config.provider === "object" ? config.provider : {};
|
|
11358
|
+
const modelEntries = { auto: { name: "auto (ScaledThought picks per request)" } };
|
|
11359
|
+
for (const m of models)
|
|
11360
|
+
modelEntries[m.id] = {
|
|
11361
|
+
name: m.id,
|
|
11362
|
+
...m.context ? { limit: { context: m.context, output: Math.min(32768, Math.floor(m.context / 2)) } } : {}
|
|
11363
|
+
};
|
|
11364
|
+
if (!modelEntries[model])
|
|
11365
|
+
modelEntries[model] = { name: model };
|
|
11366
|
+
const k = await mintChild(ctx, { name: keyName, livesIn, budgetUsd: o.budgetUsd });
|
|
11367
|
+
try {
|
|
11368
|
+
const next = {
|
|
11369
|
+
...existsSync8(configPath) ? {} : { $schema: "https://opencode.ai/config.json" },
|
|
11370
|
+
...config,
|
|
11371
|
+
provider: {
|
|
11372
|
+
...providers,
|
|
11373
|
+
[PROVIDER_ID]: {
|
|
11374
|
+
npm: "@ai-sdk/openai-compatible",
|
|
11375
|
+
name: "ScaledThought",
|
|
11376
|
+
options: { baseURL: baseUrl },
|
|
11377
|
+
models: modelEntries
|
|
11378
|
+
}
|
|
11379
|
+
},
|
|
11380
|
+
...config.model ? {} : { model: `${PROVIDER_ID}/${model}` }
|
|
11381
|
+
};
|
|
11382
|
+
writePrivate2(configPath, `${JSON.stringify(next, null, 2)}
|
|
11383
|
+
`);
|
|
11384
|
+
writePrivate2(authPath, `${JSON.stringify({ ...auth, [PROVIDER_ID]: { type: "api", key: k.api_key } }, null, 2)}
|
|
11385
|
+
`);
|
|
11386
|
+
} catch (e) {
|
|
11387
|
+
return failAfterMint(ctx, k.id, e);
|
|
11388
|
+
}
|
|
11389
|
+
return {
|
|
11390
|
+
tool,
|
|
11391
|
+
key_name: k.name,
|
|
11392
|
+
key_prefix: k.prefix,
|
|
11393
|
+
lives_in: livesIn,
|
|
11394
|
+
daily_budget_usd: k.daily_budget_usd,
|
|
11395
|
+
configured: `opencode.json (provider ${PROVIDER_ID}, base URL ${baseUrl}) + ${authPath} (key, mode 0600)`,
|
|
11396
|
+
model: `${PROVIDER_ID}/${model}`,
|
|
11397
|
+
models: Object.keys(modelEntries),
|
|
11398
|
+
restart_needed: true,
|
|
11399
|
+
next: `Restart OpenCode in this directory and pick a ${PROVIDER_ID}/… model (or ${PROVIDER_ID}/auto).`
|
|
11400
|
+
};
|
|
11401
|
+
}
|
|
11402
|
+
function instructions(tool, baseUrl, model, models, name) {
|
|
11403
|
+
const ids = ["auto", ...models.map((m) => m.id)];
|
|
11404
|
+
if (tool === "cursor")
|
|
11405
|
+
return {
|
|
11406
|
+
tool,
|
|
11407
|
+
configured: false,
|
|
11408
|
+
key_minted: false,
|
|
11409
|
+
why: "Cursor keeps its OpenAI key in its own settings store, which other programs can't write safely. A human sets it once.",
|
|
11410
|
+
steps_for_human: [
|
|
11411
|
+
`Cursor Settings → Models → API Keys: turn on "Override OpenAI Base URL" and enter ${baseUrl}`,
|
|
11412
|
+
`In your own terminal (not through the agent) run: ${NPX} keys create --child --name "${name}" and paste the key it prints into "OpenAI API Key", then Verify.`,
|
|
11413
|
+
`Add model names under Models: ${ids.join(", ")}`
|
|
11414
|
+
],
|
|
11415
|
+
base_url: baseUrl,
|
|
11416
|
+
models: ids,
|
|
11417
|
+
restart_needed: false
|
|
11418
|
+
};
|
|
11419
|
+
const block = [
|
|
11420
|
+
"models:",
|
|
11421
|
+
` - name: ScaledThought ${model}`,
|
|
11422
|
+
" provider: openai",
|
|
11423
|
+
` model: ${model}`,
|
|
11424
|
+
` apiBase: ${baseUrl}`,
|
|
11425
|
+
` apiKey: \${{ secrets.ST_API_KEY }}`,
|
|
11426
|
+
" defaultCompletionOptions:",
|
|
11427
|
+
" maxTokens: 32768",
|
|
11428
|
+
" roles: [chat, edit, apply]"
|
|
11429
|
+
].join(`
|
|
11430
|
+
`);
|
|
11431
|
+
return {
|
|
11432
|
+
tool,
|
|
11433
|
+
configured: false,
|
|
11434
|
+
key_minted: false,
|
|
11435
|
+
why: "Continue's config.yaml is hand-edited YAML; this CLI won't rewrite it. The block below holds no secret: Continue reads ST_API_KEY from this workspace's .env.",
|
|
11436
|
+
steps_for_human: [
|
|
11437
|
+
"Open ~/.continue/config.yaml (Continue: Open configuration) and add this entry under models:",
|
|
11438
|
+
"Reload Continue and pick the ScaledThought model."
|
|
11439
|
+
],
|
|
11440
|
+
config_yaml: block,
|
|
11441
|
+
base_url: baseUrl,
|
|
11442
|
+
models: ids,
|
|
11443
|
+
restart_needed: false
|
|
11444
|
+
};
|
|
11445
|
+
}
|
|
11446
|
+
function putFlagsFrom(args) {
|
|
11447
|
+
const out = {};
|
|
11448
|
+
for (const f of PUT_FLAGS)
|
|
11449
|
+
if (typeof args[f] === "string")
|
|
11450
|
+
out[f] = args[f];
|
|
11451
|
+
return out;
|
|
11452
|
+
}
|
|
11453
|
+
|
|
10003
11454
|
// src/realtime.ts
|
|
10004
|
-
import { readFileSync as
|
|
11455
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
10005
11456
|
import { resolve as resolve4 } from "node:path";
|
|
10006
11457
|
var SAMPLE_RATE = 24000;
|
|
10007
11458
|
var MAX_INPUT_SECONDS = 300;
|
|
@@ -10066,7 +11517,7 @@ async function realtime(ctx, o) {
|
|
|
10066
11517
|
throw new CliError("unsupported_runtime", "This command needs a WebSocket client (Node.js 22 or newer).", "Upgrade Node.js to 22+.");
|
|
10067
11518
|
let pcm;
|
|
10068
11519
|
try {
|
|
10069
|
-
pcm = readWav(
|
|
11520
|
+
pcm = readWav(readFileSync11(resolve4(ctx.cwd, o.input)));
|
|
10070
11521
|
} catch (e) {
|
|
10071
11522
|
if (e instanceof CliError)
|
|
10072
11523
|
throw e;
|
|
@@ -10158,7 +11609,7 @@ async function realtime(ctx, o) {
|
|
|
10158
11609
|
});
|
|
10159
11610
|
const audio = Buffer.concat(out);
|
|
10160
11611
|
const path = resolve4(ctx.cwd, o.output);
|
|
10161
|
-
|
|
11612
|
+
writeFileSync5(path, wavOf(audio));
|
|
10162
11613
|
return {
|
|
10163
11614
|
model: o.model,
|
|
10164
11615
|
region,
|
|
@@ -10189,7 +11640,7 @@ function out(data, args) {
|
|
|
10189
11640
|
process.stdout.write(`${JSON.stringify(data, null, wantsJson(args) ? 0 : 2)}
|
|
10190
11641
|
`);
|
|
10191
11642
|
}
|
|
10192
|
-
var
|
|
11643
|
+
var run2 = async (fn, args) => out(await fn(), args);
|
|
10193
11644
|
function region(v) {
|
|
10194
11645
|
if (v === undefined)
|
|
10195
11646
|
return;
|
|
@@ -10208,7 +11659,7 @@ function num(flag, v) {
|
|
|
10208
11659
|
var init2 = defineCommand({
|
|
10209
11660
|
meta: {
|
|
10210
11661
|
name: "init",
|
|
10211
|
-
description: "Create an account, save ST_API_KEY and ST_BASE_URL to ./.env, list live models and the next steps (a new account has no credits: top up first). Idempotent. " + "Creating an account accepts
|
|
11662
|
+
description: "Create an account, save ST_API_KEY and ST_BASE_URL to ./.env, list live models and the next steps (a new account has no credits: top up first). Idempotent. " + "Creating an account accepts nothing for your human: they accept the Terms themselves when they buy credits."
|
|
10212
11663
|
},
|
|
10213
11664
|
args: {
|
|
10214
11665
|
...jsonFlag,
|
|
@@ -10222,16 +11673,90 @@ var init2 = defineCommand({
|
|
|
10222
11673
|
"ad-click": {
|
|
10223
11674
|
type: "string",
|
|
10224
11675
|
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`"
|
|
11676
|
+
},
|
|
11677
|
+
account: {
|
|
11678
|
+
type: "string",
|
|
11679
|
+
description: "Use this account saved on this machine (id or name, from choose_account) with a new project key, instead of creating one"
|
|
11680
|
+
},
|
|
11681
|
+
"new-account": {
|
|
11682
|
+
type: "boolean",
|
|
11683
|
+
description: "Create a new account even when one is saved on this machine (separate billing)"
|
|
11684
|
+
},
|
|
11685
|
+
save: {
|
|
11686
|
+
type: "boolean",
|
|
11687
|
+
default: true,
|
|
11688
|
+
description: "Save the account's key in this machine's credential store so other folders reuse it (--no-save to opt out)"
|
|
10225
11689
|
}
|
|
10226
11690
|
},
|
|
10227
|
-
run: ({ args }) =>
|
|
11691
|
+
run: ({ args }) => run2(() => init(makeCtx(), {
|
|
10228
11692
|
region: region(args.region),
|
|
10229
11693
|
name: args.name,
|
|
10230
11694
|
email: args.email,
|
|
10231
11695
|
source: args.source,
|
|
10232
|
-
adClick: args["ad-click"]
|
|
11696
|
+
adClick: args["ad-click"],
|
|
11697
|
+
account: args.account,
|
|
11698
|
+
newAccount: args["new-account"],
|
|
11699
|
+
noSave: args.save === false
|
|
10233
11700
|
}), args)
|
|
10234
11701
|
});
|
|
11702
|
+
var login2 = defineCommand({
|
|
11703
|
+
meta: {
|
|
11704
|
+
name: "login",
|
|
11705
|
+
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"
|
|
11706
|
+
},
|
|
11707
|
+
args: {
|
|
11708
|
+
...jsonFlag,
|
|
11709
|
+
region: { type: "string", description: "The account's region: us (default) or ca" },
|
|
11710
|
+
open: { type: "boolean", default: true, description: "Open the connect page (--no-open only prints it)" },
|
|
11711
|
+
label: { type: "string", description: 'How this agent is named to the human (default "<tool> on <hostname>")' },
|
|
11712
|
+
wait: {
|
|
11713
|
+
type: "boolean",
|
|
11714
|
+
description: "Wait (about 90 s) for your human to approve the code, then save the key. Run again on authorization_pending"
|
|
11715
|
+
},
|
|
11716
|
+
code: {
|
|
11717
|
+
type: "string",
|
|
11718
|
+
description: "A connect line's code from the dashboard (stl_…): already approved, works once. `--code -` reads it from stdin"
|
|
11719
|
+
},
|
|
11720
|
+
force: { type: "boolean", description: "Replace the key already in ./.env" },
|
|
11721
|
+
save: {
|
|
11722
|
+
type: "boolean",
|
|
11723
|
+
default: true,
|
|
11724
|
+
description: "Save the key in this machine's credential store (--no-save to opt out)"
|
|
11725
|
+
},
|
|
11726
|
+
"timeout-s": { type: "string", description: "With --wait: seconds to wait (default 90, at most 600)" }
|
|
11727
|
+
},
|
|
11728
|
+
run: async ({ args }) => {
|
|
11729
|
+
const ctx = makeCtx();
|
|
11730
|
+
const noSave = args.save === false;
|
|
11731
|
+
const t = args["timeout-s"] === undefined ? undefined : num("timeout-s", args["timeout-s"]);
|
|
11732
|
+
if (t !== undefined && t > 600)
|
|
11733
|
+
throw new CliError("bad_argument", `--timeout-s must be at most 600, got ${t}.`, "Run login --wait again.");
|
|
11734
|
+
await run2(async () => args.code ? loginWithCode(ctx, {
|
|
11735
|
+
code: args.code === "-" ? await readCodeFrom(process.stdin) : args.code,
|
|
11736
|
+
region: region(args.region),
|
|
11737
|
+
label: args.label,
|
|
11738
|
+
force: args.force,
|
|
11739
|
+
noSave
|
|
11740
|
+
}) : args.wait ? loginWait(ctx, { timeoutS: t, noSave, force: args.force }) : login(ctx, {
|
|
11741
|
+
region: region(args.region),
|
|
11742
|
+
label: args.label,
|
|
11743
|
+
noOpen: args.open === false,
|
|
11744
|
+
force: args.force,
|
|
11745
|
+
noSave
|
|
11746
|
+
}), args);
|
|
11747
|
+
}
|
|
11748
|
+
});
|
|
11749
|
+
var use = defineCommand({
|
|
11750
|
+
meta: {
|
|
11751
|
+
name: "use",
|
|
11752
|
+
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"
|
|
11753
|
+
},
|
|
11754
|
+
args: {
|
|
11755
|
+
...jsonFlag,
|
|
11756
|
+
account: { type: "positional", required: true, description: "The account's name or id" }
|
|
11757
|
+
},
|
|
11758
|
+
run: ({ args }) => run2(() => useAccount(makeCtx(), { target: args.account }), args)
|
|
11759
|
+
});
|
|
10235
11760
|
var models = defineCommand({
|
|
10236
11761
|
meta: { name: "models", description: "List models with prices and benchmarks, newest first; or show one" },
|
|
10237
11762
|
args: {
|
|
@@ -10250,7 +11775,7 @@ var models = defineCommand({
|
|
|
10250
11775
|
},
|
|
10251
11776
|
search: { type: "string", description: "Match id, name or lab" }
|
|
10252
11777
|
},
|
|
10253
|
-
run: ({ args }) =>
|
|
11778
|
+
run: ({ args }) => run2(() => {
|
|
10254
11779
|
const r = region(args.region);
|
|
10255
11780
|
return args.id ? showModel(makeCtx(), args.id, r) : listModels(makeCtx(), {
|
|
10256
11781
|
live: args.live,
|
|
@@ -10268,7 +11793,7 @@ var account2 = defineCommand({
|
|
|
10268
11793
|
run: ({ args, rawArgs }) => {
|
|
10269
11794
|
if (rawArgs.some((a) => ACCOUNT_SUBCOMMANDS.has(a)))
|
|
10270
11795
|
return;
|
|
10271
|
-
return
|
|
11796
|
+
return run2(() => account(makeCtx()), args);
|
|
10272
11797
|
},
|
|
10273
11798
|
subCommands: {
|
|
10274
11799
|
delete: defineCommand({
|
|
@@ -10280,7 +11805,7 @@ var account2 = defineCommand({
|
|
|
10280
11805
|
...jsonFlag,
|
|
10281
11806
|
confirm: { type: "string", required: true, description: "The account id (acct_...), to confirm" }
|
|
10282
11807
|
},
|
|
10283
|
-
run: ({ args }) =>
|
|
11808
|
+
run: ({ args }) => run2(() => accountDelete(makeCtx(), { confirm: args.confirm }), args)
|
|
10284
11809
|
}),
|
|
10285
11810
|
"ad-measurement": defineCommand({
|
|
10286
11811
|
meta: {
|
|
@@ -10291,7 +11816,7 @@ var account2 = defineCommand({
|
|
|
10291
11816
|
...jsonFlag,
|
|
10292
11817
|
state: { type: "positional", required: true, description: "off or on" }
|
|
10293
11818
|
},
|
|
10294
|
-
run: ({ args }) =>
|
|
11819
|
+
run: ({ args }) => run2(() => accountAdMeasurement(makeCtx(), args.state), args)
|
|
10295
11820
|
})
|
|
10296
11821
|
}
|
|
10297
11822
|
});
|
|
@@ -10301,7 +11826,7 @@ var keys = defineCommand({
|
|
|
10301
11826
|
list: defineCommand({
|
|
10302
11827
|
meta: { name: "list", description: "Keys on this account (prefixes only)" },
|
|
10303
11828
|
args: jsonFlag,
|
|
10304
|
-
run: ({ args }) =>
|
|
11829
|
+
run: ({ args }) => run2(() => keysList(makeCtx()), args)
|
|
10305
11830
|
}),
|
|
10306
11831
|
create: defineCommand({
|
|
10307
11832
|
meta: { name: "create", description: "Create a key; the full key is printed once" },
|
|
@@ -10319,7 +11844,7 @@ var keys = defineCommand({
|
|
|
10319
11844
|
description: "Sub-agent key: budget, scopes and expiry within your key's; revoked when your key is"
|
|
10320
11845
|
}
|
|
10321
11846
|
},
|
|
10322
|
-
run: ({ args }) =>
|
|
11847
|
+
run: ({ args }) => run2(() => keysCreate(makeCtx(), {
|
|
10323
11848
|
name: args.name,
|
|
10324
11849
|
budgetUsd: num("budget-usd", args["budget-usd"]),
|
|
10325
11850
|
expiresIn: args["expires-in"],
|
|
@@ -10327,23 +11852,89 @@ var keys = defineCommand({
|
|
|
10327
11852
|
child: args.child
|
|
10328
11853
|
}), args)
|
|
10329
11854
|
}),
|
|
11855
|
+
put: defineCommand({
|
|
11856
|
+
meta: {
|
|
11857
|
+
name: "put",
|
|
11858
|
+
description: "Put a new child key straight into a platform's secret store as ST_API_KEY, through its own CLI on stdin (never printed). Records where it lives"
|
|
11859
|
+
},
|
|
11860
|
+
args: {
|
|
11861
|
+
...jsonFlag,
|
|
11862
|
+
destination: {
|
|
11863
|
+
type: "positional",
|
|
11864
|
+
required: true,
|
|
11865
|
+
description: `Where: ${Object.keys(DESTINATIONS).join(", ")}`
|
|
11866
|
+
},
|
|
11867
|
+
name: { type: "string", description: "Key name (default: named after the destination)" },
|
|
11868
|
+
"budget-usd": { type: "string", description: "Hard daily budget in USD (default: none beyond your key's)" },
|
|
11869
|
+
"expires-in": { type: "string", description: "Key lifetime, e.g. 30d (max 365d)" },
|
|
11870
|
+
scopes: {
|
|
11871
|
+
type: "string",
|
|
11872
|
+
description: "Comma-separated scopes, a subset of your key's. Default inference,read"
|
|
11873
|
+
},
|
|
11874
|
+
repo: { type: "string", description: "github: owner/name (default: this directory's repo)" },
|
|
11875
|
+
app: { type: "string", description: "github: actions|codespaces|dependabot (default actions); fly: app name" },
|
|
11876
|
+
env: {
|
|
11877
|
+
type: "string",
|
|
11878
|
+
description: "github: deployment environment; vercel: production|preview|development (default production); cloudflare: Wrangler env"
|
|
11879
|
+
},
|
|
11880
|
+
project: { type: "string", description: "vercel (must match the linked project), gcp, doppler: project" },
|
|
11881
|
+
worker: { type: "string", description: "cloudflare: Worker name (default: wrangler.toml's name)" },
|
|
11882
|
+
secret: {
|
|
11883
|
+
type: "string",
|
|
11884
|
+
description: "gcp (default st-api-key), aws (default scaledthought/api-key), k8s (default scaledthought): secret name"
|
|
11885
|
+
},
|
|
11886
|
+
"aws-region": { type: "string", description: "aws: region (default: AWS_REGION or the CLI's)" },
|
|
11887
|
+
locations: {
|
|
11888
|
+
type: "string",
|
|
11889
|
+
description: "gcp: user-managed replication locations, e.g. northamerica-northeast1"
|
|
11890
|
+
},
|
|
11891
|
+
namespace: { type: "string", description: "k8s: namespace (default: the context's)" },
|
|
11892
|
+
context: { type: "string", description: "k8s: kubectl context (default: current)" },
|
|
11893
|
+
config: { type: "string", description: "doppler: config, e.g. prd (default: this directory's)" }
|
|
11894
|
+
},
|
|
11895
|
+
run: ({ args }) => run2(() => keysPut(makeCtx(), args.destination, {
|
|
11896
|
+
name: args.name,
|
|
11897
|
+
budgetUsd: num("budget-usd", args["budget-usd"]),
|
|
11898
|
+
expiresIn: args["expires-in"],
|
|
11899
|
+
scopes: args.scopes?.split(",").map((x) => x.trim()).filter(Boolean),
|
|
11900
|
+
flags: putFlagsFrom(args)
|
|
11901
|
+
}), args)
|
|
11902
|
+
}),
|
|
10330
11903
|
rotate: defineCommand({
|
|
10331
11904
|
meta: {
|
|
10332
11905
|
name: "rotate",
|
|
10333
|
-
description: "Replace a key: prints the new key once; the old key keeps working for --grace (default 24h, 0 to 7d)"
|
|
11906
|
+
description: "Replace a key: prints the new key once; the old key keeps working for --grace (default 24h, 0 to 7d). With --push, puts the new key where the old one lives instead of printing it"
|
|
10334
11907
|
},
|
|
10335
11908
|
args: {
|
|
10336
11909
|
...jsonFlag,
|
|
10337
|
-
id: { type: "positional", required:
|
|
11910
|
+
id: { type: "positional", required: false, description: "Key id (key_…); or --all with --push" },
|
|
10338
11911
|
grace: { type: "string", description: "How long the old key keeps working: 0 to 7d (default 24h)" },
|
|
10339
|
-
"expires-in": { type: "string", description: "New key's lifetime (default: the old key's)" }
|
|
11912
|
+
"expires-in": { type: "string", description: "New key's lifetime (default: the old key's)" },
|
|
11913
|
+
push: {
|
|
11914
|
+
type: "boolean",
|
|
11915
|
+
description: "Put each new key where the old one lives (its lives_in, from keys put); prints no keys"
|
|
11916
|
+
},
|
|
11917
|
+
all: { type: "boolean", description: "With --push: every active key that has a lives_in keys put can reach" }
|
|
10340
11918
|
},
|
|
10341
|
-
run: ({ args }) =>
|
|
11919
|
+
run: ({ args }) => run2(() => {
|
|
11920
|
+
if (args.all && !args.push)
|
|
11921
|
+
throw new CliError("usage_error", "--all needs --push (rotating every key never prints them).");
|
|
11922
|
+
if (args.all && args.id)
|
|
11923
|
+
throw new CliError("usage_error", "Pass a key id or --all, not both.");
|
|
11924
|
+
if (!args.all && !args.id)
|
|
11925
|
+
throw new CliError("usage_error", "Pass a key id (or --all --push).");
|
|
11926
|
+
if (args.push) {
|
|
11927
|
+
if (args["expires-in"])
|
|
11928
|
+
throw new CliError("usage_error", "--expires-in can't be combined with --push.");
|
|
11929
|
+
return keysRotatePush(makeCtx(), { id: args.id, all: args.all, grace: args.grace });
|
|
11930
|
+
}
|
|
11931
|
+
return keysRotate(makeCtx(), args.id, { grace: args.grace, expiresIn: args["expires-in"] });
|
|
11932
|
+
}, args)
|
|
10342
11933
|
}),
|
|
10343
11934
|
revoke: defineCommand({
|
|
10344
11935
|
meta: { name: "revoke", description: "Revoke a key by id (and its sub-agent keys)" },
|
|
10345
11936
|
args: { ...jsonFlag, id: { type: "positional", required: true, description: "Key id (key_…)" } },
|
|
10346
|
-
run: ({ args }) =>
|
|
11937
|
+
run: ({ args }) => run2(() => keysRevoke(makeCtx(), args.id), args)
|
|
10347
11938
|
}),
|
|
10348
11939
|
"revoke-self": defineCommand({
|
|
10349
11940
|
meta: {
|
|
@@ -10351,7 +11942,7 @@ var keys = defineCommand({
|
|
|
10351
11942
|
description: "Revoke the key you are using now (e.g. if it may have leaked). Irreversible"
|
|
10352
11943
|
},
|
|
10353
11944
|
args: jsonFlag,
|
|
10354
|
-
run: ({ args }) =>
|
|
11945
|
+
run: ({ args }) => run2(() => keysRevokeSelf(makeCtx()), args)
|
|
10355
11946
|
})
|
|
10356
11947
|
}
|
|
10357
11948
|
});
|
|
@@ -10362,7 +11953,7 @@ var usage3 = defineCommand({
|
|
|
10362
11953
|
days: { type: "string", default: "7", description: "Look-back window, 1-90" },
|
|
10363
11954
|
by: { type: "string", default: "model", description: "model|day|key|modality" }
|
|
10364
11955
|
},
|
|
10365
|
-
run: ({ args }) =>
|
|
11956
|
+
run: ({ args }) => run2(() => usage(makeCtx(), { days: num("days", args.days) ?? 7, by: args.by }), args)
|
|
10366
11957
|
});
|
|
10367
11958
|
var chat2 = defineCommand({
|
|
10368
11959
|
meta: { name: "chat", description: "Send one message. Use -m auto to let the router pick" },
|
|
@@ -10373,7 +11964,7 @@ var chat2 = defineCommand({
|
|
|
10373
11964
|
},
|
|
10374
11965
|
run: async ({ args }) => {
|
|
10375
11966
|
const human = !wantsJson(args);
|
|
10376
|
-
await
|
|
11967
|
+
await run2(async () => {
|
|
10377
11968
|
const r = await chat(makeCtx(), {
|
|
10378
11969
|
model: args.model,
|
|
10379
11970
|
prompt: args.prompt,
|
|
@@ -10436,7 +12027,7 @@ var speak2 = defineCommand({
|
|
|
10436
12027
|
speed: { type: "string", description: "0.25 to 4 (default 1)" },
|
|
10437
12028
|
instructions: { type: "string", description: "Delivery instructions for models that take them, optional" }
|
|
10438
12029
|
},
|
|
10439
|
-
run: ({ args }) =>
|
|
12030
|
+
run: ({ args }) => run2(() => speak(makeCtx(), {
|
|
10440
12031
|
text: args.text,
|
|
10441
12032
|
output: args.output,
|
|
10442
12033
|
model: args.model,
|
|
@@ -10459,7 +12050,7 @@ var decideCmd = defineCommand({
|
|
|
10459
12050
|
question: { type: "string", alias: "q", description: "id:type:instructions[:criteria], repeatable" },
|
|
10460
12051
|
file: { type: "string", description: "A JSON request: {state, questions, model?}" }
|
|
10461
12052
|
},
|
|
10462
|
-
run: ({ args }) =>
|
|
12053
|
+
run: ({ args }) => run2(() => {
|
|
10463
12054
|
const ctx = makeCtx();
|
|
10464
12055
|
const raw = currentArgv;
|
|
10465
12056
|
const questions = [];
|
|
@@ -10492,7 +12083,7 @@ var realtime2 = defineCommand({
|
|
|
10492
12083
|
instructions: { type: "string", description: "System instructions for the session (optional)" },
|
|
10493
12084
|
timeout: { type: "string", description: "Seconds to wait for the reply (default 120)" }
|
|
10494
12085
|
},
|
|
10495
|
-
run: ({ args }) =>
|
|
12086
|
+
run: ({ args }) => run2(async () => {
|
|
10496
12087
|
const t = num("timeout", args.timeout);
|
|
10497
12088
|
return realtime(makeCtx(), {
|
|
10498
12089
|
model: args.model,
|
|
@@ -10510,7 +12101,7 @@ function autoReloadArgs(args) {
|
|
|
10510
12101
|
if (below === undefined && amount === undefined && max === undefined)
|
|
10511
12102
|
return;
|
|
10512
12103
|
if (below === undefined || amount === undefined)
|
|
10513
|
-
throw new CliError("bad_argument", "Auto-reload needs both --auto-reload-below and --auto-reload-amount.", "e.g. --auto-reload-below
|
|
12104
|
+
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 20");
|
|
10514
12105
|
return { threshold_usd: below, amount_usd: amount, ...max !== undefined ? { monthly_limit_usd: max } : {} };
|
|
10515
12106
|
}
|
|
10516
12107
|
var billing = defineCommand({
|
|
@@ -10522,25 +12113,27 @@ var billing = defineCommand({
|
|
|
10522
12113
|
description: "Credit balance, auto-reload, pending top-ups (with their approval links), recent transactions"
|
|
10523
12114
|
},
|
|
10524
12115
|
args: jsonFlag,
|
|
10525
|
-
run: ({ args }) =>
|
|
12116
|
+
run: ({ args }) => run2(() => billingStatus(makeCtx()), args)
|
|
10526
12117
|
}),
|
|
10527
12118
|
topup: defineCommand({
|
|
10528
12119
|
meta: {
|
|
10529
12120
|
name: "topup",
|
|
10530
|
-
description: "Buy prepaid credits: opens the
|
|
12121
|
+
description: "Buy prepaid credits: opens the funding page in your human's browser (approval_url), where they confirm the amount, can turn on auto-reload (recommended) and pay; then run `billing wait`, which returns once they pay"
|
|
10531
12122
|
},
|
|
10532
12123
|
args: {
|
|
10533
12124
|
...jsonFlag,
|
|
10534
12125
|
"amount-usd": {
|
|
10535
12126
|
type: "string",
|
|
10536
|
-
|
|
10537
|
-
|
|
12127
|
+
description: "Credits to buy in USD (default 20; $20 to $2,000, at most $2,000 per 24 h). Your human can change it"
|
|
12128
|
+
},
|
|
12129
|
+
"auto-reload-below": {
|
|
12130
|
+
type: "string",
|
|
12131
|
+
description: "Suggest auto-reload (pre-selected on the page) when the balance drops below this (USD, at least 5)"
|
|
10538
12132
|
},
|
|
10539
|
-
"auto-reload-
|
|
10540
|
-
"auto-reload-amount": { type: "string", description: "Amount of each auto-reload (USD)" },
|
|
12133
|
+
"auto-reload-amount": { type: "string", description: "Amount of each suggested auto-reload (USD)" },
|
|
10541
12134
|
"auto-reload-monthly-max": {
|
|
10542
12135
|
type: "string",
|
|
10543
|
-
description: "
|
|
12136
|
+
description: "Optional: most auto-reload may charge per calendar month (USD; default: no monthly limit)"
|
|
10544
12137
|
},
|
|
10545
12138
|
open: {
|
|
10546
12139
|
type: "boolean",
|
|
@@ -10553,11 +12146,11 @@ var billing = defineCommand({
|
|
|
10553
12146
|
}
|
|
10554
12147
|
},
|
|
10555
12148
|
run: async ({ args }) => {
|
|
10556
|
-
const amountUsd = num("amount-usd", args["amount-usd"]);
|
|
12149
|
+
const amountUsd = num("amount-usd", args["amount-usd"]) ?? 20;
|
|
10557
12150
|
const autoReload = autoReloadArgs(args);
|
|
10558
12151
|
checkTopupLimits(amountUsd, autoReload);
|
|
10559
12152
|
const noOpen = args.open === false;
|
|
10560
|
-
await
|
|
12153
|
+
await run2(() => args.wait ? billingTopupAndWait(makeCtx(), { amountUsd, autoReload, noOpen, onPending: (t) => out(t, args) }) : billingTopup(makeCtx(), { amountUsd, autoReload, noOpen }), args);
|
|
10561
12154
|
}
|
|
10562
12155
|
}),
|
|
10563
12156
|
wait: defineCommand({
|
|
@@ -10577,23 +12170,50 @@ var billing = defineCommand({
|
|
|
10577
12170
|
const t = args["timeout-s"] === undefined ? undefined : num("timeout-s", args["timeout-s"]);
|
|
10578
12171
|
if (t !== undefined && t > 600)
|
|
10579
12172
|
throw new CliError("bad_argument", `--timeout-s must be at most 600, got ${t}.`, "Run wait again instead.");
|
|
10580
|
-
await
|
|
12173
|
+
await run2(() => billingWait(makeCtx(), { topupId: args.topup_id || undefined, timeoutS: t }), args);
|
|
10581
12174
|
}
|
|
10582
12175
|
}),
|
|
10583
12176
|
"auto-reload": defineCommand({
|
|
10584
|
-
meta: {
|
|
12177
|
+
meta: {
|
|
12178
|
+
name: "auto-reload",
|
|
12179
|
+
description: "Turn auto-reload on (a link your human approves; nothing is bought) or off (no approval)"
|
|
12180
|
+
},
|
|
10585
12181
|
subCommands: {
|
|
12182
|
+
setup: defineCommand({
|
|
12183
|
+
meta: {
|
|
12184
|
+
name: "setup",
|
|
12185
|
+
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`"
|
|
12186
|
+
},
|
|
12187
|
+
args: {
|
|
12188
|
+
...jsonFlag,
|
|
12189
|
+
below: {
|
|
12190
|
+
type: "string",
|
|
12191
|
+
description: "Reload when the balance drops below this (USD, default and minimum 5)"
|
|
12192
|
+
},
|
|
12193
|
+
amount: { type: "string", description: "Credits each reload adds (USD, default 20)" },
|
|
12194
|
+
open: {
|
|
12195
|
+
type: "boolean",
|
|
12196
|
+
default: true,
|
|
12197
|
+
description: "Open the page in the browser (--no-open only prints approval_url)"
|
|
12198
|
+
}
|
|
12199
|
+
},
|
|
12200
|
+
run: ({ args }) => run2(() => billingAutoReloadSetup(makeCtx(), {
|
|
12201
|
+
thresholdUsd: num("below", args.below),
|
|
12202
|
+
amountUsd: num("amount", args.amount),
|
|
12203
|
+
noOpen: args.open === false
|
|
12204
|
+
}), args)
|
|
12205
|
+
}),
|
|
10586
12206
|
off: defineCommand({
|
|
10587
12207
|
meta: { name: "off", description: "Disable auto-reload. No approval needed" },
|
|
10588
12208
|
args: jsonFlag,
|
|
10589
|
-
run: ({ args }) =>
|
|
12209
|
+
run: ({ args }) => run2(() => billingAutoReloadOff(makeCtx()), args)
|
|
10590
12210
|
})
|
|
10591
12211
|
}
|
|
10592
12212
|
}),
|
|
10593
12213
|
transactions: defineCommand({
|
|
10594
12214
|
meta: { name: "transactions", description: "Credit ledger: purchases, auto-reloads, usage, batches, refunds" },
|
|
10595
12215
|
args: { ...jsonFlag, cursor: { type: "string", description: "next_cursor from the previous page" } },
|
|
10596
|
-
run: ({ args }) =>
|
|
12216
|
+
run: ({ args }) => run2(() => billingTransactions(makeCtx(), { cursor: args.cursor }), args)
|
|
10597
12217
|
}),
|
|
10598
12218
|
upgrade: defineCommand({
|
|
10599
12219
|
meta: { name: "upgrade", description: "Removed: monthly caps became prepaid credits. Use billing topup" },
|
|
@@ -10612,34 +12232,34 @@ var batches = defineCommand({
|
|
|
10612
12232
|
file: { type: "positional", required: true, description: "Path to requests.jsonl" },
|
|
10613
12233
|
endpoint: { type: "string", default: "/v1/chat/completions", description: "Endpoint every line calls" }
|
|
10614
12234
|
},
|
|
10615
|
-
run: ({ args }) =>
|
|
12235
|
+
run: ({ args }) => run2(() => batchCreate(makeCtx(), { path: args.file, endpoint: args.endpoint }), args)
|
|
10616
12236
|
}),
|
|
10617
12237
|
list: defineCommand({
|
|
10618
12238
|
meta: { name: "list", description: "Your batches" },
|
|
10619
12239
|
args: jsonFlag,
|
|
10620
|
-
run: ({ args }) =>
|
|
12240
|
+
run: ({ args }) => run2(() => batchList(makeCtx()), args)
|
|
10621
12241
|
}),
|
|
10622
12242
|
get: defineCommand({
|
|
10623
12243
|
meta: { name: "get", description: "One batch" },
|
|
10624
12244
|
args: { ...jsonFlag, id: { type: "positional", required: true, description: "Batch id" } },
|
|
10625
|
-
run: ({ args }) =>
|
|
12245
|
+
run: ({ args }) => run2(() => batchGet(makeCtx(), args.id), args)
|
|
10626
12246
|
}),
|
|
10627
12247
|
results: defineCommand({
|
|
10628
12248
|
meta: { name: "results", description: "Print the output lines of a finished batch" },
|
|
10629
12249
|
args: { ...jsonFlag, id: { type: "positional", required: true, description: "Batch id" } },
|
|
10630
|
-
run: ({ args }) =>
|
|
12250
|
+
run: ({ args }) => run2(() => batchResults(makeCtx(), args.id), args)
|
|
10631
12251
|
}),
|
|
10632
12252
|
cancel: defineCommand({
|
|
10633
12253
|
meta: { name: "cancel", description: "Cancel a batch" },
|
|
10634
12254
|
args: { ...jsonFlag, id: { type: "positional", required: true, description: "Batch id" } },
|
|
10635
|
-
run: ({ args }) =>
|
|
12255
|
+
run: ({ args }) => run2(() => batchCancel(makeCtx(), args.id), args)
|
|
10636
12256
|
})
|
|
10637
12257
|
}
|
|
10638
12258
|
});
|
|
10639
12259
|
var benchmarks2 = defineCommand({
|
|
10640
12260
|
meta: { name: "benchmarks", description: "Our own benchmark results for a model" },
|
|
10641
12261
|
args: { ...jsonFlag, ...regionFlag, id: { type: "positional", required: true, description: "Model id" } },
|
|
10642
|
-
run: ({ args }) =>
|
|
12262
|
+
run: ({ args }) => run2(() => benchmarks(makeCtx(), args.id, region(args.region)), args)
|
|
10643
12263
|
});
|
|
10644
12264
|
var fineTune = defineCommand({
|
|
10645
12265
|
meta: {
|
|
@@ -10656,7 +12276,7 @@ var fineTune = defineCommand({
|
|
|
10656
12276
|
suffix: { type: "string", description: "Suffix for the fine-tuned model id" },
|
|
10657
12277
|
epochs: { type: "string", description: "1-10, default 3" }
|
|
10658
12278
|
},
|
|
10659
|
-
run: ({ args }) =>
|
|
12279
|
+
run: ({ args }) => run2(() => fineTuneCreate(makeCtx(), {
|
|
10660
12280
|
path: args.file,
|
|
10661
12281
|
model: args.model,
|
|
10662
12282
|
suffix: args.suffix,
|
|
@@ -10666,27 +12286,27 @@ var fineTune = defineCommand({
|
|
|
10666
12286
|
list: defineCommand({
|
|
10667
12287
|
meta: { name: "list", description: "Your jobs" },
|
|
10668
12288
|
args: jsonFlag,
|
|
10669
|
-
run: ({ args }) =>
|
|
12289
|
+
run: ({ args }) => run2(() => fineTuneList(makeCtx()), args)
|
|
10670
12290
|
}),
|
|
10671
12291
|
get: defineCommand({
|
|
10672
12292
|
meta: { name: "get", description: "One job" },
|
|
10673
12293
|
args: { ...jsonFlag, id: { type: "positional", required: true, description: "Job id" } },
|
|
10674
|
-
run: ({ args }) =>
|
|
12294
|
+
run: ({ args }) => run2(() => fineTuneGet(makeCtx(), args.id), args)
|
|
10675
12295
|
}),
|
|
10676
12296
|
events: defineCommand({
|
|
10677
12297
|
meta: { name: "events", description: "A job's progress events" },
|
|
10678
12298
|
args: { ...jsonFlag, id: { type: "positional", required: true, description: "Job id" } },
|
|
10679
|
-
run: ({ args }) =>
|
|
12299
|
+
run: ({ args }) => run2(() => fineTuneEvents(makeCtx(), args.id), args)
|
|
10680
12300
|
}),
|
|
10681
12301
|
cancel: defineCommand({
|
|
10682
12302
|
meta: { name: "cancel", description: "Cancel a job" },
|
|
10683
12303
|
args: { ...jsonFlag, id: { type: "positional", required: true, description: "Job id" } },
|
|
10684
|
-
run: ({ args }) =>
|
|
12304
|
+
run: ({ args }) => run2(() => fineTuneCancel(makeCtx(), args.id), args)
|
|
10685
12305
|
}),
|
|
10686
12306
|
models: defineCommand({
|
|
10687
12307
|
meta: { name: "models", description: "Your fine-tuned models" },
|
|
10688
12308
|
args: jsonFlag,
|
|
10689
|
-
run: ({ args }) =>
|
|
12309
|
+
run: ({ args }) => run2(() => fineTuneModels(makeCtx()), args)
|
|
10690
12310
|
})
|
|
10691
12311
|
}
|
|
10692
12312
|
});
|
|
@@ -10712,7 +12332,7 @@ var video = defineCommand({
|
|
|
10712
12332
|
wait: { type: "boolean", description: "Wait until the video is ready" },
|
|
10713
12333
|
output: { type: "string", alias: "o", description: "Save the MP4 here (waits for it)" }
|
|
10714
12334
|
},
|
|
10715
|
-
run: ({ args }) =>
|
|
12335
|
+
run: ({ args }) => run2(() => videoCreate(makeCtx(), {
|
|
10716
12336
|
prompt: args.prompt,
|
|
10717
12337
|
model: args.model,
|
|
10718
12338
|
seconds: num("seconds", args.seconds),
|
|
@@ -10726,7 +12346,7 @@ var video = defineCommand({
|
|
|
10726
12346
|
get: defineCommand({
|
|
10727
12347
|
meta: { name: "get", description: "One video job: status, progress, cost" },
|
|
10728
12348
|
args: { ...jsonFlag, id: { type: "positional", required: true, description: "Video id" } },
|
|
10729
|
-
run: ({ args }) =>
|
|
12349
|
+
run: ({ args }) => run2(() => videoGet(makeCtx(), args.id), args)
|
|
10730
12350
|
}),
|
|
10731
12351
|
list: defineCommand({
|
|
10732
12352
|
meta: { name: "list", description: "Your video jobs, newest first" },
|
|
@@ -10735,7 +12355,7 @@ var video = defineCommand({
|
|
|
10735
12355
|
limit: { type: "string", description: "Page size, 1-100 (default 20)" },
|
|
10736
12356
|
after: { type: "string", description: "Continue after this video id" }
|
|
10737
12357
|
},
|
|
10738
|
-
run: ({ args }) =>
|
|
12358
|
+
run: ({ args }) => run2(() => videoList(makeCtx(), { limit: num("limit", args.limit), after: args.after }), args)
|
|
10739
12359
|
}),
|
|
10740
12360
|
download: defineCommand({
|
|
10741
12361
|
meta: { name: "download", description: "Save a finished video (or --thumbnail) to a file" },
|
|
@@ -10745,12 +12365,12 @@ var video = defineCommand({
|
|
|
10745
12365
|
output: { type: "string", alias: "o", required: true, description: "File to write" },
|
|
10746
12366
|
thumbnail: { type: "boolean", description: "Download the JPEG thumbnail instead" }
|
|
10747
12367
|
},
|
|
10748
|
-
run: ({ args }) =>
|
|
12368
|
+
run: ({ args }) => run2(() => videoDownload(makeCtx(), args.id, args.output, args.thumbnail ? "thumbnail" : "video"), args)
|
|
10749
12369
|
}),
|
|
10750
12370
|
delete: defineCommand({
|
|
10751
12371
|
meta: { name: "delete", description: "Cancel (credits returned) or delete a video and its files" },
|
|
10752
12372
|
args: { ...jsonFlag, id: { type: "positional", required: true, description: "Video id" } },
|
|
10753
|
-
run: ({ args }) =>
|
|
12373
|
+
run: ({ args }) => run2(() => videoDelete(makeCtx(), args.id), args)
|
|
10754
12374
|
})
|
|
10755
12375
|
}
|
|
10756
12376
|
});
|
|
@@ -10760,7 +12380,7 @@ var status2 = defineCommand({
|
|
|
10760
12380
|
description: "Per-model health in the region (last hour), plus your account's notices when a key is set"
|
|
10761
12381
|
},
|
|
10762
12382
|
args: { ...jsonFlag, ...regionFlag },
|
|
10763
|
-
run: ({ args }) =>
|
|
12383
|
+
run: ({ args }) => run2(() => status(makeCtx(), region(args.region)), args)
|
|
10764
12384
|
});
|
|
10765
12385
|
var doctor2 = defineCommand({
|
|
10766
12386
|
meta: {
|
|
@@ -10768,7 +12388,7 @@ var doctor2 = defineCommand({
|
|
|
10768
12388
|
description: "Check this project's setup: key and region, account, credit, and notices to tell your human"
|
|
10769
12389
|
},
|
|
10770
12390
|
args: jsonFlag,
|
|
10771
|
-
run: ({ args }) =>
|
|
12391
|
+
run: ({ args }) => run2(() => doctor(makeCtx()), args)
|
|
10772
12392
|
});
|
|
10773
12393
|
var webhooks = defineCommand({
|
|
10774
12394
|
meta: {
|
|
@@ -10786,27 +12406,68 @@ var webhooks = defineCommand({
|
|
|
10786
12406
|
description: "Comma-separated events (default: all four)"
|
|
10787
12407
|
}
|
|
10788
12408
|
},
|
|
10789
|
-
run: ({ args }) =>
|
|
12409
|
+
run: ({ args }) => run2(() => webhooksAdd(makeCtx(), { url: args.url, events: args.events }), args)
|
|
10790
12410
|
}),
|
|
10791
12411
|
list: defineCommand({
|
|
10792
12412
|
meta: { name: "list", description: "This account's webhook endpoints" },
|
|
10793
12413
|
args: jsonFlag,
|
|
10794
|
-
run: ({ args }) =>
|
|
12414
|
+
run: ({ args }) => run2(() => webhooksList(makeCtx()), args)
|
|
10795
12415
|
}),
|
|
10796
12416
|
remove: defineCommand({
|
|
10797
12417
|
meta: { name: "remove", description: "Delete an endpoint by id (whk_…)" },
|
|
10798
12418
|
args: { ...jsonFlag, id: { type: "positional", required: true, description: "Webhook id" } },
|
|
10799
|
-
run: ({ args }) =>
|
|
12419
|
+
run: ({ args }) => run2(() => webhooksRemove(makeCtx(), args.id), args)
|
|
10800
12420
|
})
|
|
10801
12421
|
}
|
|
10802
12422
|
});
|
|
12423
|
+
var connectTool = defineCommand({
|
|
12424
|
+
meta: {
|
|
12425
|
+
name: "connect",
|
|
12426
|
+
description: "Point an OpenAI-compatible tool at ScaledThought: writes its config with the base URL and a new child key (never printed), or prints the steps when the tool's key store can't be written safely"
|
|
12427
|
+
},
|
|
12428
|
+
args: {
|
|
12429
|
+
...jsonFlag,
|
|
12430
|
+
tool: { type: "positional", required: true, description: CONNECT_TOOLS.join(", ") },
|
|
12431
|
+
model: { type: "string", description: "Default model (default: the newest live chat model)" },
|
|
12432
|
+
"budget-usd": { type: "string", description: "Hard daily budget for the tool's key, in USD" }
|
|
12433
|
+
},
|
|
12434
|
+
run: ({ args }) => run2(() => connect(makeCtx(), args.tool, {
|
|
12435
|
+
model: args.model,
|
|
12436
|
+
budgetUsd: num("budget-usd", args["budget-usd"])
|
|
12437
|
+
}), args)
|
|
12438
|
+
});
|
|
10803
12439
|
var mcp = defineCommand({
|
|
10804
12440
|
meta: {
|
|
10805
12441
|
name: "mcp",
|
|
10806
12442
|
description: "Print the `claude mcp add` command and JSON config for this account's MCP server"
|
|
10807
12443
|
},
|
|
10808
12444
|
args: jsonFlag,
|
|
10809
|
-
run: ({ args }) =>
|
|
12445
|
+
run: ({ args }) => run2(() => mcpSetup(makeCtx()), args)
|
|
12446
|
+
});
|
|
12447
|
+
var dashboard = defineCommand({
|
|
12448
|
+
meta: {
|
|
12449
|
+
name: "dashboard",
|
|
12450
|
+
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"
|
|
12451
|
+
},
|
|
12452
|
+
args: {
|
|
12453
|
+
...jsonFlag,
|
|
12454
|
+
page: { type: "string", description: "overview (default), billing, keys, members or connect" },
|
|
12455
|
+
email: {
|
|
12456
|
+
type: "string",
|
|
12457
|
+
description: "Set the owner's email (only before anyone has signed in; normally the first payer is the owner)"
|
|
12458
|
+
},
|
|
12459
|
+
open: { type: "boolean", default: true, description: "Open it in the browser (--no-open only prints the URL)" }
|
|
12460
|
+
},
|
|
12461
|
+
run: ({ args }) => run2(() => dashboardOpen(makeCtx(), {
|
|
12462
|
+
page: args.page,
|
|
12463
|
+
email: args.email,
|
|
12464
|
+
noOpen: args.open === false
|
|
12465
|
+
}), args)
|
|
12466
|
+
});
|
|
12467
|
+
var members2 = defineCommand({
|
|
12468
|
+
meta: { name: "members", description: "Who can sign in to this account's dashboard (email, role, status)" },
|
|
12469
|
+
args: jsonFlag,
|
|
12470
|
+
run: ({ args }) => run2(() => membersList(makeCtx()), args)
|
|
10810
12471
|
});
|
|
10811
12472
|
var root = defineCommand({
|
|
10812
12473
|
meta: {
|
|
@@ -10816,6 +12477,8 @@ var root = defineCommand({
|
|
|
10816
12477
|
},
|
|
10817
12478
|
subCommands: {
|
|
10818
12479
|
init: init2,
|
|
12480
|
+
login: login2,
|
|
12481
|
+
use,
|
|
10819
12482
|
models,
|
|
10820
12483
|
benchmarks: benchmarks2,
|
|
10821
12484
|
status: status2,
|
|
@@ -10833,7 +12496,10 @@ var root = defineCommand({
|
|
|
10833
12496
|
video,
|
|
10834
12497
|
webhooks,
|
|
10835
12498
|
doctor: doctor2,
|
|
10836
|
-
|
|
12499
|
+
connect: connectTool,
|
|
12500
|
+
mcp,
|
|
12501
|
+
dashboard,
|
|
12502
|
+
members: members2
|
|
10837
12503
|
}
|
|
10838
12504
|
});
|
|
10839
12505
|
var NUMERIC_FLAGS = new Set([
|