@primitivedotdev/cli 0.35.0 → 0.36.0

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.
@@ -1,4 +1,4 @@
1
- import { A as createClient, C as saveCliCredentials, D as loadChatConversationByLocalId, E as loadActiveChatState, O as saveActiveChatState, S as resolveCliAuth, T as deleteChatState, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as normalizeApiBaseUrl1, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as createConfig, k as PrimitiveApiClient, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as chatStatePath, x as normalizeApiBaseUrl2, y as loadCliCredentials } from "../cli-config-SktG2dzR.js";
1
+ import { A as createClient, C as saveCliCredentials, D as loadChatConversationByLocalId, E as loadActiveChatState, O as saveActiveChatState, S as resolveCliAuth, T as deleteChatState, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as loadCliCredentials, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as createConfig, k as PrimitiveApiClient, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as chatStatePath, x as normalizeApiBaseUrl, y as detectPrimitiveKeyEnvMisname } from "../cli-config-D7wN_PBc.js";
2
2
  import { Args, Command, Errors, Flags, ux } from "@oclif/core";
3
3
  import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { randomUUID } from "node:crypto";
@@ -20,7 +20,7 @@ var __exportAll = (all, no_symbols) => {
20
20
  };
21
21
  //#endregion
22
22
  //#region ../packages/api-core/src/api/client.gen.ts
23
- const client = createClient(createConfig({ baseUrl: "https://www.primitive.dev/api/v1" }));
23
+ const client = createClient(createConfig({ baseUrl: "https://api.primitive.dev/v1" }));
24
24
  //#endregion
25
25
  //#region ../packages/api-core/src/api/sdk.gen.ts
26
26
  var sdk_gen_exports = /* @__PURE__ */ __exportAll({
@@ -44,8 +44,10 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
44
44
  getConversation: () => getConversation,
45
45
  getEmail: () => getEmail,
46
46
  getFunction: () => getFunction,
47
+ getFunctionRouting: () => getFunctionRouting,
47
48
  getFunctionTestRunTrace: () => getFunctionTestRunTrace,
48
49
  getInboxStatus: () => getInboxStatus,
50
+ getOrgRoutingTopology: () => getOrgRoutingTopology,
49
51
  getSendPermissions: () => getSendPermissions,
50
52
  getSentEmail: () => getSentEmail,
51
53
  getStorageStats: () => getStorageStats,
@@ -70,12 +72,14 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
70
72
  searchEmails: () => searchEmails,
71
73
  semanticSearch: () => semanticSearch,
72
74
  sendEmail: () => sendEmail,
75
+ setFunctionRoute: () => setFunctionRoute,
73
76
  setFunctionSecret: () => setFunctionSecret,
74
77
  startAgentSignup: () => startAgentSignup,
75
78
  startCliLogin: () => startCliLogin,
76
79
  startCliSignup: () => startCliSignup,
77
80
  testEndpoint: () => testEndpoint,
78
81
  testFunction: () => testFunction,
82
+ unsetFunctionRoute: () => unsetFunctionRoute,
79
83
  updateAccount: () => updateAccount,
80
84
  updateDomain: () => updateDomain,
81
85
  updateEndpoint: () => updateEndpoint,
@@ -934,14 +938,13 @@ const getSendPermissions = (options) => (options?.client ?? client).get({
934
938
  * the request returns once the relay accepts the message for delivery.
935
939
  * Set `wait: true` to wait for the first downstream SMTP delivery outcome.
936
940
  *
937
- * **Host routing.** /send-mail is served by the attachments-
938
- * supporting host (`https://api.primitive.dev/v1`) so the
939
- * request body can carry inline attachments up to ~30 MiB raw.
940
- * The primary host (`https://www.primitive.dev/api/v1`) also
941
- * accepts /send-mail for attachment-free sends; sends WITH
942
- * attachments to the primary host return 413
943
- * `attachments_unsupported_on_this_endpoint`. The typed SDKs
944
- * route /send-mail to the attachments host automatically.
941
+ * **Host routing.** /send-mail is served by the canonical API host
942
+ * (`https://api.primitive.dev/v1`) so the request body can carry
943
+ * inline attachments up to ~30 MiB raw. The legacy dashboard
944
+ * compatibility host (`https://www.primitive.dev/api/v1`) also accepts
945
+ * /send-mail, but Vercel request body limits apply before proxying.
946
+ * The typed SDKs route /send-mail to the canonical API host
947
+ * automatically.
945
948
  *
946
949
  */
947
950
  const sendEmail = (options) => (options.client ?? client).post({
@@ -1103,11 +1106,13 @@ const listFunctions = (options) => (options?.client ?? client).get({
1103
1106
  * attempt, and sent to the runtime so stack traces can resolve to
1104
1107
  * original source files.
1105
1108
  *
1106
- * **Auto-wiring.** On successful deploy, Primitive automatically
1107
- * creates a webhook endpoint that delivers inbound mail to the
1108
- * function. There is nothing to configure on the Endpoints API
1109
- * for this to work; the internal runtime URL is not returned by
1110
- * the API and is not a customer-facing integration surface.
1109
+ * **Routing.** On successful deploy, the function code is live
1110
+ * in the runtime, but inbound mail will not reach it until at
1111
+ * least one route is bound. Routes are managed from the Primitive
1112
+ * dashboard. A `deploy_status` of `deployed` means the script is
1113
+ * installed, not that the function is receiving mail. The
1114
+ * internal runtime URL is not returned by the API and is not a
1115
+ * customer-facing integration surface.
1111
1116
  *
1112
1117
  * **Secrets.** New functions ship with the managed secrets
1113
1118
  * (`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,
@@ -1132,7 +1137,7 @@ const createFunction = (options) => (options.client ?? client).post({
1132
1137
  * Delete a function
1133
1138
  *
1134
1139
  * Soft-deletes the function row, removes the script from the edge
1135
- * runtime, and deactivates the auto-wired webhook endpoint so no
1140
+ * runtime, and deactivates any route bound to this function so no
1136
1141
  * further inbound mail is delivered. Past deploy history,
1137
1142
  * invocations, and logs are retained.
1138
1143
  *
@@ -1249,6 +1254,80 @@ const getFunctionTestRunTrace = (options) => (options.client ?? client).get({
1249
1254
  ...options
1250
1255
  });
1251
1256
  /**
1257
+ * Get the org's function routing topology
1258
+ *
1259
+ * Returns a single snapshot of how inbound mail is routed across
1260
+ * this org's active domains and functions: which active domain has
1261
+ * which function bound, the org's fallback function (if any), and
1262
+ * every deployed function with no route bound. Use this to answer
1263
+ * "which of my functions actually receive mail?" diagnostically.
1264
+ *
1265
+ */
1266
+ const getOrgRoutingTopology = (options) => (options?.client ?? client).get({
1267
+ security: [{
1268
+ scheme: "bearer",
1269
+ type: "http"
1270
+ }],
1271
+ url: "/functions/routing-topology",
1272
+ ...options
1273
+ });
1274
+ /**
1275
+ * Get a function's current route binding
1276
+ *
1277
+ * Returns the endpoint binding for the function, or null when no
1278
+ * route is currently bound. The binding identifies whether the
1279
+ * function receives mail for a specific domain (scoped) or for any
1280
+ * active domain that has no scoped binding (fallback).
1281
+ *
1282
+ */
1283
+ const getFunctionRouting = (options) => (options.client ?? client).get({
1284
+ security: [{
1285
+ scheme: "bearer",
1286
+ type: "http"
1287
+ }],
1288
+ url: "/functions/{id}/routing",
1289
+ ...options
1290
+ });
1291
+ /**
1292
+ * Unbind any route from a function
1293
+ *
1294
+ * Deactivates every active endpoint bound to this function. The
1295
+ * function stays deployed but stops receiving inbound mail. Safe
1296
+ * to call when no route is currently bound (no-op).
1297
+ *
1298
+ */
1299
+ const unsetFunctionRoute = (options) => (options.client ?? client).delete({
1300
+ security: [{
1301
+ scheme: "bearer",
1302
+ type: "http"
1303
+ }],
1304
+ url: "/functions/{id}/route",
1305
+ ...options
1306
+ });
1307
+ /**
1308
+ * Bind a route to a function
1309
+ *
1310
+ * Binds inbound mail to this function. The route target is either
1311
+ * a specific verified domain (scoped) or the org's fallback (any
1312
+ * active domain with no scoped binding). If another function is
1313
+ * already bound at the target, returns a `conflict` envelope
1314
+ * describing the holder; re-issue with `takeover: true` to
1315
+ * deactivate that prior binding and install this one.
1316
+ *
1317
+ */
1318
+ const setFunctionRoute = (options) => (options.client ?? client).put({
1319
+ security: [{
1320
+ scheme: "bearer",
1321
+ type: "http"
1322
+ }],
1323
+ url: "/functions/{id}/route",
1324
+ ...options,
1325
+ headers: {
1326
+ ...options.body !== void 0 && { "Content-Type": "application/json" },
1327
+ ...options.headers
1328
+ }
1329
+ });
1330
+ /**
1252
1331
  * List a function's secrets
1253
1332
  *
1254
1333
  * Returns metadata for every secret bound to the function, with
@@ -1383,11 +1462,11 @@ const openapiDocument = {
1383
1462
  }
1384
1463
  },
1385
1464
  "servers": [{
1386
- "url": "https://www.primitive.dev/api/v1",
1387
- "description": "Primary API host (PRIMITIVE_API_BASE_URL_1). Carries every operation\nexcept attachment-supporting send. Vercel-backed; request body is\ncapped at 4.5 MB by the platform.\n"
1388
- }, {
1389
1465
  "url": "https://api.primitive.dev/v1",
1390
- "description": "Attachments-supporting send host (PRIMITIVE_API_BASE_URL_2).\nCloudflare Worker with a ~30 MiB raw request body cap (before\nbase64 encoding). Today only `/send-mail` is hosted here; future\nlarge-body operations will migrate here over time. SDK clients\nroute /send-mail to this server automatically.\n"
1466
+ "description": "Canonical API host (PRIMITIVE_API_BASE_URL). Carries every public\nAPI operation. Cloudflare Workers-backed; attachment-capable send\noperations can carry up to ~30 MiB raw request bodies before base64\nencoding.\n"
1467
+ }, {
1468
+ "url": "https://www.primitive.dev/api/v1",
1469
+ "description": "Legacy dashboard compatibility host. Requests are forwarded to the\ncanonical API host, but Vercel request body limits still apply before\nproxying. New integrations should use https://api.primitive.dev/v1.\n"
1391
1470
  }],
1392
1471
  "security": [{ "BearerAuth": [] }],
1393
1472
  "tags": [
@@ -2410,10 +2489,10 @@ const openapiDocument = {
2410
2489
  "description": "Sends an outbound reply to the inbound email identified by `id`.\nThreading headers (`In-Reply-To`, `References`), recipient\nderivation (Reply-To, then From, then bare sender), and the\n`Re:` subject prefix are all derived server-side from the\nstored inbound row. The request body carries only the message\nbody, optional From override, optional attachments, and optional\n`wait` flag; passing any header or recipient override is\nrejected by the schema (`additionalProperties: false`).\n\nForwards through the same gates as `/send-mail`: the response\nstatus, error envelope, and `idempotent_replay` flag mirror\nthe send-mail contract verbatim.\n",
2411
2490
  "servers": [{
2412
2491
  "url": "https://api.primitive.dev/v1",
2413
- "description": "Attachments-supporting send host (recommended)"
2492
+ "description": "Canonical API host (recommended)"
2414
2493
  }, {
2415
2494
  "url": "https://www.primitive.dev/api/v1",
2416
- "description": "Primary host (attachment-free replies only)"
2495
+ "description": "Legacy compatibility host (Vercel body limit applies)"
2417
2496
  }],
2418
2497
  "tags": ["Sending"],
2419
2498
  "requestBody": {
@@ -2822,13 +2901,13 @@ const openapiDocument = {
2822
2901
  "/send-mail": { "post": {
2823
2902
  "operationId": "sendEmail",
2824
2903
  "summary": "Send outbound email",
2825
- "description": "Sends an outbound email through Primitive's outbound relay. By default\nthe request returns once the relay accepts the message for delivery.\nSet `wait: true` to wait for the first downstream SMTP delivery outcome.\n\n**Host routing.** /send-mail is served by the attachments-\nsupporting host (`https://api.primitive.dev/v1`) so the\nrequest body can carry inline attachments up to ~30 MiB raw.\nThe primary host (`https://www.primitive.dev/api/v1`) also\naccepts /send-mail for attachment-free sends; sends WITH\nattachments to the primary host return 413\n`attachments_unsupported_on_this_endpoint`. The typed SDKs\nroute /send-mail to the attachments host automatically.\n",
2904
+ "description": "Sends an outbound email through Primitive's outbound relay. By default\nthe request returns once the relay accepts the message for delivery.\nSet `wait: true` to wait for the first downstream SMTP delivery outcome.\n\n**Host routing.** /send-mail is served by the canonical API host\n(`https://api.primitive.dev/v1`) so the request body can carry\ninline attachments up to ~30 MiB raw. The legacy dashboard\ncompatibility host (`https://www.primitive.dev/api/v1`) also accepts\n/send-mail, but Vercel request body limits apply before proxying.\nThe typed SDKs route /send-mail to the canonical API host\nautomatically.\n",
2826
2905
  "servers": [{
2827
2906
  "url": "https://api.primitive.dev/v1",
2828
- "description": "Attachments-supporting send host (recommended)"
2907
+ "description": "Canonical API host (recommended)"
2829
2908
  }, {
2830
2909
  "url": "https://www.primitive.dev/api/v1",
2831
- "description": "Primary host (attachment-free sends only)"
2910
+ "description": "Legacy compatibility host (Vercel body limit applies)"
2832
2911
  }],
2833
2912
  "tags": ["Sending"],
2834
2913
  "parameters": [{
@@ -3030,7 +3109,7 @@ const openapiDocument = {
3030
3109
  "post": {
3031
3110
  "operationId": "createFunction",
3032
3111
  "summary": "Deploy a function",
3033
- "description": "Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). Primitive signs\neach delivery and forwards the `Primitive-Signature` header to\nthe handler. Verify the raw request body with\n`PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification\nthe request body parses to an `email.received` event (see\n`EmailReceivedEvent` and the Webhook payload section for the full\nschema). Code is bundled before being uploaded; ship a single\nself-contained file rather than relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8, stored with each deployment\nattempt, and sent to the runtime so stack traces can resolve to\noriginal source files.\n\n**Auto-wiring.** On successful deploy, Primitive automatically\ncreates a webhook endpoint that delivers inbound mail to the\nfunction. There is nothing to configure on the Endpoints API\nfor this to work; the internal runtime URL is not returned by\nthe API and is not a customer-facing integration surface.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,\n`PRIMITIVE_API_BASE_URL`) already bound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n",
3112
+ "description": "Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). Primitive signs\neach delivery and forwards the `Primitive-Signature` header to\nthe handler. Verify the raw request body with\n`PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification\nthe request body parses to an `email.received` event (see\n`EmailReceivedEvent` and the Webhook payload section for the full\nschema). Code is bundled before being uploaded; ship a single\nself-contained file rather than relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8, stored with each deployment\nattempt, and sent to the runtime so stack traces can resolve to\noriginal source files.\n\n**Routing.** On successful deploy, the function code is live\nin the runtime, but inbound mail will not reach it until at\nleast one route is bound. Routes are managed from the Primitive\ndashboard. A `deploy_status` of `deployed` means the script is\ninstalled, not that the function is receiving mail. The\ninternal runtime URL is not returned by the API and is not a\ncustomer-facing integration surface.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,\n`PRIMITIVE_API_BASE_URL`) already bound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n",
3034
3113
  "tags": ["Functions"],
3035
3114
  "requestBody": {
3036
3115
  "required": true,
@@ -3109,7 +3188,7 @@ const openapiDocument = {
3109
3188
  "delete": {
3110
3189
  "operationId": "deleteFunction",
3111
3190
  "summary": "Delete a function",
3112
- "description": "Soft-deletes the function row, removes the script from the edge\nruntime, and deactivates the auto-wired webhook endpoint so no\nfurther inbound mail is delivered. Past deploy history,\ninvocations, and logs are retained.\n\nReturns 502 if the runtime delete fails partway; the function\nrow stays in place and the call is safe to retry until it\nsucceeds.\n",
3191
+ "description": "Soft-deletes the function row, removes the script from the edge\nruntime, and deactivates any route bound to this function so no\nfurther inbound mail is delivered. Past deploy history,\ninvocations, and logs are retained.\n\nReturns 502 if the runtime delete fails partway; the function\nrow stays in place and the call is safe to retry until it\nsucceeds.\n",
3113
3192
  "tags": ["Functions"],
3114
3193
  "responses": {
3115
3194
  "200": { "$ref": "#/components/responses/Deleted" },
@@ -3194,6 +3273,92 @@ const openapiDocument = {
3194
3273
  }
3195
3274
  }
3196
3275
  },
3276
+ "/functions/routing-topology": { "get": {
3277
+ "operationId": "getOrgRoutingTopology",
3278
+ "summary": "Get the org's function routing topology",
3279
+ "description": "Returns a single snapshot of how inbound mail is routed across\nthis org's active domains and functions: which active domain has\nwhich function bound, the org's fallback function (if any), and\nevery deployed function with no route bound. Use this to answer\n\"which of my functions actually receive mail?\" diagnostically.\n",
3280
+ "tags": ["Functions"],
3281
+ "responses": {
3282
+ "200": {
3283
+ "description": "Routing topology",
3284
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
3285
+ "type": "object",
3286
+ "properties": { "data": { "$ref": "#/components/schemas/RoutingTopology" } }
3287
+ }] } } }
3288
+ },
3289
+ "401": { "$ref": "#/components/responses/Unauthorized" },
3290
+ "403": { "$ref": "#/components/responses/Forbidden" }
3291
+ }
3292
+ } },
3293
+ "/functions/{id}/routing": {
3294
+ "parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
3295
+ "get": {
3296
+ "operationId": "getFunctionRouting",
3297
+ "summary": "Get a function's current route binding",
3298
+ "description": "Returns the endpoint binding for the function, or null when no\nroute is currently bound. The binding identifies whether the\nfunction receives mail for a specific domain (scoped) or for any\nactive domain that has no scoped binding (fallback).\n",
3299
+ "tags": ["Functions"],
3300
+ "responses": {
3301
+ "200": {
3302
+ "description": "Function routing",
3303
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
3304
+ "type": "object",
3305
+ "properties": { "data": { "oneOf": [{ "$ref": "#/components/schemas/FunctionRouting" }, { "type": "null" }] } }
3306
+ }] } } }
3307
+ },
3308
+ "401": { "$ref": "#/components/responses/Unauthorized" },
3309
+ "404": { "$ref": "#/components/responses/NotFound" }
3310
+ }
3311
+ }
3312
+ },
3313
+ "/functions/{id}/route": {
3314
+ "parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
3315
+ "put": {
3316
+ "operationId": "setFunctionRoute",
3317
+ "summary": "Bind a route to a function",
3318
+ "description": "Binds inbound mail to this function. The route target is either\na specific verified domain (scoped) or the org's fallback (any\nactive domain with no scoped binding). If another function is\nalready bound at the target, returns a `conflict` envelope\ndescribing the holder; re-issue with `takeover: true` to\ndeactivate that prior binding and install this one.\n",
3319
+ "tags": ["Functions"],
3320
+ "requestBody": {
3321
+ "required": true,
3322
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FunctionRouteBody" } } }
3323
+ },
3324
+ "responses": {
3325
+ "200": {
3326
+ "description": "Route bound, or conflict requiring takeover",
3327
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
3328
+ "type": "object",
3329
+ "properties": { "data": { "$ref": "#/components/schemas/FunctionRouteResult" } }
3330
+ }] } } }
3331
+ },
3332
+ "400": { "$ref": "#/components/responses/ValidationError" },
3333
+ "401": { "$ref": "#/components/responses/Unauthorized" },
3334
+ "404": { "$ref": "#/components/responses/NotFound" }
3335
+ }
3336
+ },
3337
+ "delete": {
3338
+ "operationId": "unsetFunctionRoute",
3339
+ "summary": "Unbind any route from a function",
3340
+ "description": "Deactivates every active endpoint bound to this function. The\nfunction stays deployed but stops receiving inbound mail. Safe\nto call when no route is currently bound (no-op).\n",
3341
+ "tags": ["Functions"],
3342
+ "responses": {
3343
+ "200": {
3344
+ "description": "Route unbound",
3345
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
3346
+ "type": "object",
3347
+ "properties": { "data": {
3348
+ "type": "object",
3349
+ "properties": { "unrouted": {
3350
+ "type": "boolean",
3351
+ "enum": [true]
3352
+ } },
3353
+ "required": ["unrouted"]
3354
+ } }
3355
+ }] } } }
3356
+ },
3357
+ "401": { "$ref": "#/components/responses/Unauthorized" },
3358
+ "404": { "$ref": "#/components/responses/NotFound" }
3359
+ }
3360
+ }
3361
+ },
3197
3362
  "/functions/{id}/secrets": {
3198
3363
  "parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
3199
3364
  "get": {
@@ -6719,6 +6884,178 @@ const openapiDocument = {
6719
6884
  "trace_url"
6720
6885
  ]
6721
6886
  },
6887
+ "FunctionRouting": {
6888
+ "type": "object",
6889
+ "description": "A single route binding for a function. `domain` is null when the\nbinding is the org's fallback (any active domain without a scoped\nbinding); otherwise it carries the scoped domain. `rules` is\nreserved for future routing predicates.\n",
6890
+ "properties": {
6891
+ "endpoint_id": {
6892
+ "type": "string",
6893
+ "format": "uuid"
6894
+ },
6895
+ "enabled": { "type": "boolean" },
6896
+ "domain": {
6897
+ "type": ["object", "null"],
6898
+ "properties": {
6899
+ "id": {
6900
+ "type": "string",
6901
+ "format": "uuid"
6902
+ },
6903
+ "name": { "type": ["string", "null"] }
6904
+ },
6905
+ "required": ["id"]
6906
+ },
6907
+ "rules": {
6908
+ "type": "object",
6909
+ "description": "Future routing predicates. Currently empty."
6910
+ },
6911
+ "delivery_count": { "type": "integer" },
6912
+ "success_count": { "type": "integer" },
6913
+ "failure_count": { "type": "integer" },
6914
+ "consecutive_fails": { "type": "integer" },
6915
+ "last_delivery_at": {
6916
+ "type": ["string", "null"],
6917
+ "format": "date-time"
6918
+ },
6919
+ "last_success_at": {
6920
+ "type": ["string", "null"],
6921
+ "format": "date-time"
6922
+ },
6923
+ "last_failure_at": {
6924
+ "type": ["string", "null"],
6925
+ "format": "date-time"
6926
+ }
6927
+ },
6928
+ "required": [
6929
+ "endpoint_id",
6930
+ "enabled",
6931
+ "domain",
6932
+ "rules"
6933
+ ]
6934
+ },
6935
+ "RoutingTopology": {
6936
+ "type": "object",
6937
+ "description": "Org-wide map of function routing: which domain points at which\nfunction, the org's fallback binding (if any), and every\ndeployed function with no route currently bound.\n",
6938
+ "properties": {
6939
+ "domains": {
6940
+ "type": "array",
6941
+ "items": {
6942
+ "type": "object",
6943
+ "properties": {
6944
+ "domain_id": {
6945
+ "type": "string",
6946
+ "format": "uuid"
6947
+ },
6948
+ "domain": { "type": "string" },
6949
+ "routed_function": {
6950
+ "type": ["object", "null"],
6951
+ "properties": {
6952
+ "id": {
6953
+ "type": "string",
6954
+ "format": "uuid"
6955
+ },
6956
+ "name": { "type": "string" }
6957
+ },
6958
+ "required": ["id", "name"]
6959
+ },
6960
+ "endpoint_enabled": { "type": ["boolean", "null"] }
6961
+ },
6962
+ "required": [
6963
+ "domain_id",
6964
+ "domain",
6965
+ "routed_function",
6966
+ "endpoint_enabled"
6967
+ ]
6968
+ }
6969
+ },
6970
+ "fallback_function": {
6971
+ "type": ["object", "null"],
6972
+ "properties": {
6973
+ "id": {
6974
+ "type": "string",
6975
+ "format": "uuid"
6976
+ },
6977
+ "name": { "type": "string" }
6978
+ },
6979
+ "required": ["id", "name"]
6980
+ },
6981
+ "fallback_enabled": { "type": ["boolean", "null"] },
6982
+ "unrouted_functions": {
6983
+ "type": "array",
6984
+ "items": {
6985
+ "type": "object",
6986
+ "properties": {
6987
+ "id": {
6988
+ "type": "string",
6989
+ "format": "uuid"
6990
+ },
6991
+ "name": { "type": "string" }
6992
+ },
6993
+ "required": ["id", "name"]
6994
+ }
6995
+ }
6996
+ },
6997
+ "required": [
6998
+ "domains",
6999
+ "fallback_function",
7000
+ "fallback_enabled",
7001
+ "unrouted_functions"
7002
+ ]
7003
+ },
7004
+ "FunctionRouteBody": {
7005
+ "type": "object",
7006
+ "description": "Target for a route binding. Either a specific verified domain\n(scoped) or the org-wide fallback. Pass `takeover: true` to\ndeactivate any conflicting binding before installing this one.\n",
7007
+ "properties": {
7008
+ "target": { "oneOf": [{
7009
+ "type": "object",
7010
+ "properties": {
7011
+ "kind": {
7012
+ "type": "string",
7013
+ "enum": ["domain"]
7014
+ },
7015
+ "domainId": {
7016
+ "type": "string",
7017
+ "format": "uuid"
7018
+ }
7019
+ },
7020
+ "required": ["kind", "domainId"]
7021
+ }, {
7022
+ "type": "object",
7023
+ "properties": { "kind": {
7024
+ "type": "string",
7025
+ "enum": ["fallback"]
7026
+ } },
7027
+ "required": ["kind"]
7028
+ }] },
7029
+ "takeover": {
7030
+ "type": "boolean",
7031
+ "description": "When true, deactivate any conflicting binding before installing this one."
7032
+ }
7033
+ },
7034
+ "required": ["target"]
7035
+ },
7036
+ "FunctionRouteResult": {
7037
+ "type": "object",
7038
+ "description": "On success, carries the new `routing`. On conflict, carries\n`conflict` describing the binding holder so the caller can\nre-issue with `takeover: true`.\n",
7039
+ "properties": {
7040
+ "routing": { "oneOf": [{ "$ref": "#/components/schemas/FunctionRouting" }, { "type": "null" }] },
7041
+ "conflict": {
7042
+ "type": "object",
7043
+ "properties": {
7044
+ "kind": {
7045
+ "type": "string",
7046
+ "enum": ["http", "function"]
7047
+ },
7048
+ "functionId": {
7049
+ "type": ["string", "null"],
7050
+ "format": "uuid"
7051
+ },
7052
+ "functionName": { "type": ["string", "null"] },
7053
+ "url": { "type": ["string", "null"] }
7054
+ },
7055
+ "required": ["kind"]
7056
+ }
7057
+ }
7058
+ },
6722
7059
  "FunctionTestRunState": {
6723
7060
  "type": "string",
6724
7061
  "description": "High-level state for a function test run trace:\n - `send_failed`: the initial test email send failed.\n - `waiting_for_send`: the test run was created but no send result has been recorded yet.\n - `waiting_for_inbound`: the test send was queued and the matching inbound email has not arrived yet.\n - `waiting_for_function`: the inbound email arrived and webhook/function processing is still in flight.\n - `completed`: the function webhook completed successfully.\n - `failed`: webhook delivery exhausted retries.\n",
@@ -10434,7 +10771,7 @@ const operationManifest = [
10434
10771
  "binaryResponse": false,
10435
10772
  "bodyRequired": true,
10436
10773
  "command": "create-function",
10437
- "description": "Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). Primitive signs\neach delivery and forwards the `Primitive-Signature` header to\nthe handler. Verify the raw request body with\n`PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification\nthe request body parses to an `email.received` event (see\n`EmailReceivedEvent` and the Webhook payload section for the full\nschema). Code is bundled before being uploaded; ship a single\nself-contained file rather than relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8, stored with each deployment\nattempt, and sent to the runtime so stack traces can resolve to\noriginal source files.\n\n**Auto-wiring.** On successful deploy, Primitive automatically\ncreates a webhook endpoint that delivers inbound mail to the\nfunction. There is nothing to configure on the Endpoints API\nfor this to work; the internal runtime URL is not returned by\nthe API and is not a customer-facing integration surface.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,\n`PRIMITIVE_API_BASE_URL`) already bound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n",
10774
+ "description": "Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). Primitive signs\neach delivery and forwards the `Primitive-Signature` header to\nthe handler. Verify the raw request body with\n`PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification\nthe request body parses to an `email.received` event (see\n`EmailReceivedEvent` and the Webhook payload section for the full\nschema). Code is bundled before being uploaded; ship a single\nself-contained file rather than relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8, stored with each deployment\nattempt, and sent to the runtime so stack traces can resolve to\noriginal source files.\n\n**Routing.** On successful deploy, the function code is live\nin the runtime, but inbound mail will not reach it until at\nleast one route is bound. Routes are managed from the Primitive\ndashboard. A `deploy_status` of `deployed` means the script is\ninstalled, not that the function is receiving mail. The\ninternal runtime URL is not returned by the API and is not a\ncustomer-facing integration surface.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,\n`PRIMITIVE_API_BASE_URL`) already bound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n",
10438
10775
  "hasJsonBody": true,
10439
10776
  "method": "POST",
10440
10777
  "operationId": "createFunction",
@@ -10570,7 +10907,7 @@ const operationManifest = [
10570
10907
  "binaryResponse": false,
10571
10908
  "bodyRequired": false,
10572
10909
  "command": "delete-function",
10573
- "description": "Soft-deletes the function row, removes the script from the edge\nruntime, and deactivates the auto-wired webhook endpoint so no\nfurther inbound mail is delivered. Past deploy history,\ninvocations, and logs are retained.\n\nReturns 502 if the runtime delete fails partway; the function\nrow stays in place and the call is safe to retry until it\nsucceeds.\n",
10910
+ "description": "Soft-deletes the function row, removes the script from the edge\nruntime, and deactivates any route bound to this function so no\nfurther inbound mail is delivered. Past deploy history,\ninvocations, and logs are retained.\n\nReturns 502 if the runtime delete fails partway; the function\nrow stays in place and the call is safe to retry until it\nsucceeds.\n",
10574
10911
  "hasJsonBody": false,
10575
10912
  "method": "DELETE",
10576
10913
  "operationId": "deleteFunction",
@@ -10694,57 +11031,128 @@ const operationManifest = [
10694
11031
  {
10695
11032
  "binaryResponse": false,
10696
11033
  "bodyRequired": false,
10697
- "command": "get-function-test-run-trace",
10698
- "description": "Returns the current end-to-end trace for a function test run.\nThe trace is intentionally partial while the test is still in\nflight: callers can poll this endpoint and watch it fill in\nfrom send -> inbound -> webhook deliveries -> outbound\nrequests, logs, and replies.\n",
11034
+ "command": "get-function-routing",
11035
+ "description": "Returns the endpoint binding for the function, or null when no\nroute is currently bound. The binding identifies whether the\nfunction receives mail for a specific domain (scoped) or for any\nactive domain that has no scoped binding (fallback).\n",
10699
11036
  "hasJsonBody": false,
10700
11037
  "method": "GET",
10701
- "operationId": "getFunctionTestRunTrace",
10702
- "path": "/functions/{id}/test-runs/{run_id}/trace",
11038
+ "operationId": "getFunctionRouting",
11039
+ "path": "/functions/{id}/routing",
10703
11040
  "pathParams": [{
10704
11041
  "description": "Resource UUID",
10705
11042
  "enum": null,
10706
11043
  "name": "id",
10707
11044
  "required": true,
10708
11045
  "type": "string"
10709
- }, {
10710
- "description": "Function test run id returned by POST /functions/{id}/test.",
10711
- "enum": null,
10712
- "name": "run_id",
10713
- "required": true,
10714
- "type": "string"
10715
11046
  }],
10716
11047
  "queryParams": [],
10717
11048
  "requestSchema": null,
10718
- "responseSchema": {
11049
+ "responseSchema": { "oneOf": [{
10719
11050
  "type": "object",
10720
- "description": "End-to-end trace for a `POST /functions/{id}/test` run. The\nshape is stable, but many nested sections are null or empty\nuntil the corresponding phase has happened.\n",
11051
+ "description": "A single route binding for a function. `domain` is null when the\nbinding is the org's fallback (any active domain without a scoped\nbinding); otherwise it carries the scoped domain. `rules` is\nreserved for future routing predicates.\n",
10721
11052
  "properties": {
10722
- "state": {
11053
+ "endpoint_id": {
10723
11054
  "type": "string",
10724
- "description": "High-level state for a function test run trace:\n - `send_failed`: the initial test email send failed.\n - `waiting_for_send`: the test run was created but no send result has been recorded yet.\n - `waiting_for_inbound`: the test send was queued and the matching inbound email has not arrived yet.\n - `waiting_for_function`: the inbound email arrived and webhook/function processing is still in flight.\n - `completed`: the function webhook completed successfully.\n - `failed`: webhook delivery exhausted retries.\n",
10725
- "enum": [
10726
- "send_failed",
10727
- "waiting_for_send",
10728
- "waiting_for_inbound",
10729
- "waiting_for_function",
10730
- "completed",
10731
- "failed"
10732
- ]
11055
+ "format": "uuid"
10733
11056
  },
10734
- "test_run": {
10735
- "type": "object",
11057
+ "enabled": { "type": "boolean" },
11058
+ "domain": {
11059
+ "type": ["object", "null"],
10736
11060
  "properties": {
10737
11061
  "id": {
10738
11062
  "type": "string",
10739
11063
  "format": "uuid"
10740
11064
  },
10741
- "function_id": {
10742
- "type": "string",
10743
- "format": "uuid"
10744
- },
10745
- "inbound_domain": { "type": "string" },
10746
- "to": { "type": "string" },
10747
- "from": { "type": "string" },
11065
+ "name": { "type": ["string", "null"] }
11066
+ },
11067
+ "required": ["id"]
11068
+ },
11069
+ "rules": {
11070
+ "type": "object",
11071
+ "description": "Future routing predicates. Currently empty."
11072
+ },
11073
+ "delivery_count": { "type": "integer" },
11074
+ "success_count": { "type": "integer" },
11075
+ "failure_count": { "type": "integer" },
11076
+ "consecutive_fails": { "type": "integer" },
11077
+ "last_delivery_at": {
11078
+ "type": ["string", "null"],
11079
+ "format": "date-time"
11080
+ },
11081
+ "last_success_at": {
11082
+ "type": ["string", "null"],
11083
+ "format": "date-time"
11084
+ },
11085
+ "last_failure_at": {
11086
+ "type": ["string", "null"],
11087
+ "format": "date-time"
11088
+ }
11089
+ },
11090
+ "required": [
11091
+ "endpoint_id",
11092
+ "enabled",
11093
+ "domain",
11094
+ "rules"
11095
+ ]
11096
+ }, { "type": "null" }] },
11097
+ "sdkName": "getFunctionRouting",
11098
+ "summary": "Get a function's current route binding",
11099
+ "tag": "Functions",
11100
+ "tagCommand": "functions"
11101
+ },
11102
+ {
11103
+ "binaryResponse": false,
11104
+ "bodyRequired": false,
11105
+ "command": "get-function-test-run-trace",
11106
+ "description": "Returns the current end-to-end trace for a function test run.\nThe trace is intentionally partial while the test is still in\nflight: callers can poll this endpoint and watch it fill in\nfrom send -> inbound -> webhook deliveries -> outbound\nrequests, logs, and replies.\n",
11107
+ "hasJsonBody": false,
11108
+ "method": "GET",
11109
+ "operationId": "getFunctionTestRunTrace",
11110
+ "path": "/functions/{id}/test-runs/{run_id}/trace",
11111
+ "pathParams": [{
11112
+ "description": "Resource UUID",
11113
+ "enum": null,
11114
+ "name": "id",
11115
+ "required": true,
11116
+ "type": "string"
11117
+ }, {
11118
+ "description": "Function test run id returned by POST /functions/{id}/test.",
11119
+ "enum": null,
11120
+ "name": "run_id",
11121
+ "required": true,
11122
+ "type": "string"
11123
+ }],
11124
+ "queryParams": [],
11125
+ "requestSchema": null,
11126
+ "responseSchema": {
11127
+ "type": "object",
11128
+ "description": "End-to-end trace for a `POST /functions/{id}/test` run. The\nshape is stable, but many nested sections are null or empty\nuntil the corresponding phase has happened.\n",
11129
+ "properties": {
11130
+ "state": {
11131
+ "type": "string",
11132
+ "description": "High-level state for a function test run trace:\n - `send_failed`: the initial test email send failed.\n - `waiting_for_send`: the test run was created but no send result has been recorded yet.\n - `waiting_for_inbound`: the test send was queued and the matching inbound email has not arrived yet.\n - `waiting_for_function`: the inbound email arrived and webhook/function processing is still in flight.\n - `completed`: the function webhook completed successfully.\n - `failed`: webhook delivery exhausted retries.\n",
11133
+ "enum": [
11134
+ "send_failed",
11135
+ "waiting_for_send",
11136
+ "waiting_for_inbound",
11137
+ "waiting_for_function",
11138
+ "completed",
11139
+ "failed"
11140
+ ]
11141
+ },
11142
+ "test_run": {
11143
+ "type": "object",
11144
+ "properties": {
11145
+ "id": {
11146
+ "type": "string",
11147
+ "format": "uuid"
11148
+ },
11149
+ "function_id": {
11150
+ "type": "string",
11151
+ "format": "uuid"
11152
+ },
11153
+ "inbound_domain": { "type": "string" },
11154
+ "to": { "type": "string" },
11155
+ "from": { "type": "string" },
10748
11156
  "subject": { "type": "string" },
10749
11157
  "poll_since": {
10750
11158
  "type": "string",
@@ -11124,6 +11532,92 @@ const operationManifest = [
11124
11532
  "tag": "Functions",
11125
11533
  "tagCommand": "functions"
11126
11534
  },
11535
+ {
11536
+ "binaryResponse": false,
11537
+ "bodyRequired": false,
11538
+ "command": "get-org-routing-topology",
11539
+ "description": "Returns a single snapshot of how inbound mail is routed across\nthis org's active domains and functions: which active domain has\nwhich function bound, the org's fallback function (if any), and\nevery deployed function with no route bound. Use this to answer\n\"which of my functions actually receive mail?\" diagnostically.\n",
11540
+ "hasJsonBody": false,
11541
+ "method": "GET",
11542
+ "operationId": "getOrgRoutingTopology",
11543
+ "path": "/functions/routing-topology",
11544
+ "pathParams": [],
11545
+ "queryParams": [],
11546
+ "requestSchema": null,
11547
+ "responseSchema": {
11548
+ "type": "object",
11549
+ "description": "Org-wide map of function routing: which domain points at which\nfunction, the org's fallback binding (if any), and every\ndeployed function with no route currently bound.\n",
11550
+ "properties": {
11551
+ "domains": {
11552
+ "type": "array",
11553
+ "items": {
11554
+ "type": "object",
11555
+ "properties": {
11556
+ "domain_id": {
11557
+ "type": "string",
11558
+ "format": "uuid"
11559
+ },
11560
+ "domain": { "type": "string" },
11561
+ "routed_function": {
11562
+ "type": ["object", "null"],
11563
+ "properties": {
11564
+ "id": {
11565
+ "type": "string",
11566
+ "format": "uuid"
11567
+ },
11568
+ "name": { "type": "string" }
11569
+ },
11570
+ "required": ["id", "name"]
11571
+ },
11572
+ "endpoint_enabled": { "type": ["boolean", "null"] }
11573
+ },
11574
+ "required": [
11575
+ "domain_id",
11576
+ "domain",
11577
+ "routed_function",
11578
+ "endpoint_enabled"
11579
+ ]
11580
+ }
11581
+ },
11582
+ "fallback_function": {
11583
+ "type": ["object", "null"],
11584
+ "properties": {
11585
+ "id": {
11586
+ "type": "string",
11587
+ "format": "uuid"
11588
+ },
11589
+ "name": { "type": "string" }
11590
+ },
11591
+ "required": ["id", "name"]
11592
+ },
11593
+ "fallback_enabled": { "type": ["boolean", "null"] },
11594
+ "unrouted_functions": {
11595
+ "type": "array",
11596
+ "items": {
11597
+ "type": "object",
11598
+ "properties": {
11599
+ "id": {
11600
+ "type": "string",
11601
+ "format": "uuid"
11602
+ },
11603
+ "name": { "type": "string" }
11604
+ },
11605
+ "required": ["id", "name"]
11606
+ }
11607
+ }
11608
+ },
11609
+ "required": [
11610
+ "domains",
11611
+ "fallback_function",
11612
+ "fallback_enabled",
11613
+ "unrouted_functions"
11614
+ ]
11615
+ },
11616
+ "sdkName": "getOrgRoutingTopology",
11617
+ "summary": "Get the org's function routing topology",
11618
+ "tag": "Functions",
11619
+ "tagCommand": "functions"
11620
+ },
11127
11621
  {
11128
11622
  "binaryResponse": false,
11129
11623
  "bodyRequired": false,
@@ -11343,6 +11837,130 @@ const operationManifest = [
11343
11837
  "tag": "Functions",
11344
11838
  "tagCommand": "functions"
11345
11839
  },
11840
+ {
11841
+ "binaryResponse": false,
11842
+ "bodyRequired": true,
11843
+ "command": "set-function-route",
11844
+ "description": "Binds inbound mail to this function. The route target is either\na specific verified domain (scoped) or the org's fallback (any\nactive domain with no scoped binding). If another function is\nalready bound at the target, returns a `conflict` envelope\ndescribing the holder; re-issue with `takeover: true` to\ndeactivate that prior binding and install this one.\n",
11845
+ "hasJsonBody": true,
11846
+ "method": "PUT",
11847
+ "operationId": "setFunctionRoute",
11848
+ "path": "/functions/{id}/route",
11849
+ "pathParams": [{
11850
+ "description": "Resource UUID",
11851
+ "enum": null,
11852
+ "name": "id",
11853
+ "required": true,
11854
+ "type": "string"
11855
+ }],
11856
+ "queryParams": [],
11857
+ "requestSchema": {
11858
+ "type": "object",
11859
+ "description": "Target for a route binding. Either a specific verified domain\n(scoped) or the org-wide fallback. Pass `takeover: true` to\ndeactivate any conflicting binding before installing this one.\n",
11860
+ "properties": {
11861
+ "target": { "oneOf": [{
11862
+ "type": "object",
11863
+ "properties": {
11864
+ "kind": {
11865
+ "type": "string",
11866
+ "enum": ["domain"]
11867
+ },
11868
+ "domainId": {
11869
+ "type": "string",
11870
+ "format": "uuid"
11871
+ }
11872
+ },
11873
+ "required": ["kind", "domainId"]
11874
+ }, {
11875
+ "type": "object",
11876
+ "properties": { "kind": {
11877
+ "type": "string",
11878
+ "enum": ["fallback"]
11879
+ } },
11880
+ "required": ["kind"]
11881
+ }] },
11882
+ "takeover": {
11883
+ "type": "boolean",
11884
+ "description": "When true, deactivate any conflicting binding before installing this one."
11885
+ }
11886
+ },
11887
+ "required": ["target"]
11888
+ },
11889
+ "responseSchema": {
11890
+ "type": "object",
11891
+ "description": "On success, carries the new `routing`. On conflict, carries\n`conflict` describing the binding holder so the caller can\nre-issue with `takeover: true`.\n",
11892
+ "properties": {
11893
+ "routing": { "oneOf": [{
11894
+ "type": "object",
11895
+ "description": "A single route binding for a function. `domain` is null when the\nbinding is the org's fallback (any active domain without a scoped\nbinding); otherwise it carries the scoped domain. `rules` is\nreserved for future routing predicates.\n",
11896
+ "properties": {
11897
+ "endpoint_id": {
11898
+ "type": "string",
11899
+ "format": "uuid"
11900
+ },
11901
+ "enabled": { "type": "boolean" },
11902
+ "domain": {
11903
+ "type": ["object", "null"],
11904
+ "properties": {
11905
+ "id": {
11906
+ "type": "string",
11907
+ "format": "uuid"
11908
+ },
11909
+ "name": { "type": ["string", "null"] }
11910
+ },
11911
+ "required": ["id"]
11912
+ },
11913
+ "rules": {
11914
+ "type": "object",
11915
+ "description": "Future routing predicates. Currently empty."
11916
+ },
11917
+ "delivery_count": { "type": "integer" },
11918
+ "success_count": { "type": "integer" },
11919
+ "failure_count": { "type": "integer" },
11920
+ "consecutive_fails": { "type": "integer" },
11921
+ "last_delivery_at": {
11922
+ "type": ["string", "null"],
11923
+ "format": "date-time"
11924
+ },
11925
+ "last_success_at": {
11926
+ "type": ["string", "null"],
11927
+ "format": "date-time"
11928
+ },
11929
+ "last_failure_at": {
11930
+ "type": ["string", "null"],
11931
+ "format": "date-time"
11932
+ }
11933
+ },
11934
+ "required": [
11935
+ "endpoint_id",
11936
+ "enabled",
11937
+ "domain",
11938
+ "rules"
11939
+ ]
11940
+ }, { "type": "null" }] },
11941
+ "conflict": {
11942
+ "type": "object",
11943
+ "properties": {
11944
+ "kind": {
11945
+ "type": "string",
11946
+ "enum": ["http", "function"]
11947
+ },
11948
+ "functionId": {
11949
+ "type": ["string", "null"],
11950
+ "format": "uuid"
11951
+ },
11952
+ "functionName": { "type": ["string", "null"] },
11953
+ "url": { "type": ["string", "null"] }
11954
+ },
11955
+ "required": ["kind"]
11956
+ }
11957
+ }
11958
+ },
11959
+ "sdkName": "setFunctionRoute",
11960
+ "summary": "Bind a route to a function",
11961
+ "tag": "Functions",
11962
+ "tagCommand": "functions"
11963
+ },
11346
11964
  {
11347
11965
  "binaryResponse": false,
11348
11966
  "bodyRequired": true,
@@ -11496,6 +12114,37 @@ const operationManifest = [
11496
12114
  "tag": "Functions",
11497
12115
  "tagCommand": "functions"
11498
12116
  },
12117
+ {
12118
+ "binaryResponse": false,
12119
+ "bodyRequired": false,
12120
+ "command": "unset-function-route",
12121
+ "description": "Deactivates every active endpoint bound to this function. The\nfunction stays deployed but stops receiving inbound mail. Safe\nto call when no route is currently bound (no-op).\n",
12122
+ "hasJsonBody": false,
12123
+ "method": "DELETE",
12124
+ "operationId": "unsetFunctionRoute",
12125
+ "path": "/functions/{id}/route",
12126
+ "pathParams": [{
12127
+ "description": "Resource UUID",
12128
+ "enum": null,
12129
+ "name": "id",
12130
+ "required": true,
12131
+ "type": "string"
12132
+ }],
12133
+ "queryParams": [],
12134
+ "requestSchema": null,
12135
+ "responseSchema": {
12136
+ "type": "object",
12137
+ "properties": { "unrouted": {
12138
+ "type": "boolean",
12139
+ "enum": [true]
12140
+ } },
12141
+ "required": ["unrouted"]
12142
+ },
12143
+ "sdkName": "unsetFunctionRoute",
12144
+ "summary": "Unbind any route from a function",
12145
+ "tag": "Functions",
12146
+ "tagCommand": "functions"
12147
+ },
11499
12148
  {
11500
12149
  "binaryResponse": false,
11501
12150
  "bodyRequired": true,
@@ -12805,7 +13454,7 @@ const operationManifest = [
12805
13454
  "binaryResponse": false,
12806
13455
  "bodyRequired": true,
12807
13456
  "command": "send-email",
12808
- "description": "Sends an outbound email through Primitive's outbound relay. By default\nthe request returns once the relay accepts the message for delivery.\nSet `wait: true` to wait for the first downstream SMTP delivery outcome.\n\n**Host routing.** /send-mail is served by the attachments-\nsupporting host (`https://api.primitive.dev/v1`) so the\nrequest body can carry inline attachments up to ~30 MiB raw.\nThe primary host (`https://www.primitive.dev/api/v1`) also\naccepts /send-mail for attachment-free sends; sends WITH\nattachments to the primary host return 413\n`attachments_unsupported_on_this_endpoint`. The typed SDKs\nroute /send-mail to the attachments host automatically.\n",
13457
+ "description": "Sends an outbound email through Primitive's outbound relay. By default\nthe request returns once the relay accepts the message for delivery.\nSet `wait: true` to wait for the first downstream SMTP delivery outcome.\n\n**Host routing.** /send-mail is served by the canonical API host\n(`https://api.primitive.dev/v1`) so the request body can carry\ninline attachments up to ~30 MiB raw. The legacy dashboard\ncompatibility host (`https://www.primitive.dev/api/v1`) also accepts\n/send-mail, but Vercel request body limits apply before proxying.\nThe typed SDKs route /send-mail to the canonical API host\nautomatically.\n",
12809
13458
  "hasJsonBody": true,
12810
13459
  "method": "POST",
12811
13460
  "operationId": "sendEmail",
@@ -13302,19 +13951,15 @@ function cliApiHeadersFromEnv(env = process.env) {
13302
13951
  }
13303
13952
  function resolveCliApiRequestConfig(params) {
13304
13953
  const currentEnvironment = resolveConfigEnvironment(loadCliConfig(params.configDir));
13305
- const configuredApiBaseUrl1 = currentEnvironment?.config.api_base_url_1;
13306
- const configuredApiBaseUrl2 = currentEnvironment?.config.api_base_url_2;
13307
- if (currentEnvironment !== null && currentEnvironment.name !== "default" && params.apiBaseUrl1 === void 0 && configuredApiBaseUrl1 === void 0) throw new Errors.CLIError(`The active Primitive CLI environment \`${currentEnvironment.name}\` does not specify an api_base_url_1. Set one with \`primitive config set --environment ${currentEnvironment.name} --api-base-url-1 https://...\`, or switch to a different environment with \`primitive config use <name>\`. Refusing to fall back to the production default for a non-default environment.`, { exit: 1 });
13308
- const apiBaseUrl1 = params.apiBaseUrl1 !== void 0 ? normalizeApiBaseUrl1(params.apiBaseUrl1) : configuredApiBaseUrl1;
13309
- const apiBaseUrl2 = params.apiBaseUrl2 !== void 0 ? normalizeApiBaseUrl2(params.apiBaseUrl2) : configuredApiBaseUrl2;
13954
+ const configuredApiBaseUrl = currentEnvironment?.config.api_base_url;
13955
+ if (currentEnvironment !== null && currentEnvironment.name !== "default" && params.apiBaseUrl === void 0 && configuredApiBaseUrl === void 0) throw new Errors.CLIError(`The active Primitive CLI environment \`${currentEnvironment.name}\` does not specify an api_base_url. Set one with \`primitive config set --environment ${currentEnvironment.name} --api-base-url https://...\`, or switch to a different environment with \`primitive config use <name>\`. Refusing to fall back to the production default for a non-default environment.`, { exit: 1 });
13956
+ const apiBaseUrl = params.apiBaseUrl !== void 0 ? normalizeApiBaseUrl(params.apiBaseUrl) : configuredApiBaseUrl;
13310
13957
  return {
13311
- apiBaseUrl1,
13312
- apiBaseUrl2,
13313
- baseUrlOverridden: apiBaseUrl1 !== void 0 || apiBaseUrl2 !== void 0,
13958
+ apiBaseUrl,
13959
+ baseUrlOverridden: apiBaseUrl !== void 0,
13314
13960
  environmentName: currentEnvironment?.name ?? null,
13315
13961
  headers: mergeHeaders(currentEnvironment?.config.headers, cliApiHeadersFromEnv(params.env)),
13316
- resolvedApiBaseUrl1: normalizeApiBaseUrl1(apiBaseUrl1),
13317
- resolvedApiBaseUrl2: normalizeApiBaseUrl2(apiBaseUrl2)
13962
+ resolvedApiBaseUrl: normalizeApiBaseUrl(apiBaseUrl)
13318
13963
  };
13319
13964
  }
13320
13965
  function createCliApiClient(params) {
@@ -13322,15 +13967,14 @@ function createCliApiClient(params) {
13322
13967
  return {
13323
13968
  apiClient: new PrimitiveApiClient({
13324
13969
  apiKey: params.apiKey,
13325
- apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
13326
- apiBaseUrl2: requestConfig.resolvedApiBaseUrl2,
13970
+ apiBaseUrl: requestConfig.resolvedApiBaseUrl,
13327
13971
  headers: requestConfig.headers
13328
13972
  }),
13329
13973
  requestConfig
13330
13974
  };
13331
13975
  }
13332
- function oauthTokenEndpoint(apiBaseUrl1) {
13333
- const url = new URL(apiBaseUrl1);
13976
+ function oauthTokenEndpoint(apiBaseUrl) {
13977
+ const url = new URL(apiBaseUrl);
13334
13978
  url.pathname = "/oauth/token";
13335
13979
  url.search = "";
13336
13980
  url.hash = "";
@@ -13378,7 +14022,7 @@ async function refreshStoredCliCredentials(params) {
13378
14022
  });
13379
14023
  let response;
13380
14024
  try {
13381
- response = await fetchImpl(oauthTokenEndpoint(params.apiBaseUrl1), {
14025
+ response = await fetchImpl(oauthTokenEndpoint(params.apiBaseUrl), {
13382
14026
  body,
13383
14027
  headers: {
13384
14028
  ...params.headers ?? {},
@@ -13418,13 +14062,16 @@ async function createAuthenticatedCliApiClient(params) {
13418
14062
  const requestConfig = resolveCliApiRequestConfig(params);
13419
14063
  let auth = resolveCliAuth({
13420
14064
  apiKey: params.apiKey,
13421
- apiBaseUrl1: requestConfig.apiBaseUrl1,
13422
- apiBaseUrl2: requestConfig.apiBaseUrl2,
14065
+ apiBaseUrl: requestConfig.apiBaseUrl,
13423
14066
  configDir: params.configDir
13424
14067
  });
14068
+ if (auth.apiKey === void 0) {
14069
+ const hint = detectPrimitiveKeyEnvMisname(params.env ?? process.env);
14070
+ if (hint) process.stderr.write(`${hint}\n`);
14071
+ }
13425
14072
  if (auth.source === "stored" && auth.credentials) {
13426
14073
  const refreshed = await refreshStoredCliCredentials({
13427
- apiBaseUrl1: auth.apiBaseUrl1,
14074
+ apiBaseUrl: auth.apiBaseUrl,
13428
14075
  configDir: params.configDir,
13429
14076
  credentials: auth.credentials,
13430
14077
  credentialsLockHeld: params.credentialsLockHeld,
@@ -13441,8 +14088,7 @@ async function createAuthenticatedCliApiClient(params) {
13441
14088
  return {
13442
14089
  apiClient: new PrimitiveApiClient({
13443
14090
  apiKey: auth.apiKey,
13444
- apiBaseUrl1: auth.apiBaseUrl1,
13445
- apiBaseUrl2: auth.apiBaseUrl2,
14091
+ apiBaseUrl: auth.apiBaseUrl,
13446
14092
  headers: requestConfig.headers
13447
14093
  }),
13448
14094
  auth,
@@ -13758,7 +14404,7 @@ const NETWORK_ERROR_HINTS = {
13758
14404
  ENETUNREACH: "Hint: the network is unreachable. If you're behind a proxy and set HTTP(S)_PROXY, re-run with NODE_USE_ENV_PROXY=1 (Node 22+ ignores those env vars by default). `primitive doctor` reports the local environment in one shot.",
13759
14405
  ECONNREFUSED: "Hint: the server refused the connection. Check that your firewall allows egress to *.primitive.dev, that your PRIMITIVE_API_BASE_URL_* overrides (if any) point at a reachable host, and re-run with NODE_USE_ENV_PROXY=1 if you're behind a proxy. `primitive doctor` reports the local environment in one shot.",
13760
14406
  ETIMEDOUT: "Hint: the connection timed out. Check egress rules and proxy configuration; if you're behind a proxy, re-run with NODE_USE_ENV_PROXY=1 and HTTPS_PROXY set. `primitive doctor` reports the local environment in one shot.",
13761
- EAI_AGAIN: "Hint: DNS lookup failed. Check /etc/resolv.conf inside containers, and try `curl -v https://www.primitive.dev/api/v1/account` to confirm the host resolves. `primitive doctor` reports the local environment in one shot."
14407
+ EAI_AGAIN: "Hint: DNS lookup failed. Check /etc/resolv.conf inside containers, and try `curl -v https://api.primitive.dev/v1/account` to confirm the host resolves. `primitive doctor` reports the local environment in one shot."
13762
14408
  };
13763
14409
  function writeErrorWithHints(payload) {
13764
14410
  process.stderr.write(`${formatErrorPayload(payload)}\n`);
@@ -13787,8 +14433,8 @@ function writeErrorWithHints(payload) {
13787
14433
  */
13788
14434
  function surfaceUnauthorizedHint(params) {
13789
14435
  if (extractErrorCode(params.payload) !== API_ERROR_CODES.unauthorized || params.auth.source !== "stored") return;
13790
- if (params.baseUrlOverridden && params.auth.credentials !== null && params.auth.apiBaseUrl1 !== params.auth.credentials.api_base_url_1) {
13791
- process.stderr.write("Saved Primitive CLI credentials were rejected by the overridden API base URL. The saved credential is preserved; unset PRIMITIVE_API_BASE_URL_1, run `primitive config reset` to clear configured URL overrides, or run `primitive logout` to remove the stored credential.\n");
14436
+ if (params.baseUrlOverridden && params.auth.credentials !== null && params.auth.apiBaseUrl !== params.auth.credentials.api_base_url) {
14437
+ process.stderr.write("Saved Primitive CLI credentials were rejected by the overridden API base URL. The saved credential is preserved; unset PRIMITIVE_API_BASE_URL, run `primitive config reset` to clear configured URL overrides, or run `primitive logout` to remove the stored credential.\n");
13792
14438
  return;
13793
14439
  }
13794
14440
  process.stderr.write("Your saved Primitive CLI OAuth session was rejected. If the command was working a moment ago, please retry; brief retries often clear transient rejections. If it keeps failing, run `primitive logout && primitive signin` to mint a fresh session.\n");
@@ -13809,13 +14455,10 @@ async function runWithTiming(enabled, fn) {
13809
14455
  }
13810
14456
  }
13811
14457
  const TIME_FLAG_DESCRIPTION = "Print the wall-clock duration of this command to stderr after it completes (e.g. `[time: 1.34s]`). Useful for measuring `--wait` send latency, comparing CLI overhead, or capturing timing in scripts.";
13812
- const API_BASE_URL_1_FLAG_DESCRIPTION = "Override the primary API base URL. Internal testing only; not documented to customers.";
13813
- const API_BASE_URL_2_FLAG_DESCRIPTION = "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.";
13814
- const HOST_2_OPERATIONS = new Set(["sendEmail", "replyToEmail"]);
14458
+ const API_BASE_URL_FLAG_DESCRIPTION = "Override the API base URL. Internal testing only; not documented to customers.";
13815
14459
  const RESERVED_FLAG_NAMES = new Set([
13816
14460
  "api-key",
13817
- "api-base-url-1",
13818
- "api-base-url-2",
14461
+ "api-base-url",
13819
14462
  "raw-body",
13820
14463
  "body-file",
13821
14464
  "envelope",
@@ -13847,14 +14490,9 @@ function buildFlags(operation) {
13847
14490
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
13848
14491
  env: "PRIMITIVE_API_KEY"
13849
14492
  }),
13850
- "api-base-url-1": Flags.string({
13851
- description: "Override the primary API base URL. Internal testing only; not documented to customers.",
13852
- env: "PRIMITIVE_API_BASE_URL_1",
13853
- hidden: true
13854
- }),
13855
- "api-base-url-2": Flags.string({
13856
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
13857
- env: "PRIMITIVE_API_BASE_URL_2",
14493
+ "api-base-url": Flags.string({
14494
+ description: "Override the API base URL. Internal testing only; not documented to customers.",
14495
+ env: "PRIMITIVE_API_BASE_URL",
13858
14496
  hidden: true
13859
14497
  }),
13860
14498
  time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
@@ -13942,8 +14580,7 @@ function createOperationCommand(operation) {
13942
14580
  await runWithTiming(parsedFlags.time === true, async () => {
13943
14581
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
13944
14582
  apiKey: typeof parsedFlags["api-key"] === "string" ? parsedFlags["api-key"] : void 0,
13945
- apiBaseUrl1: typeof parsedFlags["api-base-url-1"] === "string" ? parsedFlags["api-base-url-1"] : void 0,
13946
- apiBaseUrl2: typeof parsedFlags["api-base-url-2"] === "string" ? parsedFlags["api-base-url-2"] : void 0,
14583
+ apiBaseUrl: typeof parsedFlags["api-base-url"] === "string" ? parsedFlags["api-base-url"] : void 0,
13947
14584
  configDir: this.config.configDir
13948
14585
  });
13949
14586
  let body;
@@ -13964,10 +14601,9 @@ function createOperationCommand(operation) {
13964
14601
  }
13965
14602
  if (operation.bodyRequired && body === void 0) throw new Errors.CLIError(`Operation ${operation.operationId} requires a body. Pass each field as a --flag (see --help) or supply JSON via --raw-body / --body-file.`);
13966
14603
  const operationFn = sdk_gen_exports[operation.sdkName];
13967
- const targetClient = HOST_2_OPERATIONS.has(operation.sdkName) ? apiClient._sendClient : apiClient.client;
13968
14604
  const result = await operationFn({
13969
14605
  body,
13970
- client: targetClient,
14606
+ client: apiClient.client,
13971
14607
  parseAs: operation.binaryResponse ? "blob" : "auto",
13972
14608
  path: collectValues(operation.pathParams, parsedFlags),
13973
14609
  query: collectValues(operation.queryParams, parsedFlags),
@@ -14721,14 +15357,9 @@ var ChatCommand = class ChatCommand extends Command {
14721
15357
  description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive signin` credentials)",
14722
15358
  env: "PRIMITIVE_API_KEY"
14723
15359
  }),
14724
- "api-base-url-1": Flags.string({
15360
+ "api-base-url": Flags.string({
14725
15361
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
14726
- env: "PRIMITIVE_API_BASE_URL_1",
14727
- hidden: true
14728
- }),
14729
- "api-base-url-2": Flags.string({
14730
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
14731
- env: "PRIMITIVE_API_BASE_URL_2",
15362
+ env: "PRIMITIVE_API_BASE_URL",
14732
15363
  hidden: true
14733
15364
  }),
14734
15365
  from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
@@ -14787,8 +15418,7 @@ var ChatCommand = class ChatCommand extends Command {
14787
15418
  await runWithTiming(flags.time, async () => {
14788
15419
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
14789
15420
  apiKey: flags["api-key"],
14790
- apiBaseUrl1: flags["api-base-url-1"],
14791
- apiBaseUrl2: flags["api-base-url-2"],
15421
+ apiBaseUrl: flags["api-base-url"],
14792
15422
  configDir: this.config.configDir
14793
15423
  });
14794
15424
  const authFailureContext = {
@@ -14859,7 +15489,7 @@ var ChatCommand = class ChatCommand extends Command {
14859
15489
  from,
14860
15490
  ...attachments !== void 0 ? { attachments } : {}
14861
15491
  },
14862
- client: apiClient._sendClient,
15492
+ client: apiClient.client,
14863
15493
  path: { id: parentReply.id },
14864
15494
  responseStyle: "fields"
14865
15495
  }) : await sendEmail({
@@ -14871,7 +15501,7 @@ var ChatCommand = class ChatCommand extends Command {
14871
15501
  ...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {},
14872
15502
  ...attachments !== void 0 ? { attachments } : {}
14873
15503
  },
14874
- client: apiClient._sendClient,
15504
+ client: apiClient.client,
14875
15505
  responseStyle: "fields"
14876
15506
  });
14877
15507
  if (sendResult.error) {
@@ -14996,14 +15626,9 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
14996
15626
  description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive signin` credentials)",
14997
15627
  env: "PRIMITIVE_API_KEY"
14998
15628
  }),
14999
- "api-base-url-1": Flags.string({
15629
+ "api-base-url": Flags.string({
15000
15630
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
15001
- env: "PRIMITIVE_API_BASE_URL_1",
15002
- hidden: true
15003
- }),
15004
- "api-base-url-2": Flags.string({
15005
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
15006
- env: "PRIMITIVE_API_BASE_URL_2",
15631
+ env: "PRIMITIVE_API_BASE_URL",
15007
15632
  hidden: true
15008
15633
  }),
15009
15634
  id: Flags.integer({
@@ -15068,8 +15693,7 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
15068
15693
  String(state.local_id)
15069
15694
  ];
15070
15695
  if (flags["api-key"] !== void 0) argv.push("--api-key", flags["api-key"]);
15071
- if (flags["api-base-url-1"] !== void 0) argv.push("--api-base-url-1", flags["api-base-url-1"]);
15072
- if (flags["api-base-url-2"] !== void 0) argv.push("--api-base-url-2", flags["api-base-url-2"]);
15696
+ if (flags["api-base-url"] !== void 0) argv.push("--api-base-url", flags["api-base-url"]);
15073
15697
  if (flags.json) argv.push("--json");
15074
15698
  if (flags.quiet) argv.push("--quiet");
15075
15699
  for (const attachment of flags.attachment ?? []) argv.push("--attachment", attachment);
@@ -15184,8 +15808,7 @@ function upsertCliEnvironmentAndClearCredentialsIfSwitched(params) {
15184
15808
  const previousActiveEnvironment = resolveConfigEnvironment(previousConfig);
15185
15809
  const previousEnvironment = previousActiveEnvironment?.name ?? null;
15186
15810
  const config = upsertCliEnvironment({
15187
- apiBaseUrl1: params.apiBaseUrl1,
15188
- apiBaseUrl2: params.apiBaseUrl2,
15811
+ apiBaseUrl: params.apiBaseUrl,
15189
15812
  config: previousConfig,
15190
15813
  environmentName: params.environmentName,
15191
15814
  headers: params.headers,
@@ -15193,7 +15816,7 @@ function upsertCliEnvironmentAndClearCredentialsIfSwitched(params) {
15193
15816
  });
15194
15817
  const activeEnvironment = resolveConfigEnvironment(config);
15195
15818
  const environment = activeEnvironment?.name ?? null;
15196
- const shouldClearCredentials = existsSync(credentialsPath(params.configDir)) && (previousEnvironment !== environment || previousActiveEnvironment?.config.api_base_url_1 !== activeEnvironment?.config.api_base_url_1);
15819
+ const shouldClearCredentials = existsSync(credentialsPath(params.configDir)) && (previousEnvironment !== environment || previousActiveEnvironment?.config.api_base_url !== activeEnvironment?.config.api_base_url);
15197
15820
  let removedCredentials = false;
15198
15821
  if (shouldClearCredentials) {
15199
15822
  const releaseLock = acquireCliCredentialsLock(params.configDir);
@@ -15243,10 +15866,9 @@ var ConfigSetCommand = class ConfigSetCommand extends Command {
15243
15866
  static flags = {
15244
15867
  environment: Flags.string({
15245
15868
  char: "e",
15246
- description: "Environment name to create or update"
15869
+ description: "Environment name to create or update. Defaults to the active environment, or default when none is active."
15247
15870
  }),
15248
- "api-base-url-1": Flags.string({ description: "Primary API base URL" }),
15249
- "api-base-url-2": Flags.string({ description: "Attachments-supporting API base URL" }),
15871
+ "api-base-url": Flags.string({ description: "API base URL" }),
15250
15872
  header: Flags.string({
15251
15873
  description: "Request header in name=value form. Repeatable.",
15252
15874
  multiple: true
@@ -15259,10 +15881,9 @@ var ConfigSetCommand = class ConfigSetCommand extends Command {
15259
15881
  async run() {
15260
15882
  const { flags } = await this.parse(ConfigSetCommand);
15261
15883
  const headers = flags.header ?? [];
15262
- if (flags["api-base-url-1"] === void 0 && flags["api-base-url-2"] === void 0 && headers.length === 0 && (flags["unset-header"] ?? []).length === 0) throw new Errors.CLIError("Nothing to set. Pass an API base URL, --header, or --unset-header.", { exit: 1 });
15884
+ if (flags["api-base-url"] === void 0 && headers.length === 0 && (flags["unset-header"] ?? []).length === 0) throw new Errors.CLIError("Nothing to set. Pass an API base URL, --header, or --unset-header.", { exit: 1 });
15263
15885
  const { environment, removedCredentials } = upsertCliEnvironmentAndClearCredentialsIfSwitched({
15264
- apiBaseUrl1: flags["api-base-url-1"],
15265
- apiBaseUrl2: flags["api-base-url-2"],
15886
+ apiBaseUrl: flags["api-base-url"],
15266
15887
  configDir: this.config.configDir,
15267
15888
  environmentName: flags.environment,
15268
15889
  headers,
@@ -15310,8 +15931,7 @@ var ConfigListCommand = class ConfigListCommand extends Command {
15310
15931
  const active = activeEnvironment === name ? "*" : " ";
15311
15932
  const headerNames = Object.keys(environment.headers ?? {});
15312
15933
  this.log(`${active} ${name}`);
15313
- if (environment.api_base_url_1) this.log(` api_base_url_1: ${environment.api_base_url_1}`);
15314
- if (environment.api_base_url_2) this.log(` api_base_url_2: ${environment.api_base_url_2}`);
15934
+ if (environment.api_base_url) this.log(` api_base_url: ${environment.api_base_url}`);
15315
15935
  this.log(` headers: ${headerNames.length > 0 ? headerNames.join(", ") : "(none)"}`);
15316
15936
  }
15317
15937
  }
@@ -15408,9 +16028,7 @@ function checkApiKey(opts) {
15408
16028
  message: "provided but does not start with prim_",
15409
16029
  hint: "Verify the key is a Primitive API key, not a value from another service."
15410
16030
  };
15411
- const primitiveKey = env.PRIMITIVE_KEY;
15412
- const primitiveApiKey = env.PRIMITIVE_API_KEY;
15413
- if ((primitiveKey?.length ?? 0) > 0 && (primitiveApiKey?.length ?? 0) === 0) return {
16031
+ if (detectPrimitiveKeyEnvMisname(env)) return {
15414
16032
  status: "fail",
15415
16033
  message: "PRIMITIVE_KEY is set but the CLI reads PRIMITIVE_API_KEY",
15416
16034
  hint: "Rename your env var, or re-run with PRIMITIVE_API_KEY=$PRIMITIVE_KEY."
@@ -15483,7 +16101,7 @@ async function checkAccount(client) {
15483
16101
  } catch (error) {
15484
16102
  const code = error instanceof Error && error.cause && typeof error.cause === "object" && typeof error.cause.code === "string" ? error.cause.code : void 0;
15485
16103
  const message = error instanceof Error ? error.message : String(error);
15486
- const hint = code === "ENETUNREACH" || code === "ECONNREFUSED" || code === "ETIMEDOUT" || code === "EAI_AGAIN" ? "Network unreachable. If you're behind a proxy, re-run with NODE_USE_ENV_PROXY=1 and HTTPS_PROXY set. If you're in a container, check that egress to *.primitive.dev is allowed." : "Inspect the error above. `curl https://www.primitive.dev/api/v1/account -H \"Authorization: Bearer $PRIMITIVE_API_KEY\"` is the fastest way to bisect CLI vs network.";
16104
+ const hint = code === "ENETUNREACH" || code === "ECONNREFUSED" || code === "ETIMEDOUT" || code === "EAI_AGAIN" ? "Network unreachable. If you're behind a proxy, re-run with NODE_USE_ENV_PROXY=1 and HTTPS_PROXY set. If you're in a container, check that egress to *.primitive.dev is allowed." : "Inspect the error above. `curl https://api.primitive.dev/v1/account -H \"Authorization: Bearer $PRIMITIVE_API_KEY\"` is the fastest way to bisect CLI vs network.";
15487
16105
  return {
15488
16106
  outcome: {
15489
16107
  status: "fail",
@@ -15537,14 +16155,9 @@ var DoctorCommand = class DoctorCommand extends Command {
15537
16155
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
15538
16156
  env: "PRIMITIVE_API_KEY"
15539
16157
  }),
15540
- "api-base-url-1": Flags.string({
16158
+ "api-base-url": Flags.string({
15541
16159
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
15542
- env: "PRIMITIVE_API_BASE_URL_1",
15543
- hidden: true
15544
- }),
15545
- "api-base-url-2": Flags.string({
15546
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
15547
- env: "PRIMITIVE_API_BASE_URL_2",
16160
+ env: "PRIMITIVE_API_BASE_URL",
15548
16161
  hidden: true
15549
16162
  })
15550
16163
  };
@@ -15570,8 +16183,7 @@ var DoctorCommand = class DoctorCommand extends Command {
15570
16183
  if (apiKeyCheck.status !== "fail") {
15571
16184
  const { apiClient, auth } = await createAuthenticatedCliApiClient({
15572
16185
  apiKey: flags["api-key"],
15573
- apiBaseUrl1: flags["api-base-url-1"],
15574
- apiBaseUrl2: flags["api-base-url-2"],
16186
+ apiBaseUrl: flags["api-base-url"],
15575
16187
  configDir: this.config.configDir
15576
16188
  });
15577
16189
  if (auth.apiKey !== void 0) {
@@ -15657,14 +16269,9 @@ var DomainsZoneFileCommand = class DomainsZoneFileCommand extends Command {
15657
16269
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
15658
16270
  env: "PRIMITIVE_API_KEY"
15659
16271
  }),
15660
- "api-base-url-1": Flags.string({
16272
+ "api-base-url": Flags.string({
15661
16273
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
15662
- env: "PRIMITIVE_API_BASE_URL_1",
15663
- hidden: true
15664
- }),
15665
- "api-base-url-2": Flags.string({
15666
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
15667
- env: "PRIMITIVE_API_BASE_URL_2",
16274
+ env: "PRIMITIVE_API_BASE_URL",
15668
16275
  hidden: true
15669
16276
  }),
15670
16277
  domain: Flags.string({ description: "Domain name to look up before downloading its zone file. Prefer --id when you have the domain id from `primitive domains add`." }),
@@ -15683,8 +16290,7 @@ var DomainsZoneFileCommand = class DomainsZoneFileCommand extends Command {
15683
16290
  await runWithTiming(flags.time, async () => {
15684
16291
  const { apiClient, auth, baseUrlOverridden, requestConfig } = await createAuthenticatedCliApiClient({
15685
16292
  apiKey: flags["api-key"],
15686
- apiBaseUrl1: flags["api-base-url-1"],
15687
- apiBaseUrl2: flags["api-base-url-2"],
16293
+ apiBaseUrl: flags["api-base-url"],
15688
16294
  configDir: this.config.configDir
15689
16295
  });
15690
16296
  let domainId = flags.id;
@@ -15711,7 +16317,7 @@ var DomainsZoneFileCommand = class DomainsZoneFileCommand extends Command {
15711
16317
  if (!domainId) throw new Errors.CLIError("Could not resolve a domain id.", { exit: 1 });
15712
16318
  let response;
15713
16319
  try {
15714
- response = await fetch(zoneFileUrl(requestConfig.resolvedApiBaseUrl1, domainId, flags["outbound-only"]), { headers: {
16320
+ response = await fetch(zoneFileUrl(requestConfig.resolvedApiBaseUrl, domainId, flags["outbound-only"]), { headers: {
15715
16321
  ...requestConfig.headers ?? {},
15716
16322
  ...auth.apiKey ? { authorization: `Bearer ${auth.apiKey}` } : {}
15717
16323
  } });
@@ -15746,14 +16352,14 @@ var DomainsZoneFileCommand = class DomainsZoneFileCommand extends Command {
15746
16352
  };
15747
16353
  //#endregion
15748
16354
  //#region src/oclif/commands/emails-latest.ts
15749
- const DEFAULT_LIMIT = 10;
15750
- const MAX_LIMIT = 100;
16355
+ const DEFAULT_LIMIT$1 = 10;
16356
+ const MAX_LIMIT$1 = 100;
15751
16357
  const SUBJECT_DISPLAY_WIDTH = 50;
15752
16358
  const ADDRESS_DISPLAY_WIDTH = 32;
15753
16359
  const ID_DISPLAY_WIDTH_SHORT = 8;
15754
16360
  const ID_DISPLAY_WIDTH_FULL = 36;
15755
16361
  const RECEIVED_DISPLAY_WIDTH = 19;
15756
- function truncate$1(value, width) {
16362
+ function truncate$2(value, width) {
15757
16363
  if (value.length <= width) return value.padEnd(width);
15758
16364
  return `${value.slice(0, width - 3)}...`;
15759
16365
  }
@@ -15767,10 +16373,10 @@ function formatReceivedAt(value) {
15767
16373
  function pickIdWidth(isTty) {
15768
16374
  return isTty ? ID_DISPLAY_WIDTH_SHORT : ID_DISPLAY_WIDTH_FULL;
15769
16375
  }
15770
- function formatRow(email, idWidth) {
15771
- return `${truncate$1(email.id.slice(0, idWidth), idWidth)} ${formatReceivedAt(email.received_at)} ${truncate$1(email.sender ?? "", ADDRESS_DISPLAY_WIDTH)} ${truncate$1(email.recipient ?? "", ADDRESS_DISPLAY_WIDTH)} ${truncate$1((email.subject ?? "").replace(/\s+/g, " "), SUBJECT_DISPLAY_WIDTH)}`;
16376
+ function formatRow$1(email, idWidth) {
16377
+ return `${truncate$2(email.id.slice(0, idWidth), idWidth)} ${formatReceivedAt(email.received_at)} ${truncate$2(email.sender ?? "", ADDRESS_DISPLAY_WIDTH)} ${truncate$2(email.recipient ?? "", ADDRESS_DISPLAY_WIDTH)} ${truncate$2((email.subject ?? "").replace(/\s+/g, " "), SUBJECT_DISPLAY_WIDTH)}`;
15772
16378
  }
15773
- function formatHeader(idWidth) {
16379
+ function formatHeader$1(idWidth) {
15774
16380
  return `${"ID".padEnd(idWidth)} ${"RECEIVED (UTC)".padEnd(RECEIVED_DISPLAY_WIDTH)} ${"FROM".padEnd(ADDRESS_DISPLAY_WIDTH)} ${"TO".padEnd(ADDRESS_DISPLAY_WIDTH)} SUBJECT`;
15775
16381
  }
15776
16382
  var EmailsLatestCommand = class EmailsLatestCommand extends Command {
@@ -15791,21 +16397,16 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
15791
16397
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
15792
16398
  env: "PRIMITIVE_API_KEY"
15793
16399
  }),
15794
- "api-base-url-1": Flags.string({
16400
+ "api-base-url": Flags.string({
15795
16401
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
15796
- env: "PRIMITIVE_API_BASE_URL_1",
15797
- hidden: true
15798
- }),
15799
- "api-base-url-2": Flags.string({
15800
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
15801
- env: "PRIMITIVE_API_BASE_URL_2",
16402
+ env: "PRIMITIVE_API_BASE_URL",
15802
16403
  hidden: true
15803
16404
  }),
15804
16405
  limit: Flags.integer({
15805
- description: `Number of rows to print (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}).`,
15806
- default: DEFAULT_LIMIT,
16406
+ description: `Number of rows to print (1-${MAX_LIMIT$1}, default ${DEFAULT_LIMIT$1}).`,
16407
+ default: DEFAULT_LIMIT$1,
15807
16408
  min: 1,
15808
- max: MAX_LIMIT
16409
+ max: MAX_LIMIT$1
15809
16410
  }),
15810
16411
  json: Flags.boolean({ description: "Print the raw response envelope (with full UUIDs and meta) as JSON on STDOUT instead of the text table. Useful for piping into `jq`, capturing ids for follow-up commands, or scripting." }),
15811
16412
  time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
@@ -15815,8 +16416,7 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
15815
16416
  await runWithTiming(flags.time, async () => {
15816
16417
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
15817
16418
  apiKey: flags["api-key"],
15818
- apiBaseUrl1: flags["api-base-url-1"],
15819
- apiBaseUrl2: flags["api-base-url-2"],
16419
+ apiBaseUrl: flags["api-base-url"],
15820
16420
  configDir: this.config.configDir
15821
16421
  });
15822
16422
  const result = await listEmails({
@@ -15847,8 +16447,8 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
15847
16447
  return;
15848
16448
  }
15849
16449
  const idWidth = pickIdWidth(Boolean(process.stdout.isTTY));
15850
- process.stderr.write(`${formatHeader(idWidth)}\n`);
15851
- for (const row of rows) this.log(formatRow(row, idWidth));
16450
+ process.stderr.write(`${formatHeader$1(idWidth)}\n`);
16451
+ for (const row of rows) this.log(formatRow$1(row, idWidth));
15852
16452
  });
15853
16453
  }
15854
16454
  };
@@ -15871,14 +16471,9 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
15871
16471
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
15872
16472
  env: "PRIMITIVE_API_KEY"
15873
16473
  }),
15874
- "api-base-url-1": Flags.string({
16474
+ "api-base-url": Flags.string({
15875
16475
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
15876
- env: "PRIMITIVE_API_BASE_URL_1",
15877
- hidden: true
15878
- }),
15879
- "api-base-url-2": Flags.string({
15880
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
15881
- env: "PRIMITIVE_API_BASE_URL_2",
16476
+ env: "PRIMITIVE_API_BASE_URL",
15882
16477
  hidden: true
15883
16478
  }),
15884
16479
  body: Flags.string({ description: "Full-text body filter" }),
@@ -15922,8 +16517,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
15922
16517
  const { flags } = await this.parse(EmailsWaitCommand);
15923
16518
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
15924
16519
  apiKey: flags["api-key"],
15925
- apiBaseUrl1: flags["api-base-url-1"],
15926
- apiBaseUrl2: flags["api-base-url-2"],
16520
+ apiBaseUrl: flags["api-base-url"],
15927
16521
  configDir: this.config.configDir
15928
16522
  });
15929
16523
  let since;
@@ -15965,10 +16559,10 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
15965
16559
  for (const email of collectNewAcceptedEmails(page.rows, seenIds)) {
15966
16560
  if (flags.table) {
15967
16561
  if (!headerPrinted) {
15968
- process.stderr.write(`${formatHeader(idWidth)}\n`);
16562
+ process.stderr.write(`${formatHeader$1(idWidth)}\n`);
15969
16563
  headerPrinted = true;
15970
16564
  }
15971
- this.log(formatRow(email, idWidth));
16565
+ this.log(formatRow$1(email, idWidth));
15972
16566
  } else this.log(JSON.stringify(email));
15973
16567
  matched += 1;
15974
16568
  if (matched >= flags.number) return;
@@ -15999,14 +16593,9 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
15999
16593
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
16000
16594
  env: "PRIMITIVE_API_KEY"
16001
16595
  }),
16002
- "api-base-url-1": Flags.string({
16596
+ "api-base-url": Flags.string({
16003
16597
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
16004
- env: "PRIMITIVE_API_BASE_URL_1",
16005
- hidden: true
16006
- }),
16007
- "api-base-url-2": Flags.string({
16008
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
16009
- env: "PRIMITIVE_API_BASE_URL_2",
16598
+ env: "PRIMITIVE_API_BASE_URL",
16010
16599
  hidden: true
16011
16600
  }),
16012
16601
  body: Flags.string({ description: "Full-text body filter" }),
@@ -16046,8 +16635,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
16046
16635
  const { flags } = await this.parse(EmailsWatchCommand);
16047
16636
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
16048
16637
  apiKey: flags["api-key"],
16049
- apiBaseUrl1: flags["api-base-url-1"],
16050
- apiBaseUrl2: flags["api-base-url-2"],
16638
+ apiBaseUrl: flags["api-base-url"],
16051
16639
  configDir: this.config.configDir
16052
16640
  });
16053
16641
  let since;
@@ -16090,10 +16678,10 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
16090
16678
  if (flags.jsonl) this.log(JSON.stringify(email));
16091
16679
  else {
16092
16680
  if (!headerPrinted) {
16093
- process.stderr.write(`${formatHeader(idWidth)}\n`);
16681
+ process.stderr.write(`${formatHeader$1(idWidth)}\n`);
16094
16682
  headerPrinted = true;
16095
16683
  }
16096
- this.log(formatRow(email, idWidth));
16684
+ this.log(formatRow$1(email, idWidth));
16097
16685
  }
16098
16686
  printed += 1;
16099
16687
  if (flags.number && printed >= flags.number) return;
@@ -16667,6 +17255,18 @@ const SECRET_SOURCE_FLAGS_DESCRIPTION = "Safe sources: --secret-from-env KEY rea
16667
17255
  const SINGLE_SECRET_VALUE_SOURCE_DESCRIPTION = "Instead of --value, use --value-from-env ENV_VAR, --value-file PATH, --value-from-env-file FILE[:KEY], or --stdin to avoid putting the secret value in shell history or process argv. If KEY is omitted from --value-from-env-file, the command's --key is used.";
16668
17256
  //#endregion
16669
17257
  //#region src/oclif/commands/functions-deploy.ts
17258
+ async function writeRouteStatusHint(apiClient, functionId) {
17259
+ try {
17260
+ const result = await getFunctionRouting({
17261
+ client: apiClient.client,
17262
+ path: { id: functionId },
17263
+ responseStyle: "fields"
17264
+ });
17265
+ if (result.error) return;
17266
+ if (result.data?.data) process.stderr.write("Route bound. Function will receive inbound mail.\n");
17267
+ else process.stderr.write(`Deployed but no route is bound. Inbound mail will not reach this function until you bind one: primitive functions route-set --id ${functionId} --domain <domain-id> (or --fallback)\n`);
17268
+ } catch {}
17269
+ }
16670
17270
  async function runDeployWithSecrets(api, params) {
16671
17271
  const createResult = await api.createFunction({
16672
17272
  code: params.code,
@@ -16792,14 +17392,9 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
16792
17392
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
16793
17393
  env: "PRIMITIVE_API_KEY"
16794
17394
  }),
16795
- "api-base-url-1": Flags.string({
17395
+ "api-base-url": Flags.string({
16796
17396
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
16797
- env: "PRIMITIVE_API_BASE_URL_1",
16798
- hidden: true
16799
- }),
16800
- "api-base-url-2": Flags.string({
16801
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
16802
- env: "PRIMITIVE_API_BASE_URL_2",
17397
+ env: "PRIMITIVE_API_BASE_URL",
16803
17398
  hidden: true
16804
17399
  }),
16805
17400
  name: Flags.string({
@@ -16877,8 +17472,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
16877
17472
  emitRawSendMailFetchWarning(code, (chunk) => process.stderr.write(chunk));
16878
17473
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
16879
17474
  apiKey: flags["api-key"],
16880
- apiBaseUrl1: flags["api-base-url-1"],
16881
- apiBaseUrl2: flags["api-base-url-2"],
17475
+ apiBaseUrl: flags["api-base-url"],
16882
17476
  configDir: this.config.configDir
16883
17477
  });
16884
17478
  const authFailureContext = {
@@ -16972,10 +17566,11 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
16972
17566
  const detail = waitResult.function.deploy_error ? `: ${waitResult.function.deploy_error}` : ".";
16973
17567
  process.stderr.write(`Function ${payload.id} deploy failed${detail}\n`);
16974
17568
  process.exitCode = 1;
16975
- }
17569
+ } else await writeRouteStatusHint(apiClient, payload.id);
16976
17570
  return;
16977
17571
  }
16978
17572
  this.log(JSON.stringify(payload, null, 2));
17573
+ await writeRouteStatusHint(apiClient, payload.id);
16979
17574
  });
16980
17575
  }
16981
17576
  async runSourceMode(flags, sourceDir) {
@@ -16992,8 +17587,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
16992
17587
  }
16993
17588
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
16994
17589
  apiKey: flags["api-key"],
16995
- apiBaseUrl1: flags["api-base-url-1"],
16996
- apiBaseUrl2: flags["api-base-url-2"],
17590
+ apiBaseUrl: flags["api-base-url"],
16997
17591
  configDir: this.config.configDir
16998
17592
  });
16999
17593
  const authFailureContext = {
@@ -17068,10 +17662,11 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
17068
17662
  const detail = waitResult.function.deploy_error ? `: ${waitResult.function.deploy_error}` : ".";
17069
17663
  process.stderr.write(`Function ${payload.id} deploy failed${detail}\n`);
17070
17664
  process.exitCode = 1;
17071
- }
17665
+ } else await writeRouteStatusHint(apiClient, payload.id);
17072
17666
  return;
17073
17667
  }
17074
17668
  this.log(JSON.stringify(payload, null, 2));
17669
+ await writeRouteStatusHint(apiClient, payload.id);
17075
17670
  }
17076
17671
  };
17077
17672
  //#endregion
@@ -17082,8 +17677,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
17082
17677
  name: "Primitive Team",
17083
17678
  url: "https://primitive.dev"
17084
17679
  };
17085
- const SDK_VERSION_RANGE = "^0.35.0";
17086
- const CLI_VERSION_RANGE = "^0.35.0";
17680
+ const SDK_VERSION_RANGE = "^0.36.0";
17681
+ const CLI_VERSION_RANGE = "^0.36.0";
17087
17682
  const ESBUILD_VERSION_RANGE = "^0.27.0";
17088
17683
  function renderHandler() {
17089
17684
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -17240,7 +17835,7 @@ export default {
17240
17835
 
17241
17836
  const client = createPrimitiveClient({
17242
17837
  apiKey: env.PRIMITIVE_API_KEY,
17243
- apiBaseUrl1: env.PRIMITIVE_API_BASE_URL,
17838
+ apiBaseUrl: env.PRIMITIVE_API_BASE_URL,
17244
17839
  });
17245
17840
 
17246
17841
  // To add an LLM or another API, store its key as a Function secret.
@@ -17264,7 +17859,7 @@ export default {
17264
17859
  // route "support@" to a ticketing flow and "sales@" to a lead
17265
17860
  // capture flow before calling client.reply.
17266
17861
 
17267
- // client.reply routes through POST /api/v1/emails/{id}/reply
17862
+ // client.reply routes through POST /v1/emails/{id}/reply
17268
17863
  // (NOT /send-mail) so the server derives recipients, the
17269
17864
  // \`Re: <parent>\` subject, threading headers, and the
17270
17865
  // in_reply_to_email_id foreign key automatically. The FK is
@@ -17390,6 +17985,23 @@ After the first deploy, copy the returned function id into your shell:
17390
17985
  export PRIMITIVE_FUNCTION_ID=<fn-id>
17391
17986
  \`\`\`
17392
17987
 
17988
+ ## Bind a route
17989
+
17990
+ A deployed Function does not receive inbound mail until a route is
17991
+ bound to it. Without this step, the Function is installed in the
17992
+ runtime but unreachable, and \`functions test\` will refuse to send
17993
+ (returns 422 \`no_endpoint\`).
17994
+
17995
+ \`\`\`
17996
+ primitive functions route-set --id "$PRIMITIVE_FUNCTION_ID" --domain <domain-id>
17997
+ \`\`\`
17998
+
17999
+ Use \`--fallback\` instead of \`--domain\` to bind the Function as the
18000
+ org-wide fallback for any active domain that has no scoped binding.
18001
+ Run \`primitive functions route-get --id "$PRIMITIVE_FUNCTION_ID"\` to
18002
+ inspect the current binding, or \`primitive functions routing-topology\`
18003
+ for the org-wide view of which domain points at which Function.
18004
+
17393
18005
  ## Prove it works
17394
18006
 
17395
18007
  \`\`\`
@@ -17645,14 +18257,9 @@ var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
17645
18257
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
17646
18258
  env: "PRIMITIVE_API_KEY"
17647
18259
  }),
17648
- "api-base-url-1": Flags.string({
18260
+ "api-base-url": Flags.string({
17649
18261
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
17650
- env: "PRIMITIVE_API_BASE_URL_1",
17651
- hidden: true
17652
- }),
17653
- "api-base-url-2": Flags.string({
17654
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
17655
- env: "PRIMITIVE_API_BASE_URL_2",
18262
+ env: "PRIMITIVE_API_BASE_URL",
17656
18263
  hidden: true
17657
18264
  }),
17658
18265
  id: Flags.string({
@@ -17683,8 +18290,7 @@ var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
17683
18290
  await runWithTiming(flags.time, async () => {
17684
18291
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
17685
18292
  apiKey: flags["api-key"],
17686
- apiBaseUrl1: flags["api-base-url-1"],
17687
- apiBaseUrl2: flags["api-base-url-2"],
18293
+ apiBaseUrl: flags["api-base-url"],
17688
18294
  configDir: this.config.configDir
17689
18295
  });
17690
18296
  const seenIds = /* @__PURE__ */ new Set();
@@ -17843,14 +18449,9 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
17843
18449
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
17844
18450
  env: "PRIMITIVE_API_KEY"
17845
18451
  }),
17846
- "api-base-url-1": Flags.string({
18452
+ "api-base-url": Flags.string({
17847
18453
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
17848
- env: "PRIMITIVE_API_BASE_URL_1",
17849
- hidden: true
17850
- }),
17851
- "api-base-url-2": Flags.string({
17852
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
17853
- env: "PRIMITIVE_API_BASE_URL_2",
18454
+ env: "PRIMITIVE_API_BASE_URL",
17854
18455
  hidden: true
17855
18456
  }),
17856
18457
  id: Flags.string({
@@ -17919,8 +18520,7 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
17919
18520
  emitRawSendMailFetchWarning(code, (chunk) => process.stderr.write(chunk));
17920
18521
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
17921
18522
  apiKey: flags["api-key"],
17922
- apiBaseUrl1: flags["api-base-url-1"],
17923
- apiBaseUrl2: flags["api-base-url-2"],
18523
+ apiBaseUrl: flags["api-base-url"],
17924
18524
  configDir: this.config.configDir
17925
18525
  });
17926
18526
  const authFailureContext = {
@@ -18012,6 +18612,239 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
18012
18612
  }
18013
18613
  };
18014
18614
  //#endregion
18615
+ //#region src/oclif/commands/functions-route-get.ts
18616
+ var FunctionsRouteGetCommand = class FunctionsRouteGetCommand extends Command {
18617
+ static description = `Show the current route binding for a function. Returns the binding (domain or fallback, with delivery counters) or null when no route is bound.`;
18618
+ static summary = "Show a function's current route binding";
18619
+ static examples = ["<%= config.bin %> functions route-get --id <fn-id>"];
18620
+ static flags = {
18621
+ "api-key": Flags.string({
18622
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
18623
+ env: "PRIMITIVE_API_KEY"
18624
+ }),
18625
+ "api-base-url": Flags.string({
18626
+ description: API_BASE_URL_FLAG_DESCRIPTION,
18627
+ env: "PRIMITIVE_API_BASE_URL",
18628
+ hidden: true
18629
+ }),
18630
+ id: Flags.string({
18631
+ description: "Function id (UUID) whose route binding to show.",
18632
+ required: true
18633
+ }),
18634
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
18635
+ };
18636
+ async run() {
18637
+ const { flags } = await this.parse(FunctionsRouteGetCommand);
18638
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
18639
+ apiKey: flags["api-key"],
18640
+ apiBaseUrl: flags["api-base-url"],
18641
+ configDir: this.config.configDir
18642
+ });
18643
+ await runWithTiming(flags.time, async () => {
18644
+ const result = await getFunctionRouting({
18645
+ client: apiClient.client,
18646
+ path: { id: flags.id },
18647
+ responseStyle: "fields"
18648
+ });
18649
+ if (result.error) {
18650
+ const payload = extractErrorPayload(result.error);
18651
+ writeErrorWithHints(payload);
18652
+ surfaceUnauthorizedHint({
18653
+ auth,
18654
+ baseUrlOverridden,
18655
+ configDir: this.config.configDir,
18656
+ payload
18657
+ });
18658
+ process.exitCode = 1;
18659
+ return;
18660
+ }
18661
+ this.log(JSON.stringify(result.data.data, null, 2));
18662
+ });
18663
+ }
18664
+ };
18665
+ //#endregion
18666
+ //#region src/oclif/commands/functions-route-set.ts
18667
+ var FunctionsRouteSetCommand = class FunctionsRouteSetCommand extends Command {
18668
+ static description = `Bind inbound mail to a function by setting its route target.
18669
+
18670
+ Exactly one of --domain or --fallback is required. --domain scopes the
18671
+ binding to a single verified inbound domain. --fallback binds the
18672
+ function to any active domain that has no scoped binding of its own.
18673
+
18674
+ If another function is already bound at the target, the API returns a
18675
+ conflict envelope rather than overwriting; re-run with --takeover to
18676
+ deactivate the prior binding before installing this one.`;
18677
+ static summary = "Bind inbound mail to a function";
18678
+ static examples = [
18679
+ "<%= config.bin %> functions route-set --id <fn-id> --domain <domain-id>",
18680
+ "<%= config.bin %> functions route-set --id <fn-id> --fallback",
18681
+ "<%= config.bin %> functions route-set --id <fn-id> --domain <domain-id> --takeover"
18682
+ ];
18683
+ static flags = {
18684
+ "api-key": Flags.string({
18685
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
18686
+ env: "PRIMITIVE_API_KEY"
18687
+ }),
18688
+ "api-base-url": Flags.string({
18689
+ description: API_BASE_URL_FLAG_DESCRIPTION,
18690
+ env: "PRIMITIVE_API_BASE_URL",
18691
+ hidden: true
18692
+ }),
18693
+ id: Flags.string({
18694
+ description: "Function id (UUID) to bind a route to.",
18695
+ required: true
18696
+ }),
18697
+ domain: Flags.string({
18698
+ description: "Verified inbound domain id (UUID) to scope this function to. Mutually exclusive with --fallback.",
18699
+ exclusive: ["fallback"]
18700
+ }),
18701
+ fallback: Flags.boolean({
18702
+ description: "Bind this function as the org fallback (any active domain without a scoped binding). Mutually exclusive with --domain.",
18703
+ exclusive: ["domain"]
18704
+ }),
18705
+ takeover: Flags.boolean({ description: "Deactivate any conflicting binding before installing this one. Without this flag, the API returns a `conflict` envelope when another function is already bound at the target." }),
18706
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
18707
+ };
18708
+ async run() {
18709
+ const { flags } = await this.parse(FunctionsRouteSetCommand);
18710
+ if (!flags.domain && !flags.fallback) {
18711
+ process.stderr.write("Provide exactly one of --domain (scoped binding) or --fallback (org fallback).\n");
18712
+ process.exitCode = 1;
18713
+ return;
18714
+ }
18715
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
18716
+ apiKey: flags["api-key"],
18717
+ apiBaseUrl: flags["api-base-url"],
18718
+ configDir: this.config.configDir
18719
+ });
18720
+ await runWithTiming(flags.time, async () => {
18721
+ const target = flags.domain ? {
18722
+ kind: "domain",
18723
+ domainId: flags.domain
18724
+ } : { kind: "fallback" };
18725
+ const result = await setFunctionRoute({
18726
+ client: apiClient.client,
18727
+ path: { id: flags.id },
18728
+ body: {
18729
+ target,
18730
+ ...flags.takeover ? { takeover: true } : {}
18731
+ },
18732
+ responseStyle: "fields"
18733
+ });
18734
+ if (result.error) {
18735
+ const payload = extractErrorPayload(result.error);
18736
+ writeErrorWithHints(payload);
18737
+ surfaceUnauthorizedHint({
18738
+ auth,
18739
+ baseUrlOverridden,
18740
+ configDir: this.config.configDir,
18741
+ payload
18742
+ });
18743
+ process.exitCode = 1;
18744
+ return;
18745
+ }
18746
+ this.log(JSON.stringify(result.data.data, null, 2));
18747
+ });
18748
+ }
18749
+ };
18750
+ //#endregion
18751
+ //#region src/oclif/commands/functions-route-unset.ts
18752
+ var FunctionsRouteUnsetCommand = class FunctionsRouteUnsetCommand extends Command {
18753
+ static description = `Unbind every active route from a function. The function stays deployed but stops receiving inbound mail. Safe to call when no route is currently bound.`;
18754
+ static summary = "Unbind any route from a function";
18755
+ static examples = ["<%= config.bin %> functions route-unset --id <fn-id>"];
18756
+ static flags = {
18757
+ "api-key": Flags.string({
18758
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
18759
+ env: "PRIMITIVE_API_KEY"
18760
+ }),
18761
+ "api-base-url": Flags.string({
18762
+ description: API_BASE_URL_FLAG_DESCRIPTION,
18763
+ env: "PRIMITIVE_API_BASE_URL",
18764
+ hidden: true
18765
+ }),
18766
+ id: Flags.string({
18767
+ description: "Function id (UUID) whose routes should be unbound.",
18768
+ required: true
18769
+ }),
18770
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
18771
+ };
18772
+ async run() {
18773
+ const { flags } = await this.parse(FunctionsRouteUnsetCommand);
18774
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
18775
+ apiKey: flags["api-key"],
18776
+ apiBaseUrl: flags["api-base-url"],
18777
+ configDir: this.config.configDir
18778
+ });
18779
+ await runWithTiming(flags.time, async () => {
18780
+ const result = await unsetFunctionRoute({
18781
+ client: apiClient.client,
18782
+ path: { id: flags.id },
18783
+ responseStyle: "fields"
18784
+ });
18785
+ if (result.error) {
18786
+ const payload = extractErrorPayload(result.error);
18787
+ writeErrorWithHints(payload);
18788
+ surfaceUnauthorizedHint({
18789
+ auth,
18790
+ baseUrlOverridden,
18791
+ configDir: this.config.configDir,
18792
+ payload
18793
+ });
18794
+ process.exitCode = 1;
18795
+ return;
18796
+ }
18797
+ this.log(JSON.stringify(result.data.data, null, 2));
18798
+ });
18799
+ }
18800
+ };
18801
+ //#endregion
18802
+ //#region src/oclif/commands/functions-routing-topology.ts
18803
+ var FunctionsRoutingTopologyCommand = class FunctionsRoutingTopologyCommand extends Command {
18804
+ static description = `Show the org-wide function routing topology. Lists every active domain with its bound function (if any), the org fallback function (if any), and every deployed function with no route currently bound.`;
18805
+ static summary = "Show the org-wide function routing topology";
18806
+ static examples = ["<%= config.bin %> functions routing-topology"];
18807
+ static flags = {
18808
+ "api-key": Flags.string({
18809
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
18810
+ env: "PRIMITIVE_API_KEY"
18811
+ }),
18812
+ "api-base-url": Flags.string({
18813
+ description: API_BASE_URL_FLAG_DESCRIPTION,
18814
+ env: "PRIMITIVE_API_BASE_URL",
18815
+ hidden: true
18816
+ }),
18817
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
18818
+ };
18819
+ async run() {
18820
+ const { flags } = await this.parse(FunctionsRoutingTopologyCommand);
18821
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
18822
+ apiKey: flags["api-key"],
18823
+ apiBaseUrl: flags["api-base-url"],
18824
+ configDir: this.config.configDir
18825
+ });
18826
+ await runWithTiming(flags.time, async () => {
18827
+ const result = await getOrgRoutingTopology({
18828
+ client: apiClient.client,
18829
+ responseStyle: "fields"
18830
+ });
18831
+ if (result.error) {
18832
+ const payload = extractErrorPayload(result.error);
18833
+ writeErrorWithHints(payload);
18834
+ surfaceUnauthorizedHint({
18835
+ auth,
18836
+ baseUrlOverridden,
18837
+ configDir: this.config.configDir,
18838
+ payload
18839
+ });
18840
+ process.exitCode = 1;
18841
+ return;
18842
+ }
18843
+ this.log(JSON.stringify(result.data.data, null, 2));
18844
+ });
18845
+ }
18846
+ };
18847
+ //#endregion
18015
18848
  //#region src/oclif/commands/functions-set-secret.ts
18016
18849
  async function runSetSecret(api, params) {
18017
18850
  const setResult = await api.setSecret({
@@ -18103,14 +18936,9 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
18103
18936
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
18104
18937
  env: "PRIMITIVE_API_KEY"
18105
18938
  }),
18106
- "api-base-url-1": Flags.string({
18939
+ "api-base-url": Flags.string({
18107
18940
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
18108
- env: "PRIMITIVE_API_BASE_URL_1",
18109
- hidden: true
18110
- }),
18111
- "api-base-url-2": Flags.string({
18112
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
18113
- env: "PRIMITIVE_API_BASE_URL_2",
18941
+ env: "PRIMITIVE_API_BASE_URL",
18114
18942
  hidden: true
18115
18943
  }),
18116
18944
  id: Flags.string({
@@ -18134,8 +18962,7 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
18134
18962
  await runWithTiming(flags.time, async () => {
18135
18963
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
18136
18964
  apiKey: flags["api-key"],
18137
- apiBaseUrl1: flags["api-base-url-1"],
18138
- apiBaseUrl2: flags["api-base-url-2"],
18965
+ apiBaseUrl: flags["api-base-url"],
18139
18966
  configDir: this.config.configDir
18140
18967
  });
18141
18968
  const authFailureContext = {
@@ -18195,6 +19022,7 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
18195
19022
  return;
18196
19023
  }
18197
19024
  this.log(JSON.stringify(outcome.result, null, 2));
19025
+ if (outcome.result.redeploy === void 0) process.stderr.write(`Secret ${flags.key} saved. Not live until redeploy. Re-run with --redeploy, or run \`primitive functions redeploy --id ${flags.id} --file <bundle.js>\`.\n`);
18198
19026
  });
18199
19027
  }
18200
19028
  };
@@ -18319,7 +19147,7 @@ async function maybeWriteEndpointNoiseWarning(params) {
18319
19147
  } catch {}
18320
19148
  }
18321
19149
  var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Command {
18322
- static description = "Send a real test email through MX to trigger this function. With --wait, blocks until the function has processed the inbound; with --show-sends, also prints any outbound sends the function emitted in response.";
19150
+ static description = "Send a real test email through MX to trigger this function. The function must have an active route bound (see `functions route-set`); without one the API returns a 422 immediately so no doomed test send is queued. With --wait, blocks until the function has processed the inbound; with --show-sends, also prints any outbound sends the function emitted in response.";
18323
19151
  static summary = "Trigger a test invocation; with --wait, watch it land";
18324
19152
  static examples = [
18325
19153
  "<%= config.bin %> functions test --id <fn-id>",
@@ -18332,14 +19160,9 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
18332
19160
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
18333
19161
  env: "PRIMITIVE_API_KEY"
18334
19162
  }),
18335
- "api-base-url-1": Flags.string({
18336
- description: API_BASE_URL_1_FLAG_DESCRIPTION,
18337
- env: "PRIMITIVE_API_BASE_URL_1",
18338
- hidden: true
18339
- }),
18340
- "api-base-url-2": Flags.string({
18341
- description: API_BASE_URL_2_FLAG_DESCRIPTION,
18342
- env: "PRIMITIVE_API_BASE_URL_2",
19163
+ "api-base-url": Flags.string({
19164
+ description: API_BASE_URL_FLAG_DESCRIPTION,
19165
+ env: "PRIMITIVE_API_BASE_URL",
18343
19166
  hidden: true
18344
19167
  }),
18345
19168
  id: Flags.string({
@@ -18367,8 +19190,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
18367
19190
  const shouldShowSends = flags["show-sends"];
18368
19191
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
18369
19192
  apiKey: flags["api-key"],
18370
- apiBaseUrl1: flags["api-base-url-1"],
18371
- apiBaseUrl2: flags["api-base-url-2"],
19193
+ apiBaseUrl: flags["api-base-url"],
18372
19194
  configDir: this.config.configDir
18373
19195
  });
18374
19196
  await runWithTiming(flags.time, async () => {
@@ -18431,13 +19253,35 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
18431
19253
  return;
18432
19254
  }
18433
19255
  const fetched = result.data.data;
19256
+ if (fetched.inbound_email && Array.isArray(fetched.deliveries) && fetched.deliveries.length === 0 && Date.now() - startedAt > 15e3) {
19257
+ writeFunctionTestProgress(`Inbound email arrived but no route matched. Bind one with: primitive functions route-set --id ${flags.id} --domain <domain-id> (or --fallback), then retry.`);
19258
+ process.exitCode = 1;
19259
+ return;
19260
+ }
18434
19261
  if (TERMINAL_TEST_TRACE_STATES.has(fetched.state)) {
18435
19262
  trace = fetched;
18436
19263
  break;
18437
19264
  }
18438
19265
  await sleep$1(pollIntervalMs);
18439
19266
  }
18440
- if (!trace) this.error(`Timed out after ${flags.timeout}s waiting for function test run ${invocation.test_run_id} to complete. Browse ${invocation.watch_url} for the live view, or inspect ${invocation.trace_url}.`, { exit: 2 });
19267
+ if (!trace) {
19268
+ const finalTrace = ((await getFunctionTestRunTrace({
19269
+ client: apiClient.client,
19270
+ path: {
19271
+ id: flags.id,
19272
+ run_id: invocation.test_run_id
19273
+ },
19274
+ responseStyle: "fields"
19275
+ }).catch(() => null))?.data)?.data;
19276
+ const inboundLanded = Boolean(finalTrace?.inbound_email);
19277
+ const deliveryCount = finalTrace?.deliveries?.length ?? 0;
19278
+ const logCount = finalTrace?.logs?.length ?? 0;
19279
+ const replyCount = finalTrace?.replies?.length ?? 0;
19280
+ const webhookStatus = finalTrace?.inbound_email?.webhook_status ?? "n/a";
19281
+ writeFunctionTestProgress(`Timed out after ${flags.timeout}s. Trace summary: inbound_landed=${inboundLanded} deliveries=${deliveryCount} logs=${logCount} replies=${replyCount} webhook_status=${webhookStatus}. Browse ${invocation.watch_url} for the live view, or inspect ${invocation.trace_url}.`);
19282
+ process.exitCode = 2;
19283
+ return;
19284
+ }
18441
19285
  const outcome = buildFunctionTestOutcome({
18442
19286
  elapsedSeconds: Math.round((Date.now() - startedAt) / 1e3),
18443
19287
  functionId: flags.id,
@@ -18479,7 +19323,7 @@ function formatInboxDate(value) {
18479
19323
  const pad = (n) => String(n).padStart(2, "0");
18480
19324
  return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())} UTC`;
18481
19325
  }
18482
- function truncate(value, width) {
19326
+ function truncate$1(value, width) {
18483
19327
  if (value.length <= width) return value.padEnd(width);
18484
19328
  return `${value.slice(0, width - 3)}...`;
18485
19329
  }
@@ -18529,7 +19373,7 @@ function formatDomainHeader() {
18529
19373
  }
18530
19374
  function formatDomainRow(domain) {
18531
19375
  return [
18532
- truncate(domain.domain, DOMAIN_DISPLAY_WIDTH),
19376
+ truncate$1(domain.domain, DOMAIN_DISPLAY_WIDTH),
18533
19377
  statusText(domain.status).padEnd(STATUS_DISPLAY_WIDTH),
18534
19378
  yesNo(domain.receiving_ready).padEnd(BOOL_DISPLAY_WIDTH),
18535
19379
  yesNo(domain.processing_ready).padEnd(BOOL_DISPLAY_WIDTH),
@@ -18575,14 +19419,9 @@ var InboxStatusCommand = class InboxStatusCommand extends Command {
18575
19419
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
18576
19420
  env: "PRIMITIVE_API_KEY"
18577
19421
  }),
18578
- "api-base-url-1": Flags.string({
18579
- description: API_BASE_URL_1_FLAG_DESCRIPTION,
18580
- env: "PRIMITIVE_API_BASE_URL_1",
18581
- hidden: true
18582
- }),
18583
- "api-base-url-2": Flags.string({
18584
- description: API_BASE_URL_2_FLAG_DESCRIPTION,
18585
- env: "PRIMITIVE_API_BASE_URL_2",
19422
+ "api-base-url": Flags.string({
19423
+ description: API_BASE_URL_FLAG_DESCRIPTION,
19424
+ env: "PRIMITIVE_API_BASE_URL",
18586
19425
  hidden: true
18587
19426
  }),
18588
19427
  domain: Flags.string({ description: "Focus domain readiness and recent email fields on one domain returned by the inbox status API." }),
@@ -18594,8 +19433,7 @@ var InboxStatusCommand = class InboxStatusCommand extends Command {
18594
19433
  await runWithTiming(flags.time, async () => {
18595
19434
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
18596
19435
  apiKey: flags["api-key"],
18597
- apiBaseUrl1: flags["api-base-url-1"],
18598
- apiBaseUrl2: flags["api-base-url-2"],
19436
+ apiBaseUrl: flags["api-base-url"],
18599
19437
  configDir: this.config.configDir
18600
19438
  });
18601
19439
  const result = await getInboxStatus({
@@ -18750,14 +19588,9 @@ var InboxSetupCommand = class InboxSetupCommand extends Command {
18750
19588
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
18751
19589
  env: "PRIMITIVE_API_KEY"
18752
19590
  }),
18753
- "api-base-url-1": Flags.string({
18754
- description: API_BASE_URL_1_FLAG_DESCRIPTION,
18755
- env: "PRIMITIVE_API_BASE_URL_1",
18756
- hidden: true
18757
- }),
18758
- "api-base-url-2": Flags.string({
18759
- description: API_BASE_URL_2_FLAG_DESCRIPTION,
18760
- env: "PRIMITIVE_API_BASE_URL_2",
19591
+ "api-base-url": Flags.string({
19592
+ description: API_BASE_URL_FLAG_DESCRIPTION,
19593
+ env: "PRIMITIVE_API_BASE_URL",
18761
19594
  hidden: true
18762
19595
  }),
18763
19596
  json: Flags.boolean({ description: "Print structured readiness, receive address, commands, proof metadata, and raw status as JSON." }),
@@ -18768,8 +19601,7 @@ var InboxSetupCommand = class InboxSetupCommand extends Command {
18768
19601
  await runWithTiming(flags.time, async () => {
18769
19602
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
18770
19603
  apiKey: flags["api-key"],
18771
- apiBaseUrl1: flags["api-base-url-1"],
18772
- apiBaseUrl2: flags["api-base-url-2"],
19604
+ apiBaseUrl: flags["api-base-url"],
18773
19605
  configDir: this.config.configDir
18774
19606
  });
18775
19607
  const result = await getInboxStatus({
@@ -18836,14 +19668,14 @@ function retryAfterSeconds$1(result) {
18836
19668
  }
18837
19669
  async function checkExistingLogin(params) {
18838
19670
  const requestConfig = resolveCliApiRequestConfig({
18839
- apiBaseUrl1: params.apiBaseUrl1,
19671
+ apiBaseUrl: params.apiBaseUrl,
18840
19672
  configDir: params.configDir
18841
19673
  });
18842
- const probeApiBaseUrl1 = requestConfig.apiBaseUrl1 ?? params.credentials.api_base_url_1;
19674
+ const probeApiBaseUrl = requestConfig.apiBaseUrl ?? params.credentials.api_base_url;
18843
19675
  let credentials = params.credentials;
18844
19676
  try {
18845
19677
  credentials = await refreshStoredCliCredentials({
18846
- apiBaseUrl1: probeApiBaseUrl1,
19678
+ apiBaseUrl: probeApiBaseUrl,
18847
19679
  configDir: params.configDir,
18848
19680
  credentials,
18849
19681
  credentialsLockHeld: params.credentialsLockHeld,
@@ -18859,8 +19691,7 @@ async function checkExistingLogin(params) {
18859
19691
  }
18860
19692
  const apiClient = new PrimitiveApiClient({
18861
19693
  apiKey: credentials.access_token,
18862
- apiBaseUrl1: probeApiBaseUrl1,
18863
- apiBaseUrl2: requestConfig.resolvedApiBaseUrl2,
19694
+ apiBaseUrl: probeApiBaseUrl,
18864
19695
  headers: requestConfig.headers
18865
19696
  });
18866
19697
  const result = await (params.checkAccount ?? ((client) => getAccount({
@@ -18870,7 +19701,7 @@ async function checkExistingLogin(params) {
18870
19701
  if (!result.error) return { status: "valid" };
18871
19702
  const payload = extractErrorPayload(result.error);
18872
19703
  const code = extractErrorCode(payload);
18873
- const baseUrlDiffersFromSaved = requestConfig.baseUrlOverridden && requestConfig.apiBaseUrl1 !== params.credentials.api_base_url_1;
19704
+ const baseUrlDiffersFromSaved = requestConfig.baseUrlOverridden && requestConfig.apiBaseUrl !== params.credentials.api_base_url;
18874
19705
  if (code === API_ERROR_CODES.unauthorized && !baseUrlDiffersFromSaved) {
18875
19706
  deleteCliCredentials(params.configDir);
18876
19707
  process.stderr.write("Removed saved Primitive CLI OAuth credentials because the existing session was rejected during login. Continuing with a fresh login.\n");
@@ -18891,9 +19722,9 @@ var LoginCommand$1 = class extends Command {
18891
19722
  "<%= config.bin %> login --force"
18892
19723
  ];
18893
19724
  static flags = {
18894
- "api-base-url-1": Flags.string({
19725
+ "api-base-url": Flags.string({
18895
19726
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
18896
- env: "PRIMITIVE_API_BASE_URL_1",
19727
+ env: "PRIMITIVE_API_BASE_URL",
18897
19728
  hidden: true
18898
19729
  }),
18899
19730
  "device-name": Flags.string({ description: "Device name shown in the browser approval screen" }),
@@ -18926,10 +19757,10 @@ var LoginCommand$1 = class extends Command {
18926
19757
  }
18927
19758
  async runWithCredentialLock(flags, retryCommand) {
18928
19759
  const { apiClient, requestConfig } = createCliApiClient({
18929
- apiBaseUrl1: flags["api-base-url-1"],
19760
+ apiBaseUrl: flags["api-base-url"],
18930
19761
  configDir: this.config.configDir
18931
19762
  });
18932
- const apiBaseUrl1 = requestConfig.resolvedApiBaseUrl1;
19763
+ const apiBaseUrl = requestConfig.resolvedApiBaseUrl;
18933
19764
  let existing;
18934
19765
  try {
18935
19766
  existing = loadCliCredentials(this.config.configDir);
@@ -18942,7 +19773,7 @@ var LoginCommand$1 = class extends Command {
18942
19773
  if (existing && flags.force) process.stderr.write("Replacing saved Primitive CLI credentials after browser approval because --force was set.\n");
18943
19774
  else if (existing) {
18944
19775
  const existingStatus = await checkExistingLogin({
18945
- apiBaseUrl1: flags["api-base-url-1"],
19776
+ apiBaseUrl: flags["api-base-url"],
18946
19777
  configDir: this.config.configDir,
18947
19778
  credentials: existing,
18948
19779
  credentialsLockHeld: true
@@ -18988,7 +19819,7 @@ var LoginCommand$1 = class extends Command {
18988
19819
  deleteChatState(this.config.configDir);
18989
19820
  saveCliCredentials(this.config.configDir, {
18990
19821
  access_token: login.access_token,
18991
- api_base_url_1: apiBaseUrl1,
19822
+ api_base_url: apiBaseUrl,
18992
19823
  auth_method: "oauth",
18993
19824
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
18994
19825
  expires_at: cliAccessTokenExpiresAt(login.expires_in),
@@ -19052,9 +19883,11 @@ function normalizeEmail(email) {
19052
19883
  }
19053
19884
  function pendingSignupFromJson(value) {
19054
19885
  if (!isRecord(value)) return null;
19055
- if (typeof value.signup_token !== "string" || typeof value.email !== "string" || typeof value.expires_in !== "number" || typeof value.resend_after !== "number" || typeof value.verification_code_length !== "number" || typeof value.api_base_url_1 !== "string" || typeof value.created_at !== "string" || typeof value.expires_at !== "string") return null;
19886
+ if (typeof value.signup_token !== "string" || typeof value.email !== "string" || typeof value.expires_in !== "number" || typeof value.resend_after !== "number" || typeof value.verification_code_length !== "number" || typeof value.created_at !== "string" || typeof value.expires_at !== "string") return null;
19887
+ const apiBaseUrl = value.api_base_url ?? value.api_base_url_1;
19888
+ if (typeof apiBaseUrl !== "string") return null;
19056
19889
  return {
19057
- api_base_url_1: value.api_base_url_1,
19890
+ api_base_url: apiBaseUrl,
19058
19891
  created_at: value.created_at,
19059
19892
  email: value.email,
19060
19893
  expires_at: value.expires_at,
@@ -19070,20 +19903,20 @@ function pendingSignupPath(configDir) {
19070
19903
  function deletePendingAgentSignup(configDir) {
19071
19904
  rmSync(pendingSignupPath(configDir), { force: true });
19072
19905
  }
19073
- function pendingSignupFromStart(start, apiBaseUrl1) {
19906
+ function pendingSignupFromStart(start, apiBaseUrl) {
19074
19907
  return {
19075
19908
  ...start,
19076
- api_base_url_1: apiBaseUrl1,
19909
+ api_base_url: apiBaseUrl,
19077
19910
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
19078
19911
  expires_at: new Date(Date.now() + start.expires_in * 1e3).toISOString()
19079
19912
  };
19080
19913
  }
19081
- function savePendingAgentSignup(configDir, start, apiBaseUrl1) {
19914
+ function savePendingAgentSignup(configDir, start, apiBaseUrl) {
19082
19915
  mkdirSync(configDir, {
19083
19916
  mode: 448,
19084
19917
  recursive: true
19085
19918
  });
19086
- const pending = pendingSignupFromStart(start, apiBaseUrl1);
19919
+ const pending = pendingSignupFromStart(start, apiBaseUrl);
19087
19920
  const path = pendingSignupPath(configDir);
19088
19921
  const tempPath = join(configDir, `${PENDING_SIGNUP_FILE}.${process$1.pid}.${randomUUID()}.tmp`);
19089
19922
  try {
@@ -19097,7 +19930,7 @@ function savePendingAgentSignup(configDir, start, apiBaseUrl1) {
19097
19930
  throw error;
19098
19931
  }
19099
19932
  }
19100
- function loadPendingAgentSignup(configDir, apiBaseUrl1) {
19933
+ function loadPendingAgentSignup(configDir, apiBaseUrl) {
19101
19934
  const path = pendingSignupPath(configDir);
19102
19935
  let contents;
19103
19936
  try {
@@ -19116,7 +19949,7 @@ function loadPendingAgentSignup(configDir, apiBaseUrl1) {
19116
19949
  deletePendingAgentSignup(configDir);
19117
19950
  return null;
19118
19951
  }
19119
- if (pending.api_base_url_1 !== apiBaseUrl1) return null;
19952
+ if (pending.api_base_url !== apiBaseUrl) return null;
19120
19953
  if (new Date(pending.expires_at).getTime() <= Date.now()) {
19121
19954
  deletePendingAgentSignup(configDir);
19122
19955
  return null;
@@ -19126,7 +19959,7 @@ function loadPendingAgentSignup(configDir, apiBaseUrl1) {
19126
19959
  expires_in: Math.max(0, Math.ceil((new Date(pending.expires_at).getTime() - Date.now()) / 1e3))
19127
19960
  };
19128
19961
  }
19129
- function readPendingAgentSignupState(configDir, apiBaseUrl1) {
19962
+ function readPendingAgentSignupState(configDir, apiBaseUrl) {
19130
19963
  const path = pendingSignupPath(configDir);
19131
19964
  let contents;
19132
19965
  try {
@@ -19145,7 +19978,7 @@ function readPendingAgentSignupState(configDir, apiBaseUrl1) {
19145
19978
  deletePendingAgentSignup(configDir);
19146
19979
  return null;
19147
19980
  }
19148
- if (pending.api_base_url_1 !== apiBaseUrl1) return null;
19981
+ if (pending.api_base_url !== apiBaseUrl) return null;
19149
19982
  return pending;
19150
19983
  }
19151
19984
  function pendingSignupStartCommand(email) {
@@ -19153,7 +19986,7 @@ function pendingSignupStartCommand(email) {
19153
19986
  }
19154
19987
  function buildSignupStatus(params) {
19155
19988
  const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
19156
- const pending = readPendingAgentSignupState(params.configDir, params.apiBaseUrl1);
19989
+ const pending = readPendingAgentSignupState(params.configDir, params.apiBaseUrl);
19157
19990
  if (!pending) return {
19158
19991
  code_length: null,
19159
19992
  confirm_command: null,
@@ -19200,11 +20033,11 @@ function writeSignupStatus(status) {
19200
20033
  }
19201
20034
  function runSignupStatus(params) {
19202
20035
  const { requestConfig } = createCliApiClient({
19203
- apiBaseUrl1: params.flags["api-base-url-1"],
20036
+ apiBaseUrl: params.flags["api-base-url"],
19204
20037
  configDir: params.configDir
19205
20038
  });
19206
20039
  const status = buildSignupStatus({
19207
- apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
20040
+ apiBaseUrl: requestConfig.resolvedApiBaseUrl,
19208
20041
  configDir: params.configDir,
19209
20042
  copy: params.copy,
19210
20043
  email: params.email
@@ -19217,7 +20050,7 @@ function runSignupStatus(params) {
19217
20050
  }
19218
20051
  function requirePendingSignupForEmail(params) {
19219
20052
  const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
19220
- const pending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl1);
20053
+ const pending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl);
19221
20054
  if (!pending) throw cliError$2(`No pending ${copy.actionNoun} for ${params.email}. Run \`primitive signup status ${params.email}\` to inspect pending state, or \`primitive ${copy.startCommand(params.email)}\` first.`);
19222
20055
  if (normalizeEmail(pending.email) !== normalizeEmail(params.email)) throw cliError$2(`Pending ${copy.actionNoun} is for ${pending.email}, not ${params.email}. Run \`primitive signup status\` to inspect it, or \`primitive ${copy.startCommand(params.email)} --force\` to replace it.`);
19223
20056
  return pending;
@@ -19280,7 +20113,7 @@ async function checkExistingCredentials(params) {
19280
20113
  }
19281
20114
  if (!existing) return;
19282
20115
  const existingStatus = await checkExistingLoginFn({
19283
- apiBaseUrl1: params.apiBaseUrl1,
20116
+ apiBaseUrl: params.apiBaseUrl,
19284
20117
  configDir: params.configDir,
19285
20118
  credentials: existing,
19286
20119
  credentialsLockHeld: true
@@ -19299,7 +20132,7 @@ function saveSignupCredentials(params) {
19299
20132
  deleteChatState(params.configDir);
19300
20133
  saveCliCredentials(params.configDir, {
19301
20134
  access_token: params.signup.access_token,
19302
- api_base_url_1: params.apiBaseUrl1,
20135
+ api_base_url: params.apiBaseUrl,
19303
20136
  auth_method: "oauth",
19304
20137
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
19305
20138
  expires_at: cliAccessTokenExpiresAt(params.signup.expires_in),
@@ -19318,7 +20151,7 @@ function writeStartInstructions(start, copy = DEFAULT_SIGNUP_COMMAND_COPY) {
19318
20151
  }
19319
20152
  async function startSignup(params) {
19320
20153
  const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
19321
- const existingPending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl1);
20154
+ const existingPending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl);
19322
20155
  if (existingPending && !params.flags.force) {
19323
20156
  if (normalizeEmail(existingPending.email) === normalizeEmail(params.email)) {
19324
20157
  process$1.stderr.write(`Continuing pending Primitive ${copy.actionNoun} for ${existingPending.email}.\n`);
@@ -19353,7 +20186,7 @@ async function startSignup(params) {
19353
20186
  const startResult = unwrapData$1(started.data);
19354
20187
  if (!startResult) throw cliError$2("Primitive API returned an empty agent signup response.");
19355
20188
  return {
19356
- pending: savePendingAgentSignup(params.configDir, startResult, params.apiBaseUrl1),
20189
+ pending: savePendingAgentSignup(params.configDir, startResult, params.apiBaseUrl),
19357
20190
  started: true
19358
20191
  };
19359
20192
  }
@@ -19373,7 +20206,7 @@ async function resendVerificationCode(params) {
19373
20206
  verification_code_length: resend.verification_code_length
19374
20207
  } : params.start;
19375
20208
  return {
19376
- pending: savePendingAgentSignup(params.configDir, next, params.apiBaseUrl1),
20209
+ pending: savePendingAgentSignup(params.configDir, next, params.apiBaseUrl),
19377
20210
  resent: true
19378
20211
  };
19379
20212
  }
@@ -19397,18 +20230,18 @@ async function runSignupStartWithCredentialLock(params) {
19397
20230
  const promptRequiredFn = deps.promptRequired ?? promptRequired;
19398
20231
  const email = params.email ?? await promptRequiredFn("Email: ");
19399
20232
  await checkExistingCredentials({
19400
- apiBaseUrl1: flags["api-base-url-1"],
20233
+ apiBaseUrl: flags["api-base-url"],
19401
20234
  configDir,
19402
20235
  copy: params.copy,
19403
20236
  deps,
19404
20237
  flags
19405
20238
  });
19406
20239
  const { apiClient, requestConfig } = createCliApiClient({
19407
- apiBaseUrl1: flags["api-base-url-1"],
20240
+ apiBaseUrl: flags["api-base-url"],
19408
20241
  configDir
19409
20242
  });
19410
20243
  const start = await startSignup({
19411
- apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
20244
+ apiBaseUrl: requestConfig.resolvedApiBaseUrl,
19412
20245
  apiClient,
19413
20246
  configDir,
19414
20247
  copy: params.copy,
@@ -19422,19 +20255,19 @@ async function runSignupConfirmWithCredentialLock(params) {
19422
20255
  const { configDir, flags } = params;
19423
20256
  const deps = params.deps ?? {};
19424
20257
  if (!params.skipExistingCredentialCheck) await checkExistingCredentials({
19425
- apiBaseUrl1: flags["api-base-url-1"],
20258
+ apiBaseUrl: flags["api-base-url"],
19426
20259
  configDir,
19427
20260
  copy: params.copy,
19428
20261
  deps,
19429
20262
  flags
19430
20263
  });
19431
20264
  const { apiClient, requestConfig } = createCliApiClient({
19432
- apiBaseUrl1: flags["api-base-url-1"],
20265
+ apiBaseUrl: flags["api-base-url"],
19433
20266
  configDir
19434
20267
  });
19435
- const apiBaseUrl1 = requestConfig.resolvedApiBaseUrl1;
20268
+ const apiBaseUrl = requestConfig.resolvedApiBaseUrl;
19436
20269
  const pending = requirePendingSignupForEmail({
19437
- apiBaseUrl1,
20270
+ apiBaseUrl,
19438
20271
  copy: params.copy,
19439
20272
  configDir,
19440
20273
  email: params.email
@@ -19452,7 +20285,7 @@ async function runSignupConfirmWithCredentialLock(params) {
19452
20285
  const signup = unwrapData$1(verified.data);
19453
20286
  if (!signup) throw cliError$2("Primitive API returned an empty agent signup verification response.");
19454
20287
  saveSignupCredentials({
19455
- apiBaseUrl1,
20288
+ apiBaseUrl,
19456
20289
  configDir,
19457
20290
  signup
19458
20291
  });
@@ -19473,18 +20306,18 @@ async function runSignupResendWithCredentialLock(params) {
19473
20306
  const deps = params.deps ?? {};
19474
20307
  const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
19475
20308
  const { apiClient, requestConfig } = createCliApiClient({
19476
- apiBaseUrl1: params.flags["api-base-url-1"],
20309
+ apiBaseUrl: params.flags["api-base-url"],
19477
20310
  configDir: params.configDir
19478
20311
  });
19479
20312
  const pending = params.email ? requirePendingSignupForEmail({
19480
- apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
20313
+ apiBaseUrl: requestConfig.resolvedApiBaseUrl,
19481
20314
  copy,
19482
20315
  configDir: params.configDir,
19483
20316
  email: params.email
19484
- }) : loadPendingAgentSignup(params.configDir, requestConfig.resolvedApiBaseUrl1);
20317
+ }) : loadPendingAgentSignup(params.configDir, requestConfig.resolvedApiBaseUrl);
19485
20318
  if (!pending) throw cliError$2(`No pending ${copy.actionNoun} found. Run \`primitive signup status\` to inspect pending state, or start one with \`${pendingSignupStartCommand()}\`.`);
19486
20319
  const resend = await resendVerificationCode({
19487
- apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
20320
+ apiBaseUrl: requestConfig.resolvedApiBaseUrl,
19488
20321
  apiClient,
19489
20322
  configDir: params.configDir,
19490
20323
  deps,
@@ -19497,20 +20330,20 @@ async function runSignupInteractiveWithCredentialLock(params) {
19497
20330
  const deps = params.deps ?? {};
19498
20331
  const promptRequiredFn = deps.promptRequired ?? promptRequired;
19499
20332
  await checkExistingCredentials({
19500
- apiBaseUrl1: flags["api-base-url-1"],
20333
+ apiBaseUrl: flags["api-base-url"],
19501
20334
  configDir,
19502
20335
  deps,
19503
20336
  flags
19504
20337
  });
19505
20338
  const { apiClient, requestConfig } = createCliApiClient({
19506
- apiBaseUrl1: flags["api-base-url-1"],
20339
+ apiBaseUrl: flags["api-base-url"],
19507
20340
  configDir
19508
20341
  });
19509
- const apiBaseUrl1 = requestConfig.resolvedApiBaseUrl1;
19510
- let start = flags.force ? null : loadPendingAgentSignup(configDir, apiBaseUrl1);
20342
+ const apiBaseUrl = requestConfig.resolvedApiBaseUrl;
20343
+ let start = flags.force ? null : loadPendingAgentSignup(configDir, apiBaseUrl);
19511
20344
  if (start) process$1.stderr.write(`Continuing pending Primitive signup for ${start.email}.\n`);
19512
20345
  else start = (await startSignup({
19513
- apiBaseUrl1,
20346
+ apiBaseUrl,
19514
20347
  apiClient,
19515
20348
  configDir,
19516
20349
  deps,
@@ -19524,7 +20357,7 @@ async function runSignupInteractiveWithCredentialLock(params) {
19524
20357
  const verificationCode = await promptRequiredFn(`Verification code (${start.verification_code_length} digits): `);
19525
20358
  if (verificationCode.toLowerCase() === "resend") {
19526
20359
  const resend = await resendVerificationCode({
19527
- apiBaseUrl1,
20360
+ apiBaseUrl,
19528
20361
  apiClient,
19529
20362
  configDir,
19530
20363
  deps,
@@ -19541,7 +20374,7 @@ async function runSignupInteractiveWithCredentialLock(params) {
19541
20374
  deps,
19542
20375
  email: start.email,
19543
20376
  flags: {
19544
- "api-base-url-1": flags["api-base-url-1"],
20377
+ "api-base-url": flags["api-base-url"],
19545
20378
  force: true
19546
20379
  },
19547
20380
  skipExistingCredentialCheck: true
@@ -19559,9 +20392,9 @@ async function runSignupInteractiveWithCredentialLock(params) {
19559
20392
  function commonStartFlags() {
19560
20393
  return {
19561
20394
  "accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy" }),
19562
- "api-base-url-1": Flags.string({
20395
+ "api-base-url": Flags.string({
19563
20396
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
19564
- env: "PRIMITIVE_API_BASE_URL_1",
20397
+ env: "PRIMITIVE_API_BASE_URL",
19565
20398
  hidden: true
19566
20399
  }),
19567
20400
  "device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
@@ -19622,9 +20455,9 @@ var SignupConfirmCommand = class SignupConfirmCommand extends Command {
19622
20455
  static summary = "Confirm account signup";
19623
20456
  static examples = ["<%= config.bin %> signup confirm user@example.com 123456", "<%= config.bin %> signup confirm user@example.com 123456 --org-id 00000000-0000-4000-8000-000000000000"];
19624
20457
  static flags = {
19625
- "api-base-url-1": Flags.string({
20458
+ "api-base-url": Flags.string({
19626
20459
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
19627
- env: "PRIMITIVE_API_BASE_URL_1",
20460
+ env: "PRIMITIVE_API_BASE_URL",
19628
20461
  hidden: true
19629
20462
  }),
19630
20463
  force: Flags.boolean({
@@ -19661,9 +20494,9 @@ var SignupResendCommand = class SignupResendCommand extends Command {
19661
20494
  static description = "Resend the verification code for a pending signup.";
19662
20495
  static summary = "Resend signup verification code";
19663
20496
  static examples = ["<%= config.bin %> signup resend", "<%= config.bin %> signup resend user@example.com"];
19664
- static flags = { "api-base-url-1": Flags.string({
20497
+ static flags = { "api-base-url": Flags.string({
19665
20498
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
19666
- env: "PRIMITIVE_API_BASE_URL_1",
20499
+ env: "PRIMITIVE_API_BASE_URL",
19667
20500
  hidden: true
19668
20501
  }) };
19669
20502
  async run() {
@@ -19698,9 +20531,9 @@ var SignupStatusCommand = class SignupStatusCommand extends Command {
19698
20531
  "<%= config.bin %> signup status --json"
19699
20532
  ];
19700
20533
  static flags = {
19701
- "api-base-url-1": Flags.string({
20534
+ "api-base-url": Flags.string({
19702
20535
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
19703
- env: "PRIMITIVE_API_BASE_URL_1",
20536
+ env: "PRIMITIVE_API_BASE_URL",
19704
20537
  hidden: true
19705
20538
  }),
19706
20539
  json: Flags.boolean({ description: "Print pending signup status as JSON" })
@@ -19768,7 +20601,7 @@ async function runLogoutWithCredentialLock(params) {
19768
20601
  let authenticated;
19769
20602
  try {
19770
20603
  authenticated = await deps.createAuthenticatedCliApiClient({
19771
- apiBaseUrl1: params.flags["api-base-url-1"],
20604
+ apiBaseUrl: params.flags["api-base-url"],
19772
20605
  configDir: params.configDir,
19773
20606
  credentialsLockHeld: true
19774
20607
  });
@@ -19832,9 +20665,9 @@ var LogoutCommand = class LogoutCommand extends Command {
19832
20665
  static summary = "Log out and revoke the saved CLI OAuth grant";
19833
20666
  static examples = ["<%= config.bin %> logout", "<%= config.bin %> logout --force"];
19834
20667
  static flags = {
19835
- "api-base-url-1": Flags.string({
20668
+ "api-base-url": Flags.string({
19836
20669
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
19837
- env: "PRIMITIVE_API_BASE_URL_1",
20670
+ env: "PRIMITIVE_API_BASE_URL",
19838
20671
  hidden: true
19839
20672
  }),
19840
20673
  force: Flags.boolean({
@@ -19983,14 +20816,9 @@ var ReplyCommand = class ReplyCommand extends Command {
19983
20816
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
19984
20817
  env: "PRIMITIVE_API_KEY"
19985
20818
  }),
19986
- "api-base-url-1": Flags.string({
19987
- description: "Override the primary API base URL. Internal testing only; not documented to customers.",
19988
- env: "PRIMITIVE_API_BASE_URL_1",
19989
- hidden: true
19990
- }),
19991
- "api-base-url-2": Flags.string({
19992
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
19993
- env: "PRIMITIVE_API_BASE_URL_2",
20819
+ "api-base-url": Flags.string({
20820
+ description: "Override the API base URL. Internal testing only; not documented to customers.",
20821
+ env: "PRIMITIVE_API_BASE_URL",
19994
20822
  hidden: true
19995
20823
  }),
19996
20824
  id: Flags.string({
@@ -20026,8 +20854,7 @@ var ReplyCommand = class ReplyCommand extends Command {
20026
20854
  await runWithTiming(flags.time, async () => {
20027
20855
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
20028
20856
  apiKey: flags["api-key"],
20029
- apiBaseUrl1: flags["api-base-url-1"],
20030
- apiBaseUrl2: flags["api-base-url-2"],
20857
+ apiBaseUrl: flags["api-base-url"],
20031
20858
  configDir: this.config.configDir
20032
20859
  });
20033
20860
  const attachments = readAttachmentFiles(flags.attachment);
@@ -20039,7 +20866,7 @@ var ReplyCommand = class ReplyCommand extends Command {
20039
20866
  ...attachments !== void 0 ? { attachments } : {},
20040
20867
  ...flags.wait !== void 0 ? { wait: flags.wait } : {}
20041
20868
  },
20042
- client: apiClient._sendClient,
20869
+ client: apiClient.client,
20043
20870
  path: { id: flags.id },
20044
20871
  responseStyle: "fields"
20045
20872
  });
@@ -20064,6 +20891,134 @@ var ReplyCommand = class ReplyCommand extends Command {
20064
20891
  }
20065
20892
  };
20066
20893
  //#endregion
20894
+ //#region src/oclif/commands/semantic-search.ts
20895
+ const DEFAULT_LIMIT = 10;
20896
+ const MAX_LIMIT = 100;
20897
+ const SCORE_WIDTH = 7;
20898
+ const SOURCE_WIDTH = 4;
20899
+ const SUBJECT_WIDTH = 40;
20900
+ const FROM_WIDTH = 26;
20901
+ const SNIPPET_WIDTH = 60;
20902
+ function truncate(value, width) {
20903
+ if (value.length <= width) return value.padEnd(width);
20904
+ return `${value.slice(0, width - 3)}...`;
20905
+ }
20906
+ function sourceLabel(t) {
20907
+ return t === "inbound_email" ? "in" : "out";
20908
+ }
20909
+ function formatRow(r) {
20910
+ return `${r.score.toFixed(3).padStart(SCORE_WIDTH)} ${sourceLabel(r.source_type).padEnd(SOURCE_WIDTH)} ${truncate((r.subject ?? "").replace(/\s+/g, " "), SUBJECT_WIDTH)} ${truncate(r.from ?? "", FROM_WIDTH)} ${truncate((r.snippets[0]?.text ?? "").replace(/\s+/g, " "), SNIPPET_WIDTH)}`;
20911
+ }
20912
+ function formatHeader() {
20913
+ return `${"SCORE".padStart(SCORE_WIDTH)} ${"SRC".padEnd(SOURCE_WIDTH)} ${"SUBJECT".padEnd(SUBJECT_WIDTH)} ${"FROM".padEnd(FROM_WIDTH)} EXCERPT`;
20914
+ }
20915
+ var SemanticSearchCommand = class SemanticSearchCommand extends Command {
20916
+ static description = `Search received and sent mail by meaning or keywords.
20917
+
20918
+ Returns ranked rows. Each row carries a relevance score, the fields it
20919
+ matched, and a match-centered excerpt. Defaults to hybrid mode (blends
20920
+ semantic and keyword signals); use \`--mode keyword\` for plain
20921
+ full-text matching and \`--mode semantic\` for embedding-only.
20922
+
20923
+ Requires the Pro plan with the semantic_search_enabled entitlement.`;
20924
+ static summary = "Semantic / hybrid / keyword search across received and sent mail";
20925
+ static examples = [
20926
+ "<%= config.bin %> semantic-search \"invoice from acme\"",
20927
+ "<%= config.bin %> semantic-search \"shipping update\" --mode keyword",
20928
+ "<%= config.bin %> semantic-search \"kickoff\" --corpus inbound --limit 25",
20929
+ "<%= config.bin %> semantic-search renewal --json | jq '.data[].id'"
20930
+ ];
20931
+ static args = { query: Args.string({
20932
+ description: "The search query.",
20933
+ required: true
20934
+ }) };
20935
+ static flags = {
20936
+ "api-key": Flags.string({
20937
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
20938
+ env: "PRIMITIVE_API_KEY"
20939
+ }),
20940
+ "api-base-url": Flags.string({
20941
+ description: API_BASE_URL_FLAG_DESCRIPTION,
20942
+ env: "PRIMITIVE_API_BASE_URL",
20943
+ hidden: true
20944
+ }),
20945
+ mode: Flags.string({
20946
+ description: "Ranking strategy.",
20947
+ options: [
20948
+ "hybrid",
20949
+ "semantic",
20950
+ "keyword"
20951
+ ],
20952
+ default: "hybrid"
20953
+ }),
20954
+ corpus: Flags.string({
20955
+ description: "Restrict to inbound or outbound. Pass twice to include both (the default).",
20956
+ options: ["inbound", "outbound"],
20957
+ multiple: true
20958
+ }),
20959
+ "date-from": Flags.string({ description: "Only include mail at or after this ISO-8601 timestamp." }),
20960
+ "date-to": Flags.string({ description: "Only include mail at or before this ISO-8601 timestamp." }),
20961
+ limit: Flags.integer({
20962
+ description: `Maximum results to return (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}).`,
20963
+ default: DEFAULT_LIMIT,
20964
+ min: 1,
20965
+ max: MAX_LIMIT
20966
+ }),
20967
+ cursor: Flags.string({ description: "Opaque pagination cursor from a prior response's meta.cursor." }),
20968
+ json: Flags.boolean({ description: "Print the raw response envelope as JSON on STDOUT instead of the text table." }),
20969
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
20970
+ };
20971
+ async run() {
20972
+ const { args, flags } = await this.parse(SemanticSearchCommand);
20973
+ await runWithTiming(flags.time, async () => {
20974
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
20975
+ apiKey: flags["api-key"],
20976
+ apiBaseUrl: flags["api-base-url"],
20977
+ configDir: this.config.configDir
20978
+ });
20979
+ const result = await semanticSearch({
20980
+ client: apiClient.client,
20981
+ body: {
20982
+ query: args.query,
20983
+ mode: flags.mode,
20984
+ ...flags.corpus ? { corpus: flags.corpus } : {},
20985
+ ...flags["date-from"] ? { date_from: flags["date-from"] } : {},
20986
+ ...flags["date-to"] ? { date_to: flags["date-to"] } : {},
20987
+ limit: flags.limit,
20988
+ ...flags.cursor ? { cursor: flags.cursor } : {}
20989
+ },
20990
+ responseStyle: "fields"
20991
+ });
20992
+ if (result.error) {
20993
+ const errorPayload = extractErrorPayload(result.error);
20994
+ writeErrorWithHints(errorPayload);
20995
+ surfaceUnauthorizedHint({
20996
+ auth,
20997
+ baseUrlOverridden,
20998
+ configDir: this.config.configDir,
20999
+ payload: errorPayload
21000
+ });
21001
+ process.exitCode = 1;
21002
+ return;
21003
+ }
21004
+ const envelope = result.data;
21005
+ if (flags.json) {
21006
+ this.log(JSON.stringify(envelope ?? null, null, 2));
21007
+ return;
21008
+ }
21009
+ const rows = envelope?.data ?? [];
21010
+ if (rows.length === 0) {
21011
+ process.stderr.write("No matching mail.\n");
21012
+ return;
21013
+ }
21014
+ process.stderr.write(`${formatHeader()}\n`);
21015
+ for (const row of rows) this.log(formatRow(row));
21016
+ const nextCursor = envelope?.meta?.cursor ?? null;
21017
+ if (nextCursor) process.stderr.write(`\nNext page: pass --cursor ${nextCursor}\n`);
21018
+ });
21019
+ }
21020
+ };
21021
+ //#endregion
20067
21022
  //#region src/oclif/commands/send.ts
20068
21023
  var SendCommand = class SendCommand extends Command {
20069
21024
  static description = `Send an outbound email. Agent-grade shortcut for \`sending send\` with sensible defaults.
@@ -20089,14 +21044,9 @@ var SendCommand = class SendCommand extends Command {
20089
21044
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
20090
21045
  env: "PRIMITIVE_API_KEY"
20091
21046
  }),
20092
- "api-base-url-1": Flags.string({
20093
- description: "Override the primary API base URL. Internal testing only; not documented to customers.",
20094
- env: "PRIMITIVE_API_BASE_URL_1",
20095
- hidden: true
20096
- }),
20097
- "api-base-url-2": Flags.string({
20098
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
20099
- env: "PRIMITIVE_API_BASE_URL_2",
21047
+ "api-base-url": Flags.string({
21048
+ description: "Override the API base URL. Internal testing only; not documented to customers.",
21049
+ env: "PRIMITIVE_API_BASE_URL",
20100
21050
  hidden: true
20101
21051
  }),
20102
21052
  to: Flags.string({
@@ -20135,8 +21085,7 @@ var SendCommand = class SendCommand extends Command {
20135
21085
  await runWithTiming(flags.time, async () => {
20136
21086
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
20137
21087
  apiKey: flags["api-key"],
20138
- apiBaseUrl1: flags["api-base-url-1"],
20139
- apiBaseUrl2: flags["api-base-url-2"],
21088
+ apiBaseUrl: flags["api-base-url"],
20140
21089
  configDir: this.config.configDir
20141
21090
  });
20142
21091
  const authFailureContext = {
@@ -20158,7 +21107,7 @@ var SendCommand = class SendCommand extends Command {
20158
21107
  ...flags.wait !== void 0 ? { wait: flags.wait } : {},
20159
21108
  ...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
20160
21109
  },
20161
- client: apiClient._sendClient,
21110
+ client: apiClient.client,
20162
21111
  responseStyle: "fields"
20163
21112
  });
20164
21113
  if (result.error) {
@@ -20229,9 +21178,9 @@ function acquireCredentialsLock(configDir) {
20229
21178
  function commonOtpStartFlags() {
20230
21179
  return {
20231
21180
  "accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy" }),
20232
- "api-base-url-1": Flags.string({
21181
+ "api-base-url": Flags.string({
20233
21182
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
20234
- env: "PRIMITIVE_API_BASE_URL_1",
21183
+ env: "PRIMITIVE_API_BASE_URL",
20235
21184
  hidden: true
20236
21185
  }),
20237
21186
  "device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
@@ -20410,9 +21359,9 @@ var SigninOtpConfirmCommand = class extends Command {
20410
21359
  static summary = "Confirm OTP sign-in";
20411
21360
  static examples = ["<%= config.bin %> signin otp confirm user@example.com 123456", "<%= config.bin %> signin otp confirm user@example.com 123456 --org-id 00000000-0000-4000-8000-000000000000"];
20412
21361
  static flags = {
20413
- "api-base-url-1": Flags.string({
21362
+ "api-base-url": Flags.string({
20414
21363
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
20415
- env: "PRIMITIVE_API_BASE_URL_1",
21364
+ env: "PRIMITIVE_API_BASE_URL",
20416
21365
  hidden: true
20417
21366
  }),
20418
21367
  force: Flags.boolean({
@@ -20489,9 +21438,9 @@ var SigninOtpResendCommand = class extends Command {
20489
21438
  static description = "Resend the verification code for a pending OTP sign-in.";
20490
21439
  static summary = "Resend OTP sign-in code";
20491
21440
  static examples = ["<%= config.bin %> signin otp resend user@example.com"];
20492
- static flags = { "api-base-url-1": Flags.string({
21441
+ static flags = { "api-base-url": Flags.string({
20493
21442
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
20494
- env: "PRIMITIVE_API_BASE_URL_1",
21443
+ env: "PRIMITIVE_API_BASE_URL",
20495
21444
  hidden: true
20496
21445
  }) };
20497
21446
  async run() {
@@ -20577,14 +21526,9 @@ var WhoamiCommand = class WhoamiCommand extends Command {
20577
21526
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
20578
21527
  env: "PRIMITIVE_API_KEY"
20579
21528
  }),
20580
- "api-base-url-1": Flags.string({
20581
- description: API_BASE_URL_1_FLAG_DESCRIPTION,
20582
- env: "PRIMITIVE_API_BASE_URL_1",
20583
- hidden: true
20584
- }),
20585
- "api-base-url-2": Flags.string({
20586
- description: API_BASE_URL_2_FLAG_DESCRIPTION,
20587
- env: "PRIMITIVE_API_BASE_URL_2",
21529
+ "api-base-url": Flags.string({
21530
+ description: API_BASE_URL_FLAG_DESCRIPTION,
21531
+ env: "PRIMITIVE_API_BASE_URL",
20588
21532
  hidden: true
20589
21533
  }),
20590
21534
  json: Flags.boolean({ description: "Print the full account JSON response. Default output hides setup and billing internals." }),
@@ -20595,8 +21539,7 @@ var WhoamiCommand = class WhoamiCommand extends Command {
20595
21539
  await runWithTiming(flags.time, async () => {
20596
21540
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
20597
21541
  apiKey: flags["api-key"],
20598
- apiBaseUrl1: flags["api-base-url-1"],
20599
- apiBaseUrl2: flags["api-base-url-2"],
21542
+ apiBaseUrl: flags["api-base-url"],
20600
21543
  configDir: this.config.configDir
20601
21544
  });
20602
21545
  const result = await getAccount({
@@ -20927,6 +21870,7 @@ const COMMANDS = {
20927
21870
  "emails:latest": EmailsLatestCommand,
20928
21871
  "emails:watch": EmailsWatchCommand,
20929
21872
  "emails:wait": EmailsWaitCommand,
21873
+ "semantic-search": SemanticSearchCommand,
20930
21874
  "domains:zone-file": DomainsZoneFileCommand,
20931
21875
  "domains:download-domain-zone-file": DomainsZoneFileCommand,
20932
21876
  "inbox:setup": InboxSetupCommand,
@@ -20939,6 +21883,10 @@ const COMMANDS = {
20939
21883
  "functions:set-secret": FunctionsSetSecretCommand,
20940
21884
  "functions:test": FunctionsTestFunctionCommand,
20941
21885
  "functions:test-function": FunctionsTestFunctionCommand,
21886
+ "functions:route-set": FunctionsRouteSetCommand,
21887
+ "functions:route-unset": FunctionsRouteUnsetCommand,
21888
+ "functions:route-get": FunctionsRouteGetCommand,
21889
+ "functions:routing-topology": FunctionsRoutingTopologyCommand,
20942
21890
  ...Object.fromEntries(Object.entries(CANONICAL_OPERATION_ALIASES).map(([alias, target]) => {
20943
21891
  const command = generatedCommands[target];
20944
21892
  if (!command) throw new Error(`Missing generated command target for alias ${alias}`);