scaledthought 0.3.1 → 0.3.3
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 -6
- package/dist/index.js +352 -75
- package/package.json +13 -9
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# scaledthought
|
|
2
2
|
|
|
3
|
-
The CLI for [ScaledThought](https://scaledthought.com): headless, OpenAI-compatible inference for
|
|
4
|
-
open-weight models
|
|
3
|
+
The CLI for [ScaledThought](https://scaledthought.com/go/org-npm): headless, OpenAI-compatible inference for
|
|
4
|
+
open-weight models. The US region is live; Canada is coming soon. It's built for coding agents: it never prompts, prints
|
|
5
5
|
JSON when stdout isn't a terminal, and every error is one JSON line on stderr with a non-zero exit.
|
|
6
6
|
|
|
7
7
|
Agents: the full setup protocol is at <https://scaledthought.com/llms.txt>.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx -y scaledthought@0.3 init --region us #
|
|
10
|
+
npx -y scaledthought@0.3 init --region us --source org-npm # US only for now; Canada is coming soon
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
`init` creates an account, writes `ST_API_KEY` and `ST_BASE_URL` to `./.env` (mode 0600), adds `.env` to
|
|
@@ -16,8 +16,17 @@ region, `next_command` and `next_steps`.
|
|
|
16
16
|
Running it again is safe. If a key already exists it returns `"already_initialized": true` and the
|
|
17
17
|
account status instead of creating a second account.
|
|
18
18
|
|
|
19
|
+
Before `init`, ask your human one question: "What email should own this account?" and pass it as
|
|
20
|
+
`--email <address>`. We email that address one confirmation link; once they click it they own the account and sign in
|
|
21
|
+
to its dashboard (billing, credits, keys). We only send it account email, such as the confirmation, sign-in links and security
|
|
22
|
+
notices, never marketing. `init` then prints `email_confirmation` and a `message_for_human` to relay. If they'd
|
|
23
|
+
rather not, leave the flag out: `init` prints `"missing": ["owner_email"]`, and
|
|
24
|
+
`npx -y scaledthought@0.3 account email set <address>` names them later.
|
|
25
|
+
|
|
19
26
|
If you arrived through a `scaledthought.com/go/<code>` link, its instructions add `--source <code>` (a campaign code
|
|
20
|
-
such as `a-rd`, stored on the new account so we know which campaign worked
|
|
27
|
+
such as `a-rd`, stored on the new account so we know which campaign worked; the command above carries `org-npm`, the
|
|
28
|
+
code for this page). Directory listings link to their own `/go/<code>`, and a listing's MCP URL can end in
|
|
29
|
+
`?source=<code>`: connecting without a key then answers with the `init` command carrying that code. It's optional, and a value that isn't a
|
|
21
30
|
code is dropped with a warning rather than failing `init`. On some US campaigns the human can copy an opt-in prompt
|
|
22
31
|
ending in `(ad click <token>)`; the `/go` instructions then tell the agent to add `--ad-click <token>` (the token is
|
|
23
32
|
in the prompt, never in the link). A malformed token is dropped with a warning, and the flag is ignored outside `us`.
|
|
@@ -41,9 +50,10 @@ ends, so the link would reach the human only after the payment it is waiting for
|
|
|
41
50
|
|
|
42
51
|
```bash
|
|
43
52
|
npx -y scaledthought@0.3 models --live # models serving in your region now
|
|
44
|
-
npx -y scaledthought@0.3 chat -m
|
|
53
|
+
npx -y scaledthought@0.3 chat -m <id> "Reply with OK" # one real completion (an id from models --live)
|
|
45
54
|
npx -y scaledthought@0.3 mcp # `claude mcp add …` command + JSON config
|
|
46
|
-
npx -y scaledthought@0.3 account # plan, region, spend
|
|
55
|
+
npx -y scaledthought@0.3 account # plan, region, spend, owner_email
|
|
56
|
+
npx -y scaledthought@0.3 account email set <address> # your human's email owns the account once they confirm
|
|
47
57
|
npx -y scaledthought@0.3 account ad-measurement off # opt out of ad measurement
|
|
48
58
|
```
|
|
49
59
|
|
package/dist/index.js
CHANGED
|
@@ -553,8 +553,8 @@ import { join } from "node:path";
|
|
|
553
553
|
// package.json
|
|
554
554
|
var package_default = {
|
|
555
555
|
name: "scaledthought",
|
|
556
|
-
version: "0.3.
|
|
557
|
-
description: "
|
|
556
|
+
version: "0.3.3",
|
|
557
|
+
description: "OpenAI-compatible inference for open-weight models, set up by your coding agent. Prepaid, pay per token, no subscription. US live; Canada coming soon.",
|
|
558
558
|
license: "MIT",
|
|
559
559
|
keywords: [
|
|
560
560
|
"llm",
|
|
@@ -563,18 +563,22 @@ var package_default = {
|
|
|
563
563
|
"openai-compatible",
|
|
564
564
|
"mcp",
|
|
565
565
|
"agents",
|
|
566
|
-
"
|
|
567
|
-
"
|
|
568
|
-
"
|
|
566
|
+
"coding-agents",
|
|
567
|
+
"claude-code",
|
|
568
|
+
"llm-api",
|
|
569
|
+
"inference-api",
|
|
570
|
+
"open-models",
|
|
571
|
+
"mcp-server",
|
|
572
|
+
"model-context-protocol",
|
|
573
|
+
"cli"
|
|
569
574
|
],
|
|
570
|
-
homepage: "https://scaledthought.com",
|
|
575
|
+
homepage: "https://scaledthought.com/go/org-npm",
|
|
571
576
|
bugs: {
|
|
572
|
-
url: "https://github.com/Scaled-Thought/scaledthought/issues"
|
|
577
|
+
url: "https://github.com/Scaled-Thought/scaledthought-mcp/issues"
|
|
573
578
|
},
|
|
574
579
|
repository: {
|
|
575
580
|
type: "git",
|
|
576
|
-
url: "git+https://github.com/Scaled-Thought/scaledthought.git"
|
|
577
|
-
directory: "apps/cli"
|
|
581
|
+
url: "git+https://github.com/Scaled-Thought/scaledthought-mcp.git"
|
|
578
582
|
},
|
|
579
583
|
type: "module",
|
|
580
584
|
bin: {
|
|
@@ -666,6 +670,7 @@ var HINTS = {
|
|
|
666
670
|
gone: "This endpoint was removed. For credits use `billing topup --amount-usd 20`.",
|
|
667
671
|
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.",
|
|
668
672
|
rate_limit_exceeded: "Wait the number of seconds in retry_after, then retry.",
|
|
673
|
+
http_429: "Too many requests from this address for now. Wait about 5 minutes, then run the same command again. A top-up link you already gave your human still works until its expires_at: don't create a new one unless it expired.",
|
|
669
674
|
session_limit_exceeded: "Too many realtime sessions are open on this key. Close one, then retry.",
|
|
670
675
|
audio_window_exceeded: "Send shorter turns: commit the audio and ask for a response at least every 5 minutes.",
|
|
671
676
|
invalid_client_secret: "Client secrets are single use and expire in about a minute. Mint a new one and connect at once.",
|
|
@@ -677,10 +682,11 @@ var HINTS = {
|
|
|
677
682
|
video_not_ready: "Poll `video get <id>` until status is completed, or pass --wait.",
|
|
678
683
|
video_expired: "Videos are deleted 24 hours after they complete. Generate it again.",
|
|
679
684
|
video_failed: "Your credits were returned. Retry, or try another model or size.",
|
|
680
|
-
model_not_found: "Use an id from `models --live
|
|
685
|
+
model_not_found: "Use an id from `models --live`.",
|
|
686
|
+
model_required: "Pick a live chat model from `models --live` and pass its id with -m.",
|
|
681
687
|
insufficient_scope: "This key lacks the scope. Use the account's default key or create one with the scope.",
|
|
682
688
|
account_suspended: "Stop and tell your human. Contact hello@scaledthought.com.",
|
|
683
|
-
upstream_error: "Transient model-server error. Retry with backoff, or
|
|
689
|
+
upstream_error: "Transient model-server error. Retry with backoff, or try another model from `models --live`.",
|
|
684
690
|
service_unavailable: "Transient. Retry in a few seconds.",
|
|
685
691
|
invalid_body: "Fix the field named in the message. Retrying unchanged will fail again.",
|
|
686
692
|
invalid_request: "Fix the field named in the message. Retrying unchanged will fail again.",
|
|
@@ -6512,6 +6518,14 @@ function object(shape, params) {
|
|
|
6512
6518
|
};
|
|
6513
6519
|
return new ZodObject(def);
|
|
6514
6520
|
}
|
|
6521
|
+
function strictObject(shape, params) {
|
|
6522
|
+
return new ZodObject({
|
|
6523
|
+
type: "object",
|
|
6524
|
+
shape,
|
|
6525
|
+
catchall: never(),
|
|
6526
|
+
...normalizeParams(params)
|
|
6527
|
+
});
|
|
6528
|
+
}
|
|
6515
6529
|
var ZodUnion = /* @__PURE__ */ $constructor("ZodUnion", (inst, def) => {
|
|
6516
6530
|
$ZodUnion.init(inst, def);
|
|
6517
6531
|
ZodType.init(inst, def);
|
|
@@ -7149,6 +7163,11 @@ function parseWith(def, text, opts) {
|
|
|
7149
7163
|
skipped++;
|
|
7150
7164
|
return;
|
|
7151
7165
|
}
|
|
7166
|
+
const second = (r.filter((c) => c.trim() !== "")[1] ?? "").trim().toLowerCase();
|
|
7167
|
+
if (def.totalPrefix && parseReportDay(first) && second.startsWith(def.totalPrefix.toLowerCase())) {
|
|
7168
|
+
skipped++;
|
|
7169
|
+
return;
|
|
7170
|
+
}
|
|
7152
7171
|
const rawDay = cell(r, "day") ?? "";
|
|
7153
7172
|
const day = parseReportDay(rawDay);
|
|
7154
7173
|
if (!day) {
|
|
@@ -7400,10 +7419,62 @@ var GoogleCredentials = object({
|
|
|
7400
7419
|
refresh_token: string2().min(1).optional(),
|
|
7401
7420
|
service_account: object({ client_email: string2().email(), private_key: string2().min(1) }).optional()
|
|
7402
7421
|
}).refine((c) => c.service_account || c.client_id && c.client_secret && c.refresh_token);
|
|
7403
|
-
var
|
|
7422
|
+
var Secret = string2().min(1);
|
|
7423
|
+
var GoogleSpendAccount = strictObject({
|
|
7424
|
+
customer_id: CustomerId,
|
|
7425
|
+
login_customer_id: CustomerId.optional(),
|
|
7426
|
+
developer_token: Secret.optional(),
|
|
7427
|
+
auth: union([
|
|
7428
|
+
strictObject({ oauth: strictObject({ client_id: Secret, client_secret: Secret, refresh_token: Secret }) }),
|
|
7429
|
+
strictObject({
|
|
7430
|
+
service_account: strictObject({ client_email: string2().email(), private_key: Secret })
|
|
7431
|
+
})
|
|
7432
|
+
])
|
|
7433
|
+
});
|
|
7434
|
+
var GoogleSpendAccounts = array(GoogleSpendAccount).min(1).max(20).superRefine((list, ctx) => {
|
|
7435
|
+
const seen = new Set;
|
|
7436
|
+
list.forEach((a, i) => {
|
|
7437
|
+
if (seen.has(a.customer_id))
|
|
7438
|
+
ctx.addIssue({ code: "custom", path: [i, "customer_id"], message: "duplicate" });
|
|
7439
|
+
seen.add(a.customer_id);
|
|
7440
|
+
});
|
|
7441
|
+
});
|
|
7442
|
+
var flatten = (a) => ({
|
|
7443
|
+
customer_id: a.customer_id,
|
|
7444
|
+
...a.login_customer_id ? { login_customer_id: a.login_customer_id } : {},
|
|
7445
|
+
...a.developer_token ? { developer_token: a.developer_token } : {},
|
|
7446
|
+
..."oauth" in a.auth ? a.auth.oauth : { service_account: a.auth.service_account }
|
|
7447
|
+
});
|
|
7448
|
+
var parseJson2 = (raw) => {
|
|
7449
|
+
if (!raw)
|
|
7450
|
+
return { ok: false };
|
|
7451
|
+
try {
|
|
7452
|
+
return { ok: true, json: JSON.parse(raw) };
|
|
7453
|
+
} catch {
|
|
7454
|
+
return { ok: false };
|
|
7455
|
+
}
|
|
7456
|
+
};
|
|
7457
|
+
function googleSpendAccounts(raw) {
|
|
7458
|
+
const p = parseJson2(raw);
|
|
7459
|
+
if (!p.ok)
|
|
7460
|
+
throw new SpendApiError("credentials_invalid");
|
|
7461
|
+
if (Array.isArray(p.json)) {
|
|
7462
|
+
const list = GoogleSpendAccounts.safeParse(p.json);
|
|
7463
|
+
if (!list.success)
|
|
7464
|
+
throw new SpendApiError("credentials_invalid");
|
|
7465
|
+
return list.data.map(flatten);
|
|
7466
|
+
}
|
|
7467
|
+
const single = GoogleCredentials.safeParse(p.json);
|
|
7468
|
+
if (!single.success)
|
|
7469
|
+
throw new SpendApiError("credentials_invalid");
|
|
7470
|
+
return [single.data];
|
|
7471
|
+
}
|
|
7404
7472
|
var hyphenCustomerId = (id) => `${id.slice(0, 3)}-${id.slice(3, 6)}-${id.slice(6)}`;
|
|
7473
|
+
function googleApiAccounts(creds) {
|
|
7474
|
+
return googleSpendAccounts(creds[ENV]).map((c) => hyphenCustomerId(c.customer_id));
|
|
7475
|
+
}
|
|
7405
7476
|
function googleApiAccount(creds) {
|
|
7406
|
-
return
|
|
7477
|
+
return googleApiAccounts(creds)[0];
|
|
7407
7478
|
}
|
|
7408
7479
|
var Token = object({ access_token: string2().min(1) });
|
|
7409
7480
|
async function googleAccessToken(c, f) {
|
|
@@ -7473,7 +7544,10 @@ async function search(c, token, query, f) {
|
|
|
7473
7544
|
var FIELDS = "segments.date, customer.currency_code, campaign.id, campaign.name, metrics.impressions, metrics.clicks, " + "metrics.cost_micros, metrics.conversions";
|
|
7474
7545
|
async function pullGoogleAds(range, ctx) {
|
|
7475
7546
|
checkRange(range);
|
|
7476
|
-
const
|
|
7547
|
+
const all = googleSpendAccounts(ctx.credentials[ENV]);
|
|
7548
|
+
const c = ctx.adAccountId ? all.find((a) => hyphenCustomerId(a.customer_id) === ctx.adAccountId) : all[0];
|
|
7549
|
+
if (!c)
|
|
7550
|
+
throw new SpendApiError("credentials_invalid");
|
|
7477
7551
|
const token = await googleAccessToken(c, ctx.fetch);
|
|
7478
7552
|
const where = `WHERE segments.date BETWEEN '${range.from}' AND '${range.to}'`;
|
|
7479
7553
|
const adGroups = await search(c, token, `SELECT ${FIELDS}, ad_group.id, ad_group.name FROM ad_group ${where}`, ctx.fetch);
|
|
@@ -7749,8 +7823,8 @@ var MicrosoftCredentials = object({
|
|
|
7749
7823
|
tenant: string2().regex(/^[A-Za-z0-9.-]{1,100}$/).default("common"),
|
|
7750
7824
|
report_time_zone: string2().regex(/^[A-Za-z]{2,80}$/).optional()
|
|
7751
7825
|
});
|
|
7752
|
-
var
|
|
7753
|
-
var microsoftApiAccount = (creds) =>
|
|
7826
|
+
var credentials = (creds) => parseCredentialsJson(creds[ENV2], MicrosoftCredentials);
|
|
7827
|
+
var microsoftApiAccount = (creds) => credentials(creds).account_id;
|
|
7754
7828
|
var Token2 = object({ access_token: string2().min(1) });
|
|
7755
7829
|
async function microsoftAccessToken(c, f) {
|
|
7756
7830
|
const form = new URLSearchParams({
|
|
@@ -7819,7 +7893,7 @@ var Polled = object({
|
|
|
7819
7893
|
});
|
|
7820
7894
|
async function pullMicrosoftAds(range, ctx) {
|
|
7821
7895
|
checkRange(range);
|
|
7822
|
-
const c =
|
|
7896
|
+
const c = credentials(ctx.credentials);
|
|
7823
7897
|
const token = await microsoftAccessToken(c, ctx.fetch);
|
|
7824
7898
|
const headers = {
|
|
7825
7899
|
authorization: `Bearer ${token}`,
|
|
@@ -7900,8 +7974,8 @@ var AD_WRITE_CREDENTIAL_ENV = {
|
|
|
7900
7974
|
var AD_WRITE_CREDENTIAL_ENV_NAMES = Object.values(AD_WRITE_CREDENTIAL_ENV);
|
|
7901
7975
|
// ../../packages/growth/src/portfolio.ts
|
|
7902
7976
|
var PORTFOLIO_LIMITS = {
|
|
7903
|
-
weeklyBudgetMicros:
|
|
7904
|
-
founders: ["alexey-scaled-throught", "josh-scaledthought"],
|
|
7977
|
+
weeklyBudgetMicros: 1500000000,
|
|
7978
|
+
founders: ["alexey-scaled", "alexey-scaled-throught", "josh-scaledthought"],
|
|
7905
7979
|
effortDayMicros: 50000000
|
|
7906
7980
|
};
|
|
7907
7981
|
var FUNNEL_STAGES = ["click", "fetch", "signup", "activated", "paid"];
|
|
@@ -8082,18 +8156,94 @@ var ENV3 = {
|
|
|
8082
8156
|
signupAction: "GOOGLE_ADS_SIGNUP_CONVERSION_ACTION_ID",
|
|
8083
8157
|
purchaseAction: "GOOGLE_ADS_PURCHASE_CONVERSION_ACTION_ID",
|
|
8084
8158
|
loginCustomerId: "GOOGLE_ADS_LOGIN_CUSTOMER_ID",
|
|
8085
|
-
validateOnly: "GOOGLE_ADS_CONVERSIONS_VALIDATE_ONLY"
|
|
8159
|
+
validateOnly: "GOOGLE_ADS_CONVERSIONS_VALIDATE_ONLY",
|
|
8160
|
+
routes: "GOOGLE_ADS_CONVERSION_ROUTES",
|
|
8161
|
+
refreshToken2: "GOOGLE_DATA_MANAGER_REFRESH_TOKEN_2",
|
|
8162
|
+
refreshToken3: "GOOGLE_DATA_MANAGER_REFRESH_TOKEN_3"
|
|
8086
8163
|
};
|
|
8164
|
+
var GOOGLE_ROUTE_REFRESH_TOKEN_ENV = [ENV3.refreshToken2, ENV3.refreshToken3];
|
|
8165
|
+
var GOOGLE_ROUTING_ENV = [ENV3.routes, ...GOOGLE_ROUTE_REFRESH_TOKEN_ENV];
|
|
8166
|
+
var CustomerId2 = string2().regex(/^\d{3}-?\d{3}-?\d{4}$/, "a 10-digit Google Ads customer id (dashes allowed)");
|
|
8167
|
+
var ActionId = string2().regex(/^\d{1,20}$/, "a numeric conversion action id");
|
|
8168
|
+
var GoogleConversionRoute = strictObject({
|
|
8169
|
+
codes: array(string2().regex(CODE_RE, "a campaign code")).min(1).max(50),
|
|
8170
|
+
customer_id: CustomerId2,
|
|
8171
|
+
signup_action_id: ActionId,
|
|
8172
|
+
purchase_action_id: ActionId,
|
|
8173
|
+
login_customer_id: CustomerId2.optional(),
|
|
8174
|
+
refresh_token_ref: _enum(GOOGLE_ROUTE_REFRESH_TOKEN_ENV).optional()
|
|
8175
|
+
});
|
|
8176
|
+
var GoogleConversionRoutes = array(GoogleConversionRoute).max(20);
|
|
8087
8177
|
var digits = (s) => (s ?? "").replace(/-/g, "");
|
|
8088
|
-
function
|
|
8089
|
-
const
|
|
8090
|
-
|
|
8091
|
-
|
|
8178
|
+
function parseGoogleConversionRoutes(c) {
|
|
8179
|
+
const raw = c[ENV3.routes]?.trim();
|
|
8180
|
+
if (!raw)
|
|
8181
|
+
return [];
|
|
8182
|
+
let json;
|
|
8183
|
+
try {
|
|
8184
|
+
json = JSON.parse(raw);
|
|
8185
|
+
} catch {
|
|
8186
|
+
throw new Error(`${ENV3.routes} is not valid JSON`);
|
|
8187
|
+
}
|
|
8188
|
+
const parsed = GoogleConversionRoutes.safeParse(json);
|
|
8189
|
+
if (!parsed.success) {
|
|
8190
|
+
const i = parsed.error.issues[0];
|
|
8191
|
+
throw new Error(`${ENV3.routes} is invalid at ${i?.path.join(".") || "(root)"}: ${i?.message ?? "invalid"}`);
|
|
8192
|
+
}
|
|
8193
|
+
const seen = new Set;
|
|
8194
|
+
for (const [i, r] of parsed.data.entries()) {
|
|
8195
|
+
for (const code of r.codes) {
|
|
8196
|
+
if (seen.has(code))
|
|
8197
|
+
throw new Error(`${ENV3.routes}[${i}].codes repeats a code already listed`);
|
|
8198
|
+
seen.add(code);
|
|
8199
|
+
}
|
|
8200
|
+
if (r.refresh_token_ref && !c[r.refresh_token_ref]?.trim())
|
|
8201
|
+
throw new Error(`${ENV3.routes}[${i}].refresh_token_ref names ${r.refresh_token_ref}, which is not set`);
|
|
8202
|
+
}
|
|
8203
|
+
return parsed.data;
|
|
8204
|
+
}
|
|
8205
|
+
function validateGoogleConversionConfig(c) {
|
|
8206
|
+
const check = (name, schema) => {
|
|
8207
|
+
const v = c[name]?.trim();
|
|
8208
|
+
if (v && !schema.safeParse(v).success)
|
|
8209
|
+
throw new Error(`${name} must be digits`);
|
|
8210
|
+
};
|
|
8211
|
+
check(ENV3.customerId, CustomerId2);
|
|
8212
|
+
check(ENV3.loginCustomerId, CustomerId2);
|
|
8213
|
+
check(ENV3.signupAction, ActionId);
|
|
8214
|
+
check(ENV3.purchaseAction, ActionId);
|
|
8215
|
+
parseGoogleConversionRoutes(c);
|
|
8216
|
+
}
|
|
8217
|
+
var defaultDestination = (c) => ({
|
|
8218
|
+
key: "default",
|
|
8219
|
+
customerId: digits(c[ENV3.customerId]),
|
|
8220
|
+
loginCustomerId: digits(c[ENV3.loginCustomerId]),
|
|
8221
|
+
signupActionId: c[ENV3.signupAction] ?? "",
|
|
8222
|
+
purchaseActionId: c[ENV3.purchaseAction] ?? "",
|
|
8223
|
+
refreshTokenEnv: ENV3.refreshToken
|
|
8224
|
+
});
|
|
8225
|
+
function googleDestinationFor(code, routes, c) {
|
|
8226
|
+
const i = code ? routes.findIndex((r) => r.codes.includes(code)) : -1;
|
|
8227
|
+
const r = routes[i];
|
|
8228
|
+
if (!r)
|
|
8229
|
+
return defaultDestination(c);
|
|
8230
|
+
return {
|
|
8231
|
+
key: `route:${i}`,
|
|
8232
|
+
customerId: digits(r.customer_id),
|
|
8233
|
+
loginCustomerId: digits(r.login_customer_id),
|
|
8234
|
+
signupActionId: r.signup_action_id,
|
|
8235
|
+
purchaseActionId: r.purchase_action_id,
|
|
8236
|
+
refreshTokenEnv: r.refresh_token_ref ?? ENV3.refreshToken
|
|
8237
|
+
};
|
|
8238
|
+
}
|
|
8239
|
+
function googleIngestBody(e, c, dest) {
|
|
8240
|
+
const d = dest ?? googleDestinationFor(e.code, parseGoogleConversionRoutes(c), c);
|
|
8241
|
+
const action = e.kind === "signup" ? d.signupActionId : d.purchaseActionId;
|
|
8092
8242
|
return {
|
|
8093
8243
|
destinations: [
|
|
8094
8244
|
{
|
|
8095
|
-
operatingAccount: { accountType: "GOOGLE_ADS", accountId: customerId },
|
|
8096
|
-
...
|
|
8245
|
+
operatingAccount: { accountType: "GOOGLE_ADS", accountId: d.customerId },
|
|
8246
|
+
...d.loginCustomerId ? { loginAccount: { accountType: "GOOGLE_ADS", accountId: d.loginCustomerId } } : {},
|
|
8097
8247
|
productDestinationId: action
|
|
8098
8248
|
}
|
|
8099
8249
|
],
|
|
@@ -8120,22 +8270,40 @@ var googleSink = {
|
|
|
8120
8270
|
ENV3.signupAction,
|
|
8121
8271
|
ENV3.purchaseAction
|
|
8122
8272
|
],
|
|
8123
|
-
optionalCredentialEnv: [ENV3.loginCustomerId, ENV3.validateOnly],
|
|
8273
|
+
optionalCredentialEnv: [ENV3.loginCustomerId, ENV3.validateOnly, ...GOOGLE_ROUTING_ENV],
|
|
8124
8274
|
maxEventAgeDays: 90,
|
|
8125
8275
|
clickWindowDays: 90,
|
|
8276
|
+
validate: validateGoogleConversionConfig,
|
|
8126
8277
|
async send(events, { credentials: c, fetch: f }) {
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8278
|
+
let routes;
|
|
8279
|
+
try {
|
|
8280
|
+
routes = parseGoogleConversionRoutes(c);
|
|
8281
|
+
} catch {
|
|
8282
|
+
return events.map(() => ({ status: "retry", errorCode: "google_routes_invalid" }));
|
|
8283
|
+
}
|
|
8284
|
+
const tokens = new Map;
|
|
8285
|
+
const tokenFor = (env) => {
|
|
8286
|
+
let t = tokens.get(env);
|
|
8287
|
+
if (!t) {
|
|
8288
|
+
const refresh = c[env];
|
|
8289
|
+
t = refresh ? refreshAccessToken(f, "google", GOOGLE_TOKEN_URL, {
|
|
8290
|
+
client_id: c[ENV3.clientId],
|
|
8291
|
+
client_secret: c[ENV3.clientSecret],
|
|
8292
|
+
refresh_token: refresh
|
|
8293
|
+
}) : Promise.resolve({ ok: false, result: { status: "retry", errorCode: "google_route_token_missing" } });
|
|
8294
|
+
tokens.set(env, t);
|
|
8295
|
+
}
|
|
8296
|
+
return t;
|
|
8297
|
+
};
|
|
8134
8298
|
return eachEvent(events, async (e) => {
|
|
8299
|
+
const dest = googleDestinationFor(e.code, routes, c);
|
|
8300
|
+
const auth = await tokenFor(dest.refreshTokenEnv);
|
|
8301
|
+
if (!auth.ok)
|
|
8302
|
+
return auth.result;
|
|
8135
8303
|
const r = await request(f, GOOGLE_DATA_MANAGER_URL, {
|
|
8136
8304
|
method: "POST",
|
|
8137
8305
|
headers: { authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
|
|
8138
|
-
body: JSON.stringify(googleIngestBody(e, c))
|
|
8306
|
+
body: JSON.stringify(googleIngestBody(e, c, dest))
|
|
8139
8307
|
});
|
|
8140
8308
|
if (!r.ok)
|
|
8141
8309
|
return { status: "retry", errorCode: `google_${r.error}` };
|
|
@@ -8373,6 +8541,7 @@ var CONVERSION_SINKS = {
|
|
|
8373
8541
|
var CONVERSION_CREDENTIAL_ENV = [
|
|
8374
8542
|
...new Set(Object.values(CONVERSION_SINKS).flatMap((s) => [...s.credentialEnv, ...s.optionalCredentialEnv ?? []]))
|
|
8375
8543
|
].sort();
|
|
8544
|
+
var CONVERSION_ROUTING_ENV = [...GOOGLE_ROUTING_ENV];
|
|
8376
8545
|
var CONVERSION_CLAIM_MS = 30 * 60000;
|
|
8377
8546
|
var PERMANENT_FIRST_PURCHASE_SKIPS = new Set([
|
|
8378
8547
|
"outside_click_window",
|
|
@@ -8385,6 +8554,19 @@ var EXPERIMENTS = {};
|
|
|
8385
8554
|
var EXPERIMENT_KEYS = Object.keys(EXPERIMENTS);
|
|
8386
8555
|
var DEFAULT_EXPERIMENT_KEY = EXPERIMENT_KEYS[0] ?? null;
|
|
8387
8556
|
var EXPERIMENT_PHASES = ["before", "spending", "reading_payment", "payment_read", "tail_read"];
|
|
8557
|
+
// ../../packages/growth/src/funnel.ts
|
|
8558
|
+
var PIPELINE_STAGES = [
|
|
8559
|
+
"site_visits",
|
|
8560
|
+
"go_visits",
|
|
8561
|
+
"signups",
|
|
8562
|
+
"payment_link",
|
|
8563
|
+
"page_opened",
|
|
8564
|
+
"checkout_started",
|
|
8565
|
+
"paid",
|
|
8566
|
+
"first_call",
|
|
8567
|
+
"dashboard_opened"
|
|
8568
|
+
];
|
|
8569
|
+
var PIPELINE_ACCOUNT_STAGES = PIPELINE_STAGES.filter((s) => s !== "site_visits" && s !== "go_visits");
|
|
8388
8570
|
// ../../packages/growth/src/go-hits.ts
|
|
8389
8571
|
var GO_HIT_KINDS = ["human", "agent"];
|
|
8390
8572
|
var GoHit = object({
|
|
@@ -8441,8 +8623,8 @@ var EthicalAdsCredentials = object({
|
|
|
8441
8623
|
api_key: string2().min(1),
|
|
8442
8624
|
advertiser: string2().trim().regex(/^[a-z0-9_-]{1,100}$/i)
|
|
8443
8625
|
});
|
|
8444
|
-
var
|
|
8445
|
-
var ethicalAdsApiAccount = (creds) =>
|
|
8626
|
+
var credentials2 = (creds) => parseCredentialsJson(creds[ENV7], EthicalAdsCredentials);
|
|
8627
|
+
var ethicalAdsApiAccount = (creds) => credentials2(creds).advertiser;
|
|
8446
8628
|
var Num3 = union([number2(), string2()]).nullable().optional();
|
|
8447
8629
|
var Day3 = object({ date: string2().optional(), index: string2().optional(), views: Num3, clicks: Num3, cost: Num3 });
|
|
8448
8630
|
var Report = object({
|
|
@@ -8456,7 +8638,7 @@ var Report = object({
|
|
|
8456
8638
|
var dayOf2 = (d) => (d.date ?? d.index ?? "").slice(0, 10);
|
|
8457
8639
|
async function pullEthicalAds(range, ctx) {
|
|
8458
8640
|
checkRange(range);
|
|
8459
|
-
const c =
|
|
8641
|
+
const c = credentials2(ctx.credentials);
|
|
8460
8642
|
const q = new URLSearchParams({ start_date: range.from, end_date: range.to });
|
|
8461
8643
|
const report = await httpJson(ctx.fetch, `${API3}/advertisers/${encodeURIComponent(c.advertiser)}/report/?${q}`, Report, { headers: { authorization: `Token ${c.api_key}` } });
|
|
8462
8644
|
const rows = [];
|
|
@@ -8519,7 +8701,7 @@ var googleAds = defineCsvSource({
|
|
|
8519
8701
|
normalizeAccountId: googleAccountId,
|
|
8520
8702
|
totalPrefix: "total",
|
|
8521
8703
|
accountTotalLabel: "total: account",
|
|
8522
|
-
exportHint: "In Google Ads, open Campaigns > Ad groups,
|
|
8704
|
+
exportHint: "In Google Ads, open Campaigns > Ad groups, choose the dates, Segment > Time > Day, add the Campaign ID and Ad " + "group ID columns (Attributes), remove filters, then Download > .csv. Give the ad account id (123-456-7890, top " + "right) on import: the table has no Customer ID column."
|
|
8523
8705
|
});
|
|
8524
8706
|
|
|
8525
8707
|
// ../../packages/growth/src/platforms/linkedin.ts
|
|
@@ -8562,8 +8744,8 @@ var LinkedInCredentials = object({
|
|
|
8562
8744
|
client_secret: string2().min(1).optional(),
|
|
8563
8745
|
refresh_token: string2().min(1).optional()
|
|
8564
8746
|
}).refine((c) => c.access_token || c.client_id && c.client_secret && c.refresh_token);
|
|
8565
|
-
var
|
|
8566
|
-
var linkedInApiAccount = (creds) =>
|
|
8747
|
+
var credentials3 = (creds) => parseCredentialsJson(creds[ENV8], LinkedInCredentials);
|
|
8748
|
+
var linkedInApiAccount = (creds) => credentials3(creds).account_id;
|
|
8567
8749
|
var Token3 = object({ access_token: string2().min(1) });
|
|
8568
8750
|
async function accessToken(c, f) {
|
|
8569
8751
|
if (!c.refresh_token)
|
|
@@ -8614,7 +8796,7 @@ var Element = object({
|
|
|
8614
8796
|
var Analytics = object({ elements: array(Element) });
|
|
8615
8797
|
async function pullLinkedInAds(range, ctx) {
|
|
8616
8798
|
checkRange(range);
|
|
8617
|
-
const c =
|
|
8799
|
+
const c = credentials3(ctx.credentials);
|
|
8618
8800
|
const token = await accessToken(c, ctx.fetch);
|
|
8619
8801
|
const headers = {
|
|
8620
8802
|
authorization: `Bearer ${token}`,
|
|
@@ -8695,8 +8877,8 @@ var MetaCredentials = object({
|
|
|
8695
8877
|
access_token: string2().min(1),
|
|
8696
8878
|
ad_account_id: string2().trim().regex(/^(act_)?\d{1,30}$/).transform((s) => s.replace(/^act_/, ""))
|
|
8697
8879
|
});
|
|
8698
|
-
var
|
|
8699
|
-
var metaApiAccount = (creds) =>
|
|
8880
|
+
var credentials4 = (creds) => parseCredentialsJson(creds[ENV9], MetaCredentials);
|
|
8881
|
+
var metaApiAccount = (creds) => credentials4(creds).ad_account_id;
|
|
8700
8882
|
var Account3 = object({ currency: string2() });
|
|
8701
8883
|
var Insight = object({
|
|
8702
8884
|
date_start: string2(),
|
|
@@ -8746,7 +8928,7 @@ function nextUrl(next) {
|
|
|
8746
8928
|
}
|
|
8747
8929
|
async function pullMetaAds(range, ctx) {
|
|
8748
8930
|
checkRange(range);
|
|
8749
|
-
const c =
|
|
8931
|
+
const c = credentials4(ctx.credentials);
|
|
8750
8932
|
const account = await metaJson(ctx.fetch, `${API5}/act_${c.ad_account_id}?fields=currency`, c.access_token, Account3);
|
|
8751
8933
|
assertUsd(account.currency);
|
|
8752
8934
|
const q = new URLSearchParams({
|
|
@@ -8818,8 +9000,8 @@ var RedditCredentials = object({
|
|
|
8818
9000
|
ad_account_id: string2().trim().regex(/^[A-Za-z0-9_-]{1,64}$/),
|
|
8819
9001
|
user_agent: string2().min(1).max(200).optional()
|
|
8820
9002
|
});
|
|
8821
|
-
var
|
|
8822
|
-
var redditApiAccount = (creds) =>
|
|
9003
|
+
var credentials5 = (creds) => parseCredentialsJson(creds[ENV10], RedditCredentials);
|
|
9004
|
+
var redditApiAccount = (creds) => credentials5(creds).ad_account_id;
|
|
8823
9005
|
var Token4 = object({ access_token: string2().min(1) });
|
|
8824
9006
|
async function accessToken2(c, f) {
|
|
8825
9007
|
try {
|
|
@@ -8873,7 +9055,7 @@ function nextUrl2(next) {
|
|
|
8873
9055
|
}
|
|
8874
9056
|
async function pullRedditAds(range, ctx) {
|
|
8875
9057
|
checkRange(range);
|
|
8876
|
-
const c =
|
|
9058
|
+
const c = credentials5(ctx.credentials);
|
|
8877
9059
|
const token = await accessToken2(c, ctx.fetch);
|
|
8878
9060
|
const headers = { authorization: `Bearer ${token}`, "user-agent": c.user_agent ?? DEFAULT_USER_AGENT };
|
|
8879
9061
|
const acct = encodeURIComponent(c.ad_account_id);
|
|
@@ -8966,8 +9148,8 @@ var XCredentials = object({
|
|
|
8966
9148
|
access_token_secret: string2().min(1),
|
|
8967
9149
|
account_id: string2().trim().regex(/^[A-Za-z0-9]{1,32}$/)
|
|
8968
9150
|
});
|
|
8969
|
-
var
|
|
8970
|
-
var xApiAccount = (creds) =>
|
|
9151
|
+
var credentials6 = (creds) => parseCredentialsJson(creds[ENV11], XCredentials);
|
|
9152
|
+
var xApiAccount = (creds) => credentials6(creds).account_id;
|
|
8971
9153
|
function zonedMidnightUtc(day, tz) {
|
|
8972
9154
|
const fmt = new Intl.DateTimeFormat("en-US", {
|
|
8973
9155
|
timeZone: tz,
|
|
@@ -9000,13 +9182,18 @@ var Stats = object({
|
|
|
9000
9182
|
data: array(object({
|
|
9001
9183
|
id: string2(),
|
|
9002
9184
|
id_data: array(object({
|
|
9003
|
-
metrics: object({
|
|
9185
|
+
metrics: object({
|
|
9186
|
+
impressions: Series,
|
|
9187
|
+
link_clicks: Series,
|
|
9188
|
+
url_clicks: Series,
|
|
9189
|
+
billed_charge_local_micro: Series
|
|
9190
|
+
})
|
|
9004
9191
|
}))
|
|
9005
9192
|
}))
|
|
9006
9193
|
});
|
|
9007
9194
|
async function pullXAds(range, ctx) {
|
|
9008
9195
|
checkRange(range);
|
|
9009
|
-
const c =
|
|
9196
|
+
const c = credentials6(ctx.credentials);
|
|
9010
9197
|
const oauth = {
|
|
9011
9198
|
consumerKey: c.consumer_key,
|
|
9012
9199
|
consumerSecret: c.consumer_secret,
|
|
@@ -9065,6 +9252,7 @@ async function pullXAds(range, ctx) {
|
|
|
9065
9252
|
const campaignId = li?.campaign_id ?? d.id;
|
|
9066
9253
|
for (const s of d.id_data) {
|
|
9067
9254
|
const m = s.metrics;
|
|
9255
|
+
const linkClicks = m.link_clicks ?? m.url_clicks;
|
|
9068
9256
|
for (let k = 0;k < days; k++) {
|
|
9069
9257
|
rows.push(row("x", {
|
|
9070
9258
|
day: addDays(start, k),
|
|
@@ -9073,7 +9261,7 @@ async function pullXAds(range, ctx) {
|
|
|
9073
9261
|
adGroupId: d.id,
|
|
9074
9262
|
names: { campaign: campaigns.get(campaignId)?.name ?? null, adGroup: li?.name ?? null, ad: null },
|
|
9075
9263
|
impressions: count(m.impressions?.[k]),
|
|
9076
|
-
clicks: count(
|
|
9264
|
+
clicks: count(linkClicks?.[k]),
|
|
9077
9265
|
spendMicros: m.billed_charge_local_micro?.[k] == null ? 0 : intMicros(m.billed_charge_local_micro[k])
|
|
9078
9266
|
}));
|
|
9079
9267
|
}
|
|
@@ -9089,7 +9277,10 @@ async function pullXAds(range, ctx) {
|
|
|
9089
9277
|
function withApi(source, platform, pull, apiAccount) {
|
|
9090
9278
|
return { ...source, pull, apiAccount, credentialEnv: [SPEND_CREDENTIAL_ENV[platform]] };
|
|
9091
9279
|
}
|
|
9092
|
-
var googleAds2 =
|
|
9280
|
+
var googleAds2 = {
|
|
9281
|
+
...withApi(googleAds, "google", pullGoogleAds, googleApiAccount),
|
|
9282
|
+
apiAccounts: googleApiAccounts
|
|
9283
|
+
};
|
|
9093
9284
|
var redditAds2 = withApi(redditAds, "reddit", pullRedditAds, redditApiAccount);
|
|
9094
9285
|
var xAds2 = withApi(xAds, "x", pullXAds, xApiAccount);
|
|
9095
9286
|
var ethicalAds2 = withApi(ethicalAds, "ethicalads", pullEthicalAds, ethicalAdsApiAccount);
|
|
@@ -9288,8 +9479,34 @@ var GrowthResults = object({
|
|
|
9288
9479
|
summary: string2(),
|
|
9289
9480
|
decisions: array(GrowthDecision)
|
|
9290
9481
|
});
|
|
9482
|
+
// ../../packages/growth/src/site-visits.ts
|
|
9483
|
+
var SITE_PATH_RE = /^\/[a-z0-9._~/-]{0,99}$/;
|
|
9484
|
+
var OTHER_PATH = "(other)";
|
|
9485
|
+
var SECTION_OTHER_RE = /^\/[a-z0-9._~-]{1,80}\/\(other\)$/;
|
|
9486
|
+
var SOURCE_RE = /^(?=.{1,100}$)[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
|
|
9487
|
+
var TAG_SOURCE_RE = /^tag:[a-z0-9][a-z0-9._-]{0,39}$/;
|
|
9488
|
+
var COUNTRY_RE = /^[A-Z]{2}$/;
|
|
9489
|
+
var SiteVisit = object({
|
|
9490
|
+
path: union([string2().regex(SITE_PATH_RE), string2().regex(SECTION_OTHER_RE), literal(OTHER_PATH)]),
|
|
9491
|
+
source: union([
|
|
9492
|
+
string2().regex(SOURCE_RE),
|
|
9493
|
+
string2().regex(TAG_SOURCE_RE),
|
|
9494
|
+
literal("direct"),
|
|
9495
|
+
literal("internal")
|
|
9496
|
+
]),
|
|
9497
|
+
country: string2().regex(COUNTRY_RE),
|
|
9498
|
+
kind: _enum(["human", "agent"])
|
|
9499
|
+
}).strict();
|
|
9500
|
+
var SiteVisitHit = object({ visit: SiteVisit }).strict();
|
|
9501
|
+
var IP_RE = /^[0-9a-fA-F:.]{2,45}$/;
|
|
9502
|
+
var SiteTouch = object({
|
|
9503
|
+
ip: string2().regex(IP_RE),
|
|
9504
|
+
path: SiteVisit.shape.path,
|
|
9505
|
+
source: SiteVisit.shape.source
|
|
9506
|
+
}).strict();
|
|
9507
|
+
var SiteTouchHit = object({ touch: SiteTouch }).strict();
|
|
9291
9508
|
// ../../packages/schemas/src/legal.ts
|
|
9292
|
-
var TERMS_VERSION = "2026-10-
|
|
9509
|
+
var TERMS_VERSION = "2026-10-12";
|
|
9293
9510
|
var LEGAL_SITE = "https://scaledthought.com";
|
|
9294
9511
|
var LEGAL_PATHS = {
|
|
9295
9512
|
index: "/legal",
|
|
@@ -9805,11 +10022,22 @@ var CREDIT_STEPS = [
|
|
|
9805
10022
|
`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
10023
|
"4. Then make the call below."
|
|
9807
10024
|
];
|
|
10025
|
+
var OWNER_EMAIL_QUESTION = "What email should own this account? You'll use it to sign in to the ScaledThought dashboard (billing, credits, keys). We only send it account email, such as this confirmation, sign-in links and security notices, never marketing.";
|
|
10026
|
+
var OWNER_EMAIL_COMMAND = `${NPX} account email set <their email>`;
|
|
10027
|
+
var OWNER_EMAIL_STEP = `This account has no owner email yet. Ask your human one question: "${OWNER_EMAIL_QUESTION}" Then run: ${OWNER_EMAIL_COMMAND} (we email them one confirmation link; relay message_for_human). If they'd rather not, carry on without it.`;
|
|
10028
|
+
function ownerEmailFields(given, r) {
|
|
10029
|
+
const owner = r.owner_email ?? (r.email_confirmation === "sent" ? "pending" : given ? undefined : "none");
|
|
10030
|
+
return {
|
|
10031
|
+
...owner ? { owner_email: owner } : {},
|
|
10032
|
+
...r.email_confirmation ? { email_confirmation: r.email_confirmation } : {},
|
|
10033
|
+
...r.message_for_human ? { message_for_human: r.message_for_human } : {},
|
|
10034
|
+
...owner === "none" ? { missing: ["owner_email"], owner_email_hint: OWNER_EMAIL_STEP } : {}
|
|
10035
|
+
};
|
|
10036
|
+
}
|
|
9808
10037
|
function nextSteps(model, needsCredits) {
|
|
9809
10038
|
return [
|
|
9810
10039
|
...needsCredits ? CREDIT_STEPS : [],
|
|
9811
10040
|
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
10041
|
"Use it from code: any OpenAI SDK with base_url=$ST_BASE_URL and api_key=$ST_API_KEY (both are in .env)",
|
|
9814
10042
|
`Optional, connect MCP: ${NPX} mcp`,
|
|
9815
10043
|
...needsCredits ? [] : [`More credits when needed: ${TOPUP_COMMAND} returns an approval_url for your human (inference is prepaid)`]
|
|
@@ -9831,7 +10059,7 @@ async function liveSummary(ctx, region, balanceUsd) {
|
|
|
9831
10059
|
return {
|
|
9832
10060
|
balance_usd: balanceUsd ?? 0,
|
|
9833
10061
|
needs_credits: needsCredits,
|
|
9834
|
-
next_command: needsCredits ? TOPUP_COMMAND : `${NPX} chat -m ${suggested
|
|
10062
|
+
next_command: needsCredits ? TOPUP_COMMAND : suggested ? `${NPX} chat -m ${suggested} "Reply with OK"` : `${NPX} models --live`,
|
|
9835
10063
|
newest_models: live.slice(0, 5),
|
|
9836
10064
|
suggested_model: suggested,
|
|
9837
10065
|
next_steps: nextSteps(suggested, needsCredits)
|
|
@@ -9872,13 +10100,26 @@ async function init(ctx, opts) {
|
|
|
9872
10100
|
const status = await account(ctx);
|
|
9873
10101
|
const ignored = [opts.source !== undefined && "--source", opts.adClick !== undefined && "--ad-click"].filter(Boolean);
|
|
9874
10102
|
const shown = withoutFreeFields(status);
|
|
10103
|
+
const summary = await liveSummary(ctx, region, balanceOf(status));
|
|
10104
|
+
const noOwner = status.owner_email === "none";
|
|
10105
|
+
const warnings = [
|
|
10106
|
+
...ignored.length ? [`${ignored.join(" and ")} apply only to a new account and were not sent.`] : [],
|
|
10107
|
+
...opts.email !== undefined ? [
|
|
10108
|
+
`--email applies only to a new account and was not sent. To name this account's owner, run: ${OWNER_EMAIL_COMMAND}`
|
|
10109
|
+
] : []
|
|
10110
|
+
];
|
|
9875
10111
|
return {
|
|
9876
10112
|
already_initialized: true,
|
|
9877
10113
|
...shown,
|
|
9878
10114
|
key_saved_to: where,
|
|
9879
10115
|
terms: termsOf(),
|
|
9880
|
-
...
|
|
9881
|
-
...
|
|
10116
|
+
...summary,
|
|
10117
|
+
...noOwner ? {
|
|
10118
|
+
missing: ["owner_email"],
|
|
10119
|
+
owner_email_hint: OWNER_EMAIL_STEP,
|
|
10120
|
+
next_steps: [OWNER_EMAIL_STEP, ...summary.next_steps]
|
|
10121
|
+
} : {},
|
|
10122
|
+
...warnings.length ? { warnings } : {}
|
|
9882
10123
|
};
|
|
9883
10124
|
}
|
|
9884
10125
|
if (opts.account && opts.newAccount)
|
|
@@ -9906,6 +10147,8 @@ async function init(ctx, opts) {
|
|
|
9906
10147
|
const { saved, existingCode } = await afterNewAccount(ctx, { ...acct, name: opts.name }, { noSave: opts.noSave });
|
|
9907
10148
|
const keyed = { ...ctx, env: { ...ctx.env, ST_API_KEY: acct.api_key } };
|
|
9908
10149
|
const billing = await api(keyed, "GET", "/v1/billing").catch(() => null);
|
|
10150
|
+
const owner = ownerEmailFields(opts.email !== undefined, acct);
|
|
10151
|
+
const summary = await liveSummary(keyed, acct.region, balanceOf(billing));
|
|
9909
10152
|
return {
|
|
9910
10153
|
already_initialized: false,
|
|
9911
10154
|
account_id: acct.account_id,
|
|
@@ -9919,7 +10162,9 @@ async function init(ctx, opts) {
|
|
|
9919
10162
|
...existingCode ? { existing_account_code: existingCode, existing_account_hint: existingAccountHint(existingCode) } : {},
|
|
9920
10163
|
terms: termsOf(acct),
|
|
9921
10164
|
...attribution.source ? { source: acct.source ?? attribution.source } : {},
|
|
9922
|
-
...
|
|
10165
|
+
...owner,
|
|
10166
|
+
...summary,
|
|
10167
|
+
...owner.missing ? { next_steps: [OWNER_EMAIL_STEP, ...summary.next_steps] } : {},
|
|
9923
10168
|
...attribution.warnings.length ? { warnings: attribution.warnings } : {}
|
|
9924
10169
|
};
|
|
9925
10170
|
}
|
|
@@ -10020,6 +10265,12 @@ async function accountDelete(ctx, o) {
|
|
|
10020
10265
|
throw new CliError("bad_argument", "--confirm must be your account id (acct_...).", "Find it with `account`.");
|
|
10021
10266
|
return api(ctx, "DELETE", "/v1/account", { body: { confirm: o.confirm } });
|
|
10022
10267
|
}
|
|
10268
|
+
async function accountEmailSet(ctx, address) {
|
|
10269
|
+
const email = address.trim();
|
|
10270
|
+
if (!email.includes("@"))
|
|
10271
|
+
throw new CliError("bad_argument", `"${address}" isn't an email address.`, `Ask your human for their email, then run: ${OWNER_EMAIL_COMMAND}`);
|
|
10272
|
+
return api(ctx, "PUT", "/v1/account/email", { body: { email } });
|
|
10273
|
+
}
|
|
10023
10274
|
async function accountAdMeasurement(ctx, state) {
|
|
10024
10275
|
const v = state.trim().toLowerCase();
|
|
10025
10276
|
if (v !== "on" && v !== "off")
|
|
@@ -10051,12 +10302,15 @@ var keysRotate = async (ctx, id, o = {}) => api(ctx, "POST", `/v1/keys/${encodeU
|
|
|
10051
10302
|
var keysRevokeSelf = (ctx) => api(ctx, "DELETE", "/v1/keys/self");
|
|
10052
10303
|
var usage = (ctx, o) => api(ctx, "GET", `/v1/usage?days=${o.days}&group_by=${encodeURIComponent(o.by)}`);
|
|
10053
10304
|
async function chat(ctx, o) {
|
|
10305
|
+
const requested = o.model?.trim();
|
|
10306
|
+
if (!requested)
|
|
10307
|
+
throw new CliError("model_required", 'chat needs a model id: chat -m <id> "your message".');
|
|
10054
10308
|
const { key, region } = resolveKey(ctx);
|
|
10055
10309
|
const res = await ctx.fetch(`${inferenceUrl(region, ctx.env)}/chat/completions`, {
|
|
10056
10310
|
method: "POST",
|
|
10057
10311
|
headers: { authorization: `Bearer ${key}`, "content-type": "application/json", "user-agent": USER_AGENT },
|
|
10058
10312
|
body: JSON.stringify({
|
|
10059
|
-
model:
|
|
10313
|
+
model: requested,
|
|
10060
10314
|
messages: [{ role: "user", content: o.prompt }],
|
|
10061
10315
|
stream: true,
|
|
10062
10316
|
stream_options: { include_usage: true }
|
|
@@ -10066,7 +10320,7 @@ async function chat(ctx, o) {
|
|
|
10066
10320
|
throw await responseError(res);
|
|
10067
10321
|
let text = "";
|
|
10068
10322
|
let usageOut = null;
|
|
10069
|
-
let model =
|
|
10323
|
+
let model = requested;
|
|
10070
10324
|
const decoder = new TextDecoder;
|
|
10071
10325
|
let buf = "";
|
|
10072
10326
|
for await (const chunk of res.body) {
|
|
@@ -10097,7 +10351,6 @@ async function chat(ctx, o) {
|
|
|
10097
10351
|
}
|
|
10098
10352
|
return {
|
|
10099
10353
|
model,
|
|
10100
|
-
routed_to: res.headers.get("x-st-routed-to"),
|
|
10101
10354
|
region: res.headers.get("x-st-region"),
|
|
10102
10355
|
request_id: res.headers.get("x-st-request-id"),
|
|
10103
10356
|
text,
|
|
@@ -11317,7 +11570,9 @@ async function connect(ctx, tool, o = {}) {
|
|
|
11317
11570
|
const { region } = resolveKey(ctx);
|
|
11318
11571
|
const baseUrl = inferenceUrl(region, ctx.env);
|
|
11319
11572
|
const models = await liveChatModels(ctx);
|
|
11320
|
-
const model = o.model ?? models[0]?.id
|
|
11573
|
+
const model = o.model ?? models[0]?.id;
|
|
11574
|
+
if (!model)
|
|
11575
|
+
throw new CliError("no_live_model", "No chat model is live in this region right now, so there is nothing to configure. Nothing was changed.", "Check `models --live` again later, or pass --model <id>.");
|
|
11321
11576
|
if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(model))
|
|
11322
11577
|
throw new CliError("bad_argument", "--model isn't a model id.", "Pick one from `models --live`.");
|
|
11323
11578
|
const host = machineName();
|
|
@@ -11345,7 +11600,7 @@ async function connect(ctx, tool, o = {}) {
|
|
|
11345
11600
|
daily_budget_usd: k.daily_budget_usd,
|
|
11346
11601
|
configured: ".env (OPENAI_API_BASE, OPENAI_API_KEY, AIDER_MODEL), mode 0600, git-ignored",
|
|
11347
11602
|
model: `openai/${model}`,
|
|
11348
|
-
models:
|
|
11603
|
+
models: models.map((m) => m.id),
|
|
11349
11604
|
next: "Run `aider` in this directory. Aider may warn that it doesn't know the model's context window; that's harmless."
|
|
11350
11605
|
};
|
|
11351
11606
|
}
|
|
@@ -11355,7 +11610,7 @@ async function connect(ctx, tool, o = {}) {
|
|
|
11355
11610
|
const config = readJsonObject(configPath, "opencode.json");
|
|
11356
11611
|
const auth = readJsonObject(authPath, authPath);
|
|
11357
11612
|
const providers = config.provider && typeof config.provider === "object" ? config.provider : {};
|
|
11358
|
-
const modelEntries = {
|
|
11613
|
+
const modelEntries = {};
|
|
11359
11614
|
for (const m of models)
|
|
11360
11615
|
modelEntries[m.id] = {
|
|
11361
11616
|
name: m.id,
|
|
@@ -11396,11 +11651,11 @@ async function connect(ctx, tool, o = {}) {
|
|
|
11396
11651
|
model: `${PROVIDER_ID}/${model}`,
|
|
11397
11652
|
models: Object.keys(modelEntries),
|
|
11398
11653
|
restart_needed: true,
|
|
11399
|
-
next: `Restart OpenCode in this directory and pick a ${PROVIDER_ID}/… model
|
|
11654
|
+
next: `Restart OpenCode in this directory and pick a ${PROVIDER_ID}/… model.`
|
|
11400
11655
|
};
|
|
11401
11656
|
}
|
|
11402
11657
|
function instructions(tool, baseUrl, model, models, name) {
|
|
11403
|
-
const ids = [
|
|
11658
|
+
const ids = models.some((m) => m.id === model) ? models.map((m) => m.id) : [model, ...models.map((m) => m.id)];
|
|
11404
11659
|
if (tool === "cursor")
|
|
11405
11660
|
return {
|
|
11406
11661
|
tool,
|
|
@@ -11665,7 +11920,10 @@ var init2 = defineCommand({
|
|
|
11665
11920
|
...jsonFlag,
|
|
11666
11921
|
region: { type: "string", description: "Home region for your data: us (default) or ca. Permanent." },
|
|
11667
11922
|
name: { type: "string", description: "Account name (optional)" },
|
|
11668
|
-
email: {
|
|
11923
|
+
email: {
|
|
11924
|
+
type: "string",
|
|
11925
|
+
description: "Your human's email, to own the account: ask them first. We email it one confirmation link; once they click it they sign in to the dashboard. Account email only, never marketing (optional; later: account email set)"
|
|
11926
|
+
},
|
|
11669
11927
|
source: {
|
|
11670
11928
|
type: "string",
|
|
11671
11929
|
description: "Campaign code from the scaledthought.com/go/<code> link that brought you here, e.g. a-rd (optional; stored on the new account)"
|
|
@@ -11786,9 +12044,9 @@ var models = defineCommand({
|
|
|
11786
12044
|
});
|
|
11787
12045
|
}, args)
|
|
11788
12046
|
});
|
|
11789
|
-
var ACCOUNT_SUBCOMMANDS = new Set(["delete", "ad-measurement"]);
|
|
12047
|
+
var ACCOUNT_SUBCOMMANDS = new Set(["delete", "ad-measurement", "email"]);
|
|
11790
12048
|
var account2 = defineCommand({
|
|
11791
|
-
meta: { name: "account", description: "Region, spend, credit balance
|
|
12049
|
+
meta: { name: "account", description: "Region, spend, credit balance, auto-reload and owner_email" },
|
|
11792
12050
|
args: jsonFlag,
|
|
11793
12051
|
run: ({ args, rawArgs }) => {
|
|
11794
12052
|
if (rawArgs.some((a) => ACCOUNT_SUBCOMMANDS.has(a)))
|
|
@@ -11817,6 +12075,22 @@ var account2 = defineCommand({
|
|
|
11817
12075
|
state: { type: "positional", required: true, description: "off or on" }
|
|
11818
12076
|
},
|
|
11819
12077
|
run: ({ args }) => run2(() => accountAdMeasurement(makeCtx(), args.state), args)
|
|
12078
|
+
}),
|
|
12079
|
+
email: defineCommand({
|
|
12080
|
+
meta: { name: "email", description: "The email that owns this account (confirmed by a link we email them)" },
|
|
12081
|
+
subCommands: {
|
|
12082
|
+
set: defineCommand({
|
|
12083
|
+
meta: {
|
|
12084
|
+
name: "set",
|
|
12085
|
+
description: "Name your human as the account's owner: we email them one confirmation link and they own it once they click. Ask them first; account email only, never marketing. Before anyone has signed in or paid"
|
|
12086
|
+
},
|
|
12087
|
+
args: {
|
|
12088
|
+
...jsonFlag,
|
|
12089
|
+
address: { type: "positional", required: true, description: "Their email address" }
|
|
12090
|
+
},
|
|
12091
|
+
run: ({ args }) => run2(() => accountEmailSet(makeCtx(), args.address), args)
|
|
12092
|
+
})
|
|
12093
|
+
}
|
|
11820
12094
|
})
|
|
11821
12095
|
}
|
|
11822
12096
|
});
|
|
@@ -11956,10 +12230,10 @@ var usage3 = defineCommand({
|
|
|
11956
12230
|
run: ({ args }) => run2(() => usage(makeCtx(), { days: num("days", args.days) ?? 7, by: args.by }), args)
|
|
11957
12231
|
});
|
|
11958
12232
|
var chat2 = defineCommand({
|
|
11959
|
-
meta: { name: "chat", description:
|
|
12233
|
+
meta: { name: "chat", description: 'Send one message to a model: chat -m <id> "…" (ids from `models --live`)' },
|
|
11960
12234
|
args: {
|
|
11961
12235
|
...jsonFlag,
|
|
11962
|
-
model: { type: "string", alias: "m",
|
|
12236
|
+
model: { type: "string", alias: "m", description: "Model id, required (from `models --live`)" },
|
|
11963
12237
|
prompt: { type: "positional", required: true, description: "The message" }
|
|
11964
12238
|
},
|
|
11965
12239
|
run: async ({ args }) => {
|
|
@@ -12223,7 +12497,10 @@ var billing = defineCommand({
|
|
|
12223
12497
|
}
|
|
12224
12498
|
});
|
|
12225
12499
|
var batches = defineCommand({
|
|
12226
|
-
meta: {
|
|
12500
|
+
meta: {
|
|
12501
|
+
name: "batches",
|
|
12502
|
+
description: "Batch inference at the normal per-token price (OpenAI-compatible). Paid from prepaid credits"
|
|
12503
|
+
},
|
|
12227
12504
|
subCommands: {
|
|
12228
12505
|
create: defineCommand({
|
|
12229
12506
|
meta: { name: "create", description: "Upload a JSONL file and start a batch" },
|
|
@@ -12447,7 +12724,7 @@ var mcp = defineCommand({
|
|
|
12447
12724
|
var dashboard = defineCommand({
|
|
12448
12725
|
meta: {
|
|
12449
12726
|
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"
|
|
12727
|
+
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 (after they've confirmed their email)"
|
|
12451
12728
|
},
|
|
12452
12729
|
args: {
|
|
12453
12730
|
...jsonFlag,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scaledthought",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.3",
|
|
4
|
+
"description": "OpenAI-compatible inference for open-weight models, set up by your coding agent. Prepaid, pay per token, no subscription. US live; Canada coming soon.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"llm",
|
|
@@ -10,18 +10,22 @@
|
|
|
10
10
|
"openai-compatible",
|
|
11
11
|
"mcp",
|
|
12
12
|
"agents",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
13
|
+
"coding-agents",
|
|
14
|
+
"claude-code",
|
|
15
|
+
"llm-api",
|
|
16
|
+
"inference-api",
|
|
17
|
+
"open-models",
|
|
18
|
+
"mcp-server",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"cli"
|
|
16
21
|
],
|
|
17
|
-
"homepage": "https://scaledthought.com",
|
|
22
|
+
"homepage": "https://scaledthought.com/go/org-npm",
|
|
18
23
|
"bugs": {
|
|
19
|
-
"url": "https://github.com/Scaled-Thought/scaledthought/issues"
|
|
24
|
+
"url": "https://github.com/Scaled-Thought/scaledthought-mcp/issues"
|
|
20
25
|
},
|
|
21
26
|
"repository": {
|
|
22
27
|
"type": "git",
|
|
23
|
-
"url": "git+https://github.com/Scaled-Thought/scaledthought.git"
|
|
24
|
-
"directory": "apps/cli"
|
|
28
|
+
"url": "git+https://github.com/Scaled-Thought/scaledthought-mcp.git"
|
|
25
29
|
},
|
|
26
30
|
"type": "module",
|
|
27
31
|
"bin": {
|