scaledthought 0.3.2 → 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.
Files changed (3) hide show
  1. package/README.md +15 -5
  2. package/dist/index.js +334 -61
  3. 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, hosted in the US or Canada. It's built for coding agents: it never prompts, prints
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 # or: --region ca (data stays in Canada)
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). It's optional, and a value that isn't a
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`.
@@ -43,7 +52,8 @@ ends, so the link would reach the human only after the payment it is waiting for
43
52
  npx -y scaledthought@0.3 models --live # models serving in your region now
44
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.2",
557
- description: "Headless open-model inference, run by your agent. Prepaid, pay per token. US or Canada.",
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
- "cli",
567
- "canada",
568
- "data-residency"
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.",
@@ -6513,6 +6518,14 @@ function object(shape, params) {
6513
6518
  };
6514
6519
  return new ZodObject(def);
6515
6520
  }
6521
+ function strictObject(shape, params) {
6522
+ return new ZodObject({
6523
+ type: "object",
6524
+ shape,
6525
+ catchall: never(),
6526
+ ...normalizeParams(params)
6527
+ });
6528
+ }
6516
6529
  var ZodUnion = /* @__PURE__ */ $constructor("ZodUnion", (inst, def) => {
6517
6530
  $ZodUnion.init(inst, def);
6518
6531
  ZodType.init(inst, def);
@@ -7150,6 +7163,11 @@ function parseWith(def, text, opts) {
7150
7163
  skipped++;
7151
7164
  return;
7152
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
+ }
7153
7171
  const rawDay = cell(r, "day") ?? "";
7154
7172
  const day = parseReportDay(rawDay);
7155
7173
  if (!day) {
@@ -7401,10 +7419,62 @@ var GoogleCredentials = object({
7401
7419
  refresh_token: string2().min(1).optional(),
7402
7420
  service_account: object({ client_email: string2().email(), private_key: string2().min(1) }).optional()
7403
7421
  }).refine((c) => c.service_account || c.client_id && c.client_secret && c.refresh_token);
7404
- var credentials = (ctx) => parseCredentialsJson(ctx.credentials[ENV], GoogleCredentials);
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
+ }
7405
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
+ }
7406
7476
  function googleApiAccount(creds) {
7407
- return hyphenCustomerId(credentials({ credentials: creds }).customer_id);
7477
+ return googleApiAccounts(creds)[0];
7408
7478
  }
7409
7479
  var Token = object({ access_token: string2().min(1) });
7410
7480
  async function googleAccessToken(c, f) {
@@ -7474,7 +7544,10 @@ async function search(c, token, query, f) {
7474
7544
  var FIELDS = "segments.date, customer.currency_code, campaign.id, campaign.name, metrics.impressions, metrics.clicks, " + "metrics.cost_micros, metrics.conversions";
7475
7545
  async function pullGoogleAds(range, ctx) {
7476
7546
  checkRange(range);
7477
- const c = credentials(ctx);
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");
7478
7551
  const token = await googleAccessToken(c, ctx.fetch);
7479
7552
  const where = `WHERE segments.date BETWEEN '${range.from}' AND '${range.to}'`;
7480
7553
  const adGroups = await search(c, token, `SELECT ${FIELDS}, ad_group.id, ad_group.name FROM ad_group ${where}`, ctx.fetch);
@@ -7750,8 +7823,8 @@ var MicrosoftCredentials = object({
7750
7823
  tenant: string2().regex(/^[A-Za-z0-9.-]{1,100}$/).default("common"),
7751
7824
  report_time_zone: string2().regex(/^[A-Za-z]{2,80}$/).optional()
7752
7825
  });
7753
- var credentials2 = (creds) => parseCredentialsJson(creds[ENV2], MicrosoftCredentials);
7754
- var microsoftApiAccount = (creds) => credentials2(creds).account_id;
7826
+ var credentials = (creds) => parseCredentialsJson(creds[ENV2], MicrosoftCredentials);
7827
+ var microsoftApiAccount = (creds) => credentials(creds).account_id;
7755
7828
  var Token2 = object({ access_token: string2().min(1) });
7756
7829
  async function microsoftAccessToken(c, f) {
7757
7830
  const form = new URLSearchParams({
@@ -7820,7 +7893,7 @@ var Polled = object({
7820
7893
  });
7821
7894
  async function pullMicrosoftAds(range, ctx) {
7822
7895
  checkRange(range);
7823
- const c = credentials2(ctx.credentials);
7896
+ const c = credentials(ctx.credentials);
7824
7897
  const token = await microsoftAccessToken(c, ctx.fetch);
7825
7898
  const headers = {
7826
7899
  authorization: `Bearer ${token}`,
@@ -7901,8 +7974,8 @@ var AD_WRITE_CREDENTIAL_ENV = {
7901
7974
  var AD_WRITE_CREDENTIAL_ENV_NAMES = Object.values(AD_WRITE_CREDENTIAL_ENV);
7902
7975
  // ../../packages/growth/src/portfolio.ts
7903
7976
  var PORTFOLIO_LIMITS = {
7904
- weeklyBudgetMicros: 1e9,
7905
- founders: ["alexey-scaled-throught", "josh-scaledthought"],
7977
+ weeklyBudgetMicros: 1500000000,
7978
+ founders: ["alexey-scaled", "alexey-scaled-throught", "josh-scaledthought"],
7906
7979
  effortDayMicros: 50000000
7907
7980
  };
7908
7981
  var FUNNEL_STAGES = ["click", "fetch", "signup", "activated", "paid"];
@@ -8083,18 +8156,94 @@ var ENV3 = {
8083
8156
  signupAction: "GOOGLE_ADS_SIGNUP_CONVERSION_ACTION_ID",
8084
8157
  purchaseAction: "GOOGLE_ADS_PURCHASE_CONVERSION_ACTION_ID",
8085
8158
  loginCustomerId: "GOOGLE_ADS_LOGIN_CUSTOMER_ID",
8086
- 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"
8087
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);
8088
8177
  var digits = (s) => (s ?? "").replace(/-/g, "");
8089
- function googleIngestBody(e, c) {
8090
- const customerId = digits(c[ENV3.customerId]);
8091
- const login = digits(c[ENV3.loginCustomerId]);
8092
- const action = e.kind === "signup" ? c[ENV3.signupAction] : c[ENV3.purchaseAction];
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;
8093
8242
  return {
8094
8243
  destinations: [
8095
8244
  {
8096
- operatingAccount: { accountType: "GOOGLE_ADS", accountId: customerId },
8097
- ...login ? { loginAccount: { accountType: "GOOGLE_ADS", accountId: login } } : {},
8245
+ operatingAccount: { accountType: "GOOGLE_ADS", accountId: d.customerId },
8246
+ ...d.loginCustomerId ? { loginAccount: { accountType: "GOOGLE_ADS", accountId: d.loginCustomerId } } : {},
8098
8247
  productDestinationId: action
8099
8248
  }
8100
8249
  ],
@@ -8121,22 +8270,40 @@ var googleSink = {
8121
8270
  ENV3.signupAction,
8122
8271
  ENV3.purchaseAction
8123
8272
  ],
8124
- optionalCredentialEnv: [ENV3.loginCustomerId, ENV3.validateOnly],
8273
+ optionalCredentialEnv: [ENV3.loginCustomerId, ENV3.validateOnly, ...GOOGLE_ROUTING_ENV],
8125
8274
  maxEventAgeDays: 90,
8126
8275
  clickWindowDays: 90,
8276
+ validate: validateGoogleConversionConfig,
8127
8277
  async send(events, { credentials: c, fetch: f }) {
8128
- const auth = await refreshAccessToken(f, "google", GOOGLE_TOKEN_URL, {
8129
- client_id: c[ENV3.clientId],
8130
- client_secret: c[ENV3.clientSecret],
8131
- refresh_token: c[ENV3.refreshToken]
8132
- });
8133
- if (!auth.ok)
8134
- return events.map(() => auth.result);
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
+ };
8135
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;
8136
8303
  const r = await request(f, GOOGLE_DATA_MANAGER_URL, {
8137
8304
  method: "POST",
8138
8305
  headers: { authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
8139
- body: JSON.stringify(googleIngestBody(e, c))
8306
+ body: JSON.stringify(googleIngestBody(e, c, dest))
8140
8307
  });
8141
8308
  if (!r.ok)
8142
8309
  return { status: "retry", errorCode: `google_${r.error}` };
@@ -8374,6 +8541,7 @@ var CONVERSION_SINKS = {
8374
8541
  var CONVERSION_CREDENTIAL_ENV = [
8375
8542
  ...new Set(Object.values(CONVERSION_SINKS).flatMap((s) => [...s.credentialEnv, ...s.optionalCredentialEnv ?? []]))
8376
8543
  ].sort();
8544
+ var CONVERSION_ROUTING_ENV = [...GOOGLE_ROUTING_ENV];
8377
8545
  var CONVERSION_CLAIM_MS = 30 * 60000;
8378
8546
  var PERMANENT_FIRST_PURCHASE_SKIPS = new Set([
8379
8547
  "outside_click_window",
@@ -8386,6 +8554,19 @@ var EXPERIMENTS = {};
8386
8554
  var EXPERIMENT_KEYS = Object.keys(EXPERIMENTS);
8387
8555
  var DEFAULT_EXPERIMENT_KEY = EXPERIMENT_KEYS[0] ?? null;
8388
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");
8389
8570
  // ../../packages/growth/src/go-hits.ts
8390
8571
  var GO_HIT_KINDS = ["human", "agent"];
8391
8572
  var GoHit = object({
@@ -8442,8 +8623,8 @@ var EthicalAdsCredentials = object({
8442
8623
  api_key: string2().min(1),
8443
8624
  advertiser: string2().trim().regex(/^[a-z0-9_-]{1,100}$/i)
8444
8625
  });
8445
- var credentials3 = (creds) => parseCredentialsJson(creds[ENV7], EthicalAdsCredentials);
8446
- var ethicalAdsApiAccount = (creds) => credentials3(creds).advertiser;
8626
+ var credentials2 = (creds) => parseCredentialsJson(creds[ENV7], EthicalAdsCredentials);
8627
+ var ethicalAdsApiAccount = (creds) => credentials2(creds).advertiser;
8447
8628
  var Num3 = union([number2(), string2()]).nullable().optional();
8448
8629
  var Day3 = object({ date: string2().optional(), index: string2().optional(), views: Num3, clicks: Num3, cost: Num3 });
8449
8630
  var Report = object({
@@ -8457,7 +8638,7 @@ var Report = object({
8457
8638
  var dayOf2 = (d) => (d.date ?? d.index ?? "").slice(0, 10);
8458
8639
  async function pullEthicalAds(range, ctx) {
8459
8640
  checkRange(range);
8460
- const c = credentials3(ctx.credentials);
8641
+ const c = credentials2(ctx.credentials);
8461
8642
  const q = new URLSearchParams({ start_date: range.from, end_date: range.to });
8462
8643
  const report = await httpJson(ctx.fetch, `${API3}/advertisers/${encodeURIComponent(c.advertiser)}/report/?${q}`, Report, { headers: { authorization: `Token ${c.api_key}` } });
8463
8644
  const rows = [];
@@ -8520,7 +8701,7 @@ var googleAds = defineCsvSource({
8520
8701
  normalizeAccountId: googleAccountId,
8521
8702
  totalPrefix: "total",
8522
8703
  accountTotalLabel: "total: account",
8523
- exportHint: "In Google Ads, open Campaigns > Ad groups, segment by Day, add Campaign ID, Ad group ID, Currency code and " + "Customer ID, remove filters, then Download > .csv."
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."
8524
8705
  });
8525
8706
 
8526
8707
  // ../../packages/growth/src/platforms/linkedin.ts
@@ -8563,8 +8744,8 @@ var LinkedInCredentials = object({
8563
8744
  client_secret: string2().min(1).optional(),
8564
8745
  refresh_token: string2().min(1).optional()
8565
8746
  }).refine((c) => c.access_token || c.client_id && c.client_secret && c.refresh_token);
8566
- var credentials4 = (creds) => parseCredentialsJson(creds[ENV8], LinkedInCredentials);
8567
- var linkedInApiAccount = (creds) => credentials4(creds).account_id;
8747
+ var credentials3 = (creds) => parseCredentialsJson(creds[ENV8], LinkedInCredentials);
8748
+ var linkedInApiAccount = (creds) => credentials3(creds).account_id;
8568
8749
  var Token3 = object({ access_token: string2().min(1) });
8569
8750
  async function accessToken(c, f) {
8570
8751
  if (!c.refresh_token)
@@ -8615,7 +8796,7 @@ var Element = object({
8615
8796
  var Analytics = object({ elements: array(Element) });
8616
8797
  async function pullLinkedInAds(range, ctx) {
8617
8798
  checkRange(range);
8618
- const c = credentials4(ctx.credentials);
8799
+ const c = credentials3(ctx.credentials);
8619
8800
  const token = await accessToken(c, ctx.fetch);
8620
8801
  const headers = {
8621
8802
  authorization: `Bearer ${token}`,
@@ -8696,8 +8877,8 @@ var MetaCredentials = object({
8696
8877
  access_token: string2().min(1),
8697
8878
  ad_account_id: string2().trim().regex(/^(act_)?\d{1,30}$/).transform((s) => s.replace(/^act_/, ""))
8698
8879
  });
8699
- var credentials5 = (creds) => parseCredentialsJson(creds[ENV9], MetaCredentials);
8700
- var metaApiAccount = (creds) => credentials5(creds).ad_account_id;
8880
+ var credentials4 = (creds) => parseCredentialsJson(creds[ENV9], MetaCredentials);
8881
+ var metaApiAccount = (creds) => credentials4(creds).ad_account_id;
8701
8882
  var Account3 = object({ currency: string2() });
8702
8883
  var Insight = object({
8703
8884
  date_start: string2(),
@@ -8747,7 +8928,7 @@ function nextUrl(next) {
8747
8928
  }
8748
8929
  async function pullMetaAds(range, ctx) {
8749
8930
  checkRange(range);
8750
- const c = credentials5(ctx.credentials);
8931
+ const c = credentials4(ctx.credentials);
8751
8932
  const account = await metaJson(ctx.fetch, `${API5}/act_${c.ad_account_id}?fields=currency`, c.access_token, Account3);
8752
8933
  assertUsd(account.currency);
8753
8934
  const q = new URLSearchParams({
@@ -8819,8 +9000,8 @@ var RedditCredentials = object({
8819
9000
  ad_account_id: string2().trim().regex(/^[A-Za-z0-9_-]{1,64}$/),
8820
9001
  user_agent: string2().min(1).max(200).optional()
8821
9002
  });
8822
- var credentials6 = (creds) => parseCredentialsJson(creds[ENV10], RedditCredentials);
8823
- var redditApiAccount = (creds) => credentials6(creds).ad_account_id;
9003
+ var credentials5 = (creds) => parseCredentialsJson(creds[ENV10], RedditCredentials);
9004
+ var redditApiAccount = (creds) => credentials5(creds).ad_account_id;
8824
9005
  var Token4 = object({ access_token: string2().min(1) });
8825
9006
  async function accessToken2(c, f) {
8826
9007
  try {
@@ -8874,7 +9055,7 @@ function nextUrl2(next) {
8874
9055
  }
8875
9056
  async function pullRedditAds(range, ctx) {
8876
9057
  checkRange(range);
8877
- const c = credentials6(ctx.credentials);
9058
+ const c = credentials5(ctx.credentials);
8878
9059
  const token = await accessToken2(c, ctx.fetch);
8879
9060
  const headers = { authorization: `Bearer ${token}`, "user-agent": c.user_agent ?? DEFAULT_USER_AGENT };
8880
9061
  const acct = encodeURIComponent(c.ad_account_id);
@@ -8967,8 +9148,8 @@ var XCredentials = object({
8967
9148
  access_token_secret: string2().min(1),
8968
9149
  account_id: string2().trim().regex(/^[A-Za-z0-9]{1,32}$/)
8969
9150
  });
8970
- var credentials7 = (creds) => parseCredentialsJson(creds[ENV11], XCredentials);
8971
- var xApiAccount = (creds) => credentials7(creds).account_id;
9151
+ var credentials6 = (creds) => parseCredentialsJson(creds[ENV11], XCredentials);
9152
+ var xApiAccount = (creds) => credentials6(creds).account_id;
8972
9153
  function zonedMidnightUtc(day, tz) {
8973
9154
  const fmt = new Intl.DateTimeFormat("en-US", {
8974
9155
  timeZone: tz,
@@ -9001,13 +9182,18 @@ var Stats = object({
9001
9182
  data: array(object({
9002
9183
  id: string2(),
9003
9184
  id_data: array(object({
9004
- metrics: object({ impressions: Series, url_clicks: Series, billed_charge_local_micro: Series })
9185
+ metrics: object({
9186
+ impressions: Series,
9187
+ link_clicks: Series,
9188
+ url_clicks: Series,
9189
+ billed_charge_local_micro: Series
9190
+ })
9005
9191
  }))
9006
9192
  }))
9007
9193
  });
9008
9194
  async function pullXAds(range, ctx) {
9009
9195
  checkRange(range);
9010
- const c = credentials7(ctx.credentials);
9196
+ const c = credentials6(ctx.credentials);
9011
9197
  const oauth = {
9012
9198
  consumerKey: c.consumer_key,
9013
9199
  consumerSecret: c.consumer_secret,
@@ -9066,6 +9252,7 @@ async function pullXAds(range, ctx) {
9066
9252
  const campaignId = li?.campaign_id ?? d.id;
9067
9253
  for (const s of d.id_data) {
9068
9254
  const m = s.metrics;
9255
+ const linkClicks = m.link_clicks ?? m.url_clicks;
9069
9256
  for (let k = 0;k < days; k++) {
9070
9257
  rows.push(row("x", {
9071
9258
  day: addDays(start, k),
@@ -9074,7 +9261,7 @@ async function pullXAds(range, ctx) {
9074
9261
  adGroupId: d.id,
9075
9262
  names: { campaign: campaigns.get(campaignId)?.name ?? null, adGroup: li?.name ?? null, ad: null },
9076
9263
  impressions: count(m.impressions?.[k]),
9077
- clicks: count(m.url_clicks?.[k]),
9264
+ clicks: count(linkClicks?.[k]),
9078
9265
  spendMicros: m.billed_charge_local_micro?.[k] == null ? 0 : intMicros(m.billed_charge_local_micro[k])
9079
9266
  }));
9080
9267
  }
@@ -9090,7 +9277,10 @@ async function pullXAds(range, ctx) {
9090
9277
  function withApi(source, platform, pull, apiAccount) {
9091
9278
  return { ...source, pull, apiAccount, credentialEnv: [SPEND_CREDENTIAL_ENV[platform]] };
9092
9279
  }
9093
- var googleAds2 = withApi(googleAds, "google", pullGoogleAds, googleApiAccount);
9280
+ var googleAds2 = {
9281
+ ...withApi(googleAds, "google", pullGoogleAds, googleApiAccount),
9282
+ apiAccounts: googleApiAccounts
9283
+ };
9094
9284
  var redditAds2 = withApi(redditAds, "reddit", pullRedditAds, redditApiAccount);
9095
9285
  var xAds2 = withApi(xAds, "x", pullXAds, xApiAccount);
9096
9286
  var ethicalAds2 = withApi(ethicalAds, "ethicalads", pullEthicalAds, ethicalAdsApiAccount);
@@ -9289,8 +9479,34 @@ var GrowthResults = object({
9289
9479
  summary: string2(),
9290
9480
  decisions: array(GrowthDecision)
9291
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();
9292
9508
  // ../../packages/schemas/src/legal.ts
9293
- var TERMS_VERSION = "2026-10-11";
9509
+ var TERMS_VERSION = "2026-10-12";
9294
9510
  var LEGAL_SITE = "https://scaledthought.com";
9295
9511
  var LEGAL_PATHS = {
9296
9512
  index: "/legal",
@@ -9806,6 +10022,18 @@ var CREDIT_STEPS = [
9806
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.`,
9807
10023
  "4. Then make the call below."
9808
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
+ }
9809
10037
  function nextSteps(model, needsCredits) {
9810
10038
  return [
9811
10039
  ...needsCredits ? CREDIT_STEPS : [],
@@ -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
- ...await liveSummary(ctx, region, balanceOf(status)),
9881
- ...ignored.length ? { warnings: [`${ignored.join(" and ")} apply only to a new account and were not sent.`] } : {}
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
- ...await liveSummary(keyed, acct.region, balanceOf(billing)),
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")
@@ -11669,7 +11920,10 @@ var init2 = defineCommand({
11669
11920
  ...jsonFlag,
11670
11921
  region: { type: "string", description: "Home region for your data: us (default) or ca. Permanent." },
11671
11922
  name: { type: "string", description: "Account name (optional)" },
11672
- email: { type: "string", description: "Email for receipts (optional)" },
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
+ },
11673
11927
  source: {
11674
11928
  type: "string",
11675
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)"
@@ -11790,9 +12044,9 @@ var models = defineCommand({
11790
12044
  });
11791
12045
  }, args)
11792
12046
  });
11793
- var ACCOUNT_SUBCOMMANDS = new Set(["delete", "ad-measurement"]);
12047
+ var ACCOUNT_SUBCOMMANDS = new Set(["delete", "ad-measurement", "email"]);
11794
12048
  var account2 = defineCommand({
11795
- meta: { name: "account", description: "Region, spend, credit balance and auto-reload" },
12049
+ meta: { name: "account", description: "Region, spend, credit balance, auto-reload and owner_email" },
11796
12050
  args: jsonFlag,
11797
12051
  run: ({ args, rawArgs }) => {
11798
12052
  if (rawArgs.some((a) => ACCOUNT_SUBCOMMANDS.has(a)))
@@ -11821,6 +12075,22 @@ var account2 = defineCommand({
11821
12075
  state: { type: "positional", required: true, description: "off or on" }
11822
12076
  },
11823
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
+ }
11824
12094
  })
11825
12095
  }
11826
12096
  });
@@ -12227,7 +12497,10 @@ var billing = defineCommand({
12227
12497
  }
12228
12498
  });
12229
12499
  var batches = defineCommand({
12230
- meta: { name: "batches", description: "Batch inference at 50% off (OpenAI-compatible). Paid from prepaid credits" },
12500
+ meta: {
12501
+ name: "batches",
12502
+ description: "Batch inference at the normal per-token price (OpenAI-compatible). Paid from prepaid credits"
12503
+ },
12231
12504
  subCommands: {
12232
12505
  create: defineCommand({
12233
12506
  meta: { name: "create", description: "Upload a JSONL file and start a batch" },
@@ -12451,7 +12724,7 @@ var mcp = defineCommand({
12451
12724
  var dashboard = defineCommand({
12452
12725
  meta: {
12453
12726
  name: "dashboard",
12454
- 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)"
12455
12728
  },
12456
12729
  args: {
12457
12730
  ...jsonFlag,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "scaledthought",
3
- "version": "0.3.2",
4
- "description": "Headless open-model inference, run by your agent. Prepaid, pay per token. US or Canada.",
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
- "cli",
14
- "canada",
15
- "data-residency"
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": {