@primitivedotdev/cli 0.34.0 → 0.35.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
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-D9nB6fOW.js";
2
- import { Args, Command, Errors, Flags } from "@oclif/core";
1
+ import { A as createConfig, C as chatStatePath, D as saveActiveChatState, E as loadChatConversationByLocalId, O as PrimitiveApiClient, S as saveCliCredentials, T as loadActiveChatState, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as normalizeApiBaseUrl, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, k as createClient, 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 deleteChatState, x as resolveCliAuth, y as loadCliCredentials } from "../cli-config-DREZ2BxT.js";
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";
5
5
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
@@ -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({
@@ -68,6 +68,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
68
68
  resendCliSignupVerification: () => resendCliSignupVerification,
69
69
  rotateWebhookSecret: () => rotateWebhookSecret,
70
70
  searchEmails: () => searchEmails,
71
+ semanticSearch: () => semanticSearch,
71
72
  sendEmail: () => sendEmail,
72
73
  setFunctionSecret: () => setFunctionSecret,
73
74
  startAgentSignup: () => startAgentSignup,
@@ -595,9 +596,9 @@ const downloadAttachments = (options) => (options.client ?? client).get({
595
596
  * derivation (Reply-To, then From, then bare sender), and the
596
597
  * `Re:` subject prefix are all derived server-side from the
597
598
  * stored inbound row. The request body carries only the message
598
- * body and optional `wait` flag; passing any header or recipient
599
- * override is rejected by the schema (`additionalProperties:
600
- * false`).
599
+ * body, optional From override, optional attachments, and optional
600
+ * `wait` flag; passing any header or recipient override is
601
+ * rejected by the schema (`additionalProperties: false`).
601
602
  *
602
603
  * Forwards through the same gates as `/send-mail`: the response
603
604
  * status, error envelope, and `idempotent_replay` flag mirror
@@ -933,14 +934,13 @@ const getSendPermissions = (options) => (options?.client ?? client).get({
933
934
  * the request returns once the relay accepts the message for delivery.
934
935
  * Set `wait: true` to wait for the first downstream SMTP delivery outcome.
935
936
  *
936
- * **Host routing.** /send-mail is served by the attachments-
937
- * supporting host (`https://api.primitive.dev/v1`) so the
938
- * request body can carry inline attachments up to ~30 MiB raw.
939
- * The primary host (`https://www.primitive.dev/api/v1`) also
940
- * accepts /send-mail for attachment-free sends; sends WITH
941
- * attachments to the primary host return 413
942
- * `attachments_unsupported_on_this_endpoint`. The typed SDKs
943
- * route /send-mail to the attachments host automatically.
937
+ * **Host routing.** /send-mail is served by the canonical API host
938
+ * (`https://api.primitive.dev/v1`) so the request body can carry
939
+ * inline attachments up to ~30 MiB raw. The legacy dashboard
940
+ * compatibility host (`https://www.primitive.dev/api/v1`) also accepts
941
+ * /send-mail, but Vercel request body limits apply before proxying.
942
+ * The typed SDKs route /send-mail to the canonical API host
943
+ * automatically.
944
944
  *
945
945
  */
946
946
  const sendEmail = (options) => (options.client ?? client).post({
@@ -956,6 +956,42 @@ const sendEmail = (options) => (options.client ?? client).post({
956
956
  }
957
957
  });
958
958
  /**
959
+ * Semantic search across received and sent mail
960
+ *
961
+ * Ranked search across both received and sent mail. The `mode`
962
+ * field selects the ranking strategy:
963
+ *
964
+ * - `keyword`: lexical full-text matching only (no embeddings).
965
+ * - `semantic`: meaning-based matching using vector embeddings.
966
+ * - `hybrid` (default): blends the semantic and keyword signals.
967
+ *
968
+ * Results are ordered by a relevance `score`. Every row reports the
969
+ * fields it matched (`matched_fields`), a match-centered excerpt per
970
+ * field (`snippets`), and a `score_breakdown` whose components account
971
+ * for the `score`. Page through results by passing the prior
972
+ * response's `meta.cursor` back as `cursor`.
973
+ *
974
+ * Requires the Pro plan and the `semantic_search_enabled`
975
+ * entitlement; callers without them receive `403`.
976
+ *
977
+ * Host routing: this operation is served only by the search host
978
+ * (`https://api.primitive.dev/v1`). The typed SDKs route it there
979
+ * automatically.
980
+ *
981
+ */
982
+ const semanticSearch = (options) => (options.client ?? client).post({
983
+ security: [{
984
+ scheme: "bearer",
985
+ type: "http"
986
+ }],
987
+ url: "/semantic-search",
988
+ ...options,
989
+ headers: {
990
+ ...options.body !== void 0 && { "Content-Type": "application/json" },
991
+ ...options.headers
992
+ }
993
+ });
994
+ /**
959
995
  * List outbound sent emails
960
996
  *
961
997
  * Returns a paginated list of OUTBOUND emails the caller's
@@ -1346,11 +1382,11 @@ const openapiDocument = {
1346
1382
  }
1347
1383
  },
1348
1384
  "servers": [{
1349
- "url": "https://www.primitive.dev/api/v1",
1350
- "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"
1351
- }, {
1352
1385
  "url": "https://api.primitive.dev/v1",
1353
- "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"
1386
+ "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"
1387
+ }, {
1388
+ "url": "https://www.primitive.dev/api/v1",
1389
+ "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"
1354
1390
  }],
1355
1391
  "security": [{ "BearerAuth": [] }],
1356
1392
  "tags": [
@@ -1378,6 +1414,10 @@ const openapiDocument = {
1378
1414
  "name": "Emails",
1379
1415
  "description": "List, inspect, and manage received emails"
1380
1416
  },
1417
+ {
1418
+ "name": "Search",
1419
+ "description": "Semantic and hybrid search across received and sent mail"
1420
+ },
1381
1421
  {
1382
1422
  "name": "Sending",
1383
1423
  "description": "Send outbound emails through the Primitive API"
@@ -2366,7 +2406,14 @@ const openapiDocument = {
2366
2406
  "post": {
2367
2407
  "operationId": "replyToEmail",
2368
2408
  "summary": "Reply to an inbound email",
2369
- "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 and optional `wait` flag; passing any header or recipient\noverride is rejected by the schema (`additionalProperties:\nfalse`).\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",
2409
+ "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",
2410
+ "servers": [{
2411
+ "url": "https://api.primitive.dev/v1",
2412
+ "description": "Canonical API host (recommended)"
2413
+ }, {
2414
+ "url": "https://www.primitive.dev/api/v1",
2415
+ "description": "Legacy compatibility host (Vercel body limit applies)"
2416
+ }],
2370
2417
  "tags": ["Sending"],
2371
2418
  "requestBody": {
2372
2419
  "required": true,
@@ -2774,13 +2821,13 @@ const openapiDocument = {
2774
2821
  "/send-mail": { "post": {
2775
2822
  "operationId": "sendEmail",
2776
2823
  "summary": "Send outbound email",
2777
- "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",
2824
+ "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",
2778
2825
  "servers": [{
2779
2826
  "url": "https://api.primitive.dev/v1",
2780
- "description": "Attachments-supporting send host (recommended)"
2827
+ "description": "Canonical API host (recommended)"
2781
2828
  }, {
2782
2829
  "url": "https://www.primitive.dev/api/v1",
2783
- "description": "Primary host (attachment-free sends only)"
2830
+ "description": "Legacy compatibility host (Vercel body limit applies)"
2784
2831
  }],
2785
2832
  "tags": ["Sending"],
2786
2833
  "parameters": [{
@@ -2816,6 +2863,42 @@ const openapiDocument = {
2816
2863
  "503": { "$ref": "#/components/responses/ServiceUnavailable" }
2817
2864
  }
2818
2865
  } },
2866
+ "/semantic-search": { "post": {
2867
+ "operationId": "semanticSearch",
2868
+ "summary": "Semantic search across received and sent mail",
2869
+ "description": "Ranked search across both received and sent mail. The `mode`\nfield selects the ranking strategy:\n\n- `keyword`: lexical full-text matching only (no embeddings).\n- `semantic`: meaning-based matching using vector embeddings.\n- `hybrid` (default): blends the semantic and keyword signals.\n\nResults are ordered by a relevance `score`. Every row reports the\nfields it matched (`matched_fields`), a match-centered excerpt per\nfield (`snippets`), and a `score_breakdown` whose components account\nfor the `score`. Page through results by passing the prior\nresponse's `meta.cursor` back as `cursor`.\n\nRequires the Pro plan and the `semantic_search_enabled`\nentitlement; callers without them receive `403`.\n\nHost routing: this operation is served only by the search host\n(`https://api.primitive.dev/v1`). The typed SDKs route it there\nautomatically.\n",
2870
+ "servers": [{
2871
+ "url": "https://api.primitive.dev/v1",
2872
+ "description": "Search host"
2873
+ }],
2874
+ "tags": ["Search"],
2875
+ "requestBody": {
2876
+ "required": true,
2877
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SemanticSearchInput" } } }
2878
+ },
2879
+ "responses": {
2880
+ "200": {
2881
+ "description": "Ranked search results",
2882
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
2883
+ "type": "object",
2884
+ "properties": {
2885
+ "data": {
2886
+ "type": "array",
2887
+ "items": { "$ref": "#/components/schemas/SemanticSearchResult" }
2888
+ },
2889
+ "meta": { "$ref": "#/components/schemas/SemanticSearchMeta" }
2890
+ },
2891
+ "required": ["data", "meta"]
2892
+ }] } } }
2893
+ },
2894
+ "400": { "$ref": "#/components/responses/ValidationError" },
2895
+ "401": { "$ref": "#/components/responses/Unauthorized" },
2896
+ "403": { "$ref": "#/components/responses/Forbidden" },
2897
+ "429": { "$ref": "#/components/responses/RateLimited" },
2898
+ "500": { "$ref": "#/components/responses/InternalError" },
2899
+ "503": { "$ref": "#/components/responses/ServiceUnavailable" }
2900
+ }
2901
+ } },
2819
2902
  "/sent-emails": { "get": {
2820
2903
  "operationId": "listSentEmails",
2821
2904
  "summary": "List outbound sent emails",
@@ -5660,6 +5743,236 @@ const openapiDocument = {
5660
5743
  "body_size_bytes"
5661
5744
  ]
5662
5745
  },
5746
+ "SemanticSearchField": {
5747
+ "type": "string",
5748
+ "enum": [
5749
+ "subject",
5750
+ "headers",
5751
+ "addresses",
5752
+ "body"
5753
+ ],
5754
+ "description": "A searchable email field."
5755
+ },
5756
+ "SemanticSearchInput": {
5757
+ "type": "object",
5758
+ "properties": {
5759
+ "query": {
5760
+ "type": "string",
5761
+ "minLength": 1,
5762
+ "maxLength": 2048,
5763
+ "description": "Free-text query. Required for `semantic` and `hybrid` modes;\noptional for `keyword` mode.\n"
5764
+ },
5765
+ "mode": {
5766
+ "type": "string",
5767
+ "enum": [
5768
+ "hybrid",
5769
+ "semantic",
5770
+ "keyword"
5771
+ ],
5772
+ "default": "hybrid",
5773
+ "description": "Ranking strategy. `keyword` is lexical only, `semantic` is\nembedding-based, `hybrid` blends both.\n"
5774
+ },
5775
+ "corpus": {
5776
+ "type": "array",
5777
+ "items": {
5778
+ "type": "string",
5779
+ "enum": ["inbound", "outbound"]
5780
+ },
5781
+ "minItems": 1,
5782
+ "maxItems": 2,
5783
+ "description": "Which mail to search. Defaults to both received (`inbound`)\nand sent (`outbound`).\n"
5784
+ },
5785
+ "search_in": {
5786
+ "type": "array",
5787
+ "items": { "$ref": "#/components/schemas/SemanticSearchField" },
5788
+ "description": "Restrict matching to these fields. Defaults to all."
5789
+ },
5790
+ "exclude": {
5791
+ "type": "array",
5792
+ "items": { "$ref": "#/components/schemas/SemanticSearchField" },
5793
+ "description": "Exclude these fields from matching."
5794
+ },
5795
+ "date_from": {
5796
+ "type": "string",
5797
+ "format": "date-time",
5798
+ "description": "Only include mail at or after this timestamp."
5799
+ },
5800
+ "date_to": {
5801
+ "type": "string",
5802
+ "format": "date-time",
5803
+ "description": "Only include mail at or before this timestamp."
5804
+ },
5805
+ "include": {
5806
+ "type": "array",
5807
+ "items": {
5808
+ "type": "string",
5809
+ "enum": ["coverage"]
5810
+ },
5811
+ "description": "Opt-in extras. `coverage` adds an index-coverage snapshot to\n`meta`. Matched fields, snippets, and the score breakdown are\nalways returned regardless of this field.\n"
5812
+ },
5813
+ "limit": {
5814
+ "type": "integer",
5815
+ "minimum": 1,
5816
+ "maximum": 100,
5817
+ "default": 10,
5818
+ "description": "Maximum number of results to return."
5819
+ },
5820
+ "cursor": {
5821
+ "type": "string",
5822
+ "description": "Opaque pagination cursor from a prior response's `meta.cursor`."
5823
+ }
5824
+ }
5825
+ },
5826
+ "SemanticSearchSnippet": {
5827
+ "type": "object",
5828
+ "properties": {
5829
+ "field": {
5830
+ "type": "string",
5831
+ "description": "The field this excerpt came from."
5832
+ },
5833
+ "text": {
5834
+ "type": "string",
5835
+ "description": "Plain-text excerpt centered on the match (no markup)."
5836
+ }
5837
+ },
5838
+ "required": ["field", "text"]
5839
+ },
5840
+ "SemanticSearchScoreBreakdown": {
5841
+ "type": "object",
5842
+ "description": "Additive contributions to `score`. `semantic` and `keyword` are the\nraw signals times the mode's weight (null when not applicable);\nthese plus `field_boost` and `recency` sum to `score` before each\nvalue is independently rounded to 5 decimal places.\n",
5843
+ "properties": {
5844
+ "semantic": { "type": ["number", "null"] },
5845
+ "keyword": { "type": ["number", "null"] },
5846
+ "field_boost": { "type": "number" },
5847
+ "recency": { "type": "number" }
5848
+ },
5849
+ "required": [
5850
+ "semantic",
5851
+ "keyword",
5852
+ "field_boost",
5853
+ "recency"
5854
+ ]
5855
+ },
5856
+ "SemanticSearchResult": {
5857
+ "type": "object",
5858
+ "properties": {
5859
+ "source_type": {
5860
+ "type": "string",
5861
+ "enum": ["inbound_email", "sent_email"],
5862
+ "description": "Whether this row is a received or sent message."
5863
+ },
5864
+ "id": {
5865
+ "type": "string",
5866
+ "description": "Message id. Combine with `api_url` to fetch the full record."
5867
+ },
5868
+ "subject": { "type": ["string", "null"] },
5869
+ "from": { "type": ["string", "null"] },
5870
+ "to": { "type": ["string", "null"] },
5871
+ "timestamp": {
5872
+ "type": "string",
5873
+ "description": "Message timestamp (received_at for inbound, created_at for sent)."
5874
+ },
5875
+ "status": {
5876
+ "type": "string",
5877
+ "description": "Lifecycle status of the message."
5878
+ },
5879
+ "score": {
5880
+ "type": "number",
5881
+ "description": "Overall relevance score; the `score_breakdown` components account for it."
5882
+ },
5883
+ "semantic_score": {
5884
+ "type": ["number", "null"],
5885
+ "description": "Raw semantic similarity signal, or null when not applicable."
5886
+ },
5887
+ "keyword_score": {
5888
+ "type": ["number", "null"],
5889
+ "description": "Raw keyword (lexical) signal, or null when not applicable."
5890
+ },
5891
+ "matched_fields": {
5892
+ "type": "array",
5893
+ "items": { "$ref": "#/components/schemas/SemanticSearchField" },
5894
+ "description": "Fields where the query matched."
5895
+ },
5896
+ "snippets": {
5897
+ "type": "array",
5898
+ "items": { "$ref": "#/components/schemas/SemanticSearchSnippet" },
5899
+ "description": "Match-centered excerpts, one per matched field."
5900
+ },
5901
+ "score_breakdown": { "$ref": "#/components/schemas/SemanticSearchScoreBreakdown" },
5902
+ "api_url": {
5903
+ "type": ["string", "null"],
5904
+ "description": "Relative API path to fetch the full message."
5905
+ }
5906
+ },
5907
+ "required": [
5908
+ "source_type",
5909
+ "id",
5910
+ "subject",
5911
+ "from",
5912
+ "to",
5913
+ "timestamp",
5914
+ "status",
5915
+ "score",
5916
+ "semantic_score",
5917
+ "keyword_score",
5918
+ "matched_fields",
5919
+ "snippets",
5920
+ "score_breakdown",
5921
+ "api_url"
5922
+ ]
5923
+ },
5924
+ "SemanticSearchCoverage": {
5925
+ "type": "object",
5926
+ "description": "Index-coverage snapshot for the org, returned only when the `coverage` include option is requested.",
5927
+ "properties": {
5928
+ "embedded_chunks": { "type": "integer" },
5929
+ "pending_chunks": { "type": "integer" },
5930
+ "skipped_plan_chunks": { "type": "integer" },
5931
+ "skipped_quota_chunks": { "type": "integer" },
5932
+ "unsupported_attachment_chunks": { "type": "integer" },
5933
+ "failed_chunks": { "type": "integer" }
5934
+ },
5935
+ "required": [
5936
+ "embedded_chunks",
5937
+ "pending_chunks",
5938
+ "skipped_plan_chunks",
5939
+ "skipped_quota_chunks",
5940
+ "unsupported_attachment_chunks",
5941
+ "failed_chunks"
5942
+ ]
5943
+ },
5944
+ "SemanticSearchMeta": {
5945
+ "type": "object",
5946
+ "properties": {
5947
+ "limit": {
5948
+ "type": "integer",
5949
+ "description": "Page size used for this request."
5950
+ },
5951
+ "cursor": {
5952
+ "type": ["string", "null"],
5953
+ "description": "Cursor for the next page, or null if there are no more results."
5954
+ },
5955
+ "mode": {
5956
+ "type": "string",
5957
+ "enum": [
5958
+ "hybrid",
5959
+ "semantic",
5960
+ "keyword"
5961
+ ],
5962
+ "description": "Ranking mode used for this response."
5963
+ },
5964
+ "coverage": {
5965
+ "oneOf": [{ "$ref": "#/components/schemas/SemanticSearchCoverage" }, { "type": "null" }],
5966
+ "description": "Index-coverage snapshot, present only when requested via\n`include: [coverage]`; otherwise null.\n"
5967
+ }
5968
+ },
5969
+ "required": [
5970
+ "limit",
5971
+ "cursor",
5972
+ "mode",
5973
+ "coverage"
5974
+ ]
5975
+ },
5663
5976
  "SentEmailDetail": {
5664
5977
  "description": "Full sent-email record, including `body_text` and\n`body_html`. Returned by /sent-emails/{id}.\n",
5665
5978
  "allOf": [{ "$ref": "#/components/schemas/SentEmailSummary" }, {
@@ -5698,6 +6011,12 @@ const openapiDocument = {
5698
6011
  "wait": {
5699
6012
  "type": "boolean",
5700
6013
  "description": "When true, wait for the first downstream SMTP delivery outcome before returning, mirroring the send-mail `wait` semantics."
6014
+ },
6015
+ "attachments": {
6016
+ "type": "array",
6017
+ "maxItems": 100,
6018
+ "description": "Inline attachments for this reply. Use https://api.primitive.dev/v1 for replies with attachments. Combined raw decoded attachment bytes must be at most 31457280.",
6019
+ "items": { "$ref": "#/components/schemas/SendMailAttachment" }
5701
6020
  }
5702
6021
  }
5703
6022
  },
@@ -11458,35 +11777,247 @@ const operationManifest = [
11458
11777
  },
11459
11778
  {
11460
11779
  "binaryResponse": false,
11461
- "bodyRequired": false,
11462
- "command": "get-send-permissions",
11463
- "description": "Returns a flat list of rules describing every recipient the\ncaller may send to. Each rule has a `type`, a kind-specific\npayload, and a human-readable `description`. If any rule\nmatches the recipient, /send-mail will accept the send under\nthe recipient-scope check.\n\nThe endpoint is the answer to \"where can I send\" without\nexposing internal entitlement names. Agents that don't\nrecognize a `type` can still read the `description` prose\nand act on it.\n\nRule kinds, ordered broadest-first so an agent can stop\nscanning at the first match:\n\n 1. `any_recipient` (one entry, only when the org can send\n anywhere): every other rule below it is redundant.\n 2. `managed_zone` (always emitted, one per Primitive-managed\n zone): sends to any address at *.primitive.email or\n *.email.works always succeed; no entitlement required.\n 3. `your_domain` (one per active verified outbound domain\n owned by the org): sends to that domain are approved.\n 4. `address` (one per address that has authenticated\n inbound mail to the org, capped at `meta.address_cap`):\n sends to that exact address are approved.\n\nThe list is informational, not an authorization check.\n/send-mail remains the source of truth on whether an\nindividual send will succeed (it also enforces the\nfrom-address and the `send_mail` entitlement, which are\nnot recipient-scope concerns and are not represented here).\n",
11464
- "hasJsonBody": false,
11465
- "method": "GET",
11466
- "operationId": "getSendPermissions",
11467
- "path": "/send-permissions",
11780
+ "bodyRequired": true,
11781
+ "command": "semantic-search",
11782
+ "description": "Ranked search across both received and sent mail. The `mode`\nfield selects the ranking strategy:\n\n- `keyword`: lexical full-text matching only (no embeddings).\n- `semantic`: meaning-based matching using vector embeddings.\n- `hybrid` (default): blends the semantic and keyword signals.\n\nResults are ordered by a relevance `score`. Every row reports the\nfields it matched (`matched_fields`), a match-centered excerpt per\nfield (`snippets`), and a `score_breakdown` whose components account\nfor the `score`. Page through results by passing the prior\nresponse's `meta.cursor` back as `cursor`.\n\nRequires the Pro plan and the `semantic_search_enabled`\nentitlement; callers without them receive `403`.\n\nHost routing: this operation is served only by the search host\n(`https://api.primitive.dev/v1`). The typed SDKs route it there\nautomatically.\n",
11783
+ "hasJsonBody": true,
11784
+ "method": "POST",
11785
+ "operationId": "semanticSearch",
11786
+ "path": "/semantic-search",
11468
11787
  "pathParams": [],
11469
11788
  "queryParams": [],
11470
- "requestSchema": null,
11789
+ "requestSchema": {
11790
+ "type": "object",
11791
+ "properties": {
11792
+ "query": {
11793
+ "type": "string",
11794
+ "minLength": 1,
11795
+ "maxLength": 2048,
11796
+ "description": "Free-text query. Required for `semantic` and `hybrid` modes;\noptional for `keyword` mode.\n"
11797
+ },
11798
+ "mode": {
11799
+ "type": "string",
11800
+ "enum": [
11801
+ "hybrid",
11802
+ "semantic",
11803
+ "keyword"
11804
+ ],
11805
+ "default": "hybrid",
11806
+ "description": "Ranking strategy. `keyword` is lexical only, `semantic` is\nembedding-based, `hybrid` blends both.\n"
11807
+ },
11808
+ "corpus": {
11809
+ "type": "array",
11810
+ "items": {
11811
+ "type": "string",
11812
+ "enum": ["inbound", "outbound"]
11813
+ },
11814
+ "minItems": 1,
11815
+ "maxItems": 2,
11816
+ "description": "Which mail to search. Defaults to both received (`inbound`)\nand sent (`outbound`).\n"
11817
+ },
11818
+ "search_in": {
11819
+ "type": "array",
11820
+ "items": {
11821
+ "type": "string",
11822
+ "enum": [
11823
+ "subject",
11824
+ "headers",
11825
+ "addresses",
11826
+ "body"
11827
+ ],
11828
+ "description": "A searchable email field."
11829
+ },
11830
+ "description": "Restrict matching to these fields. Defaults to all."
11831
+ },
11832
+ "exclude": {
11833
+ "type": "array",
11834
+ "items": {
11835
+ "type": "string",
11836
+ "enum": [
11837
+ "subject",
11838
+ "headers",
11839
+ "addresses",
11840
+ "body"
11841
+ ],
11842
+ "description": "A searchable email field."
11843
+ },
11844
+ "description": "Exclude these fields from matching."
11845
+ },
11846
+ "date_from": {
11847
+ "type": "string",
11848
+ "format": "date-time",
11849
+ "description": "Only include mail at or after this timestamp."
11850
+ },
11851
+ "date_to": {
11852
+ "type": "string",
11853
+ "format": "date-time",
11854
+ "description": "Only include mail at or before this timestamp."
11855
+ },
11856
+ "include": {
11857
+ "type": "array",
11858
+ "items": {
11859
+ "type": "string",
11860
+ "enum": ["coverage"]
11861
+ },
11862
+ "description": "Opt-in extras. `coverage` adds an index-coverage snapshot to\n`meta`. Matched fields, snippets, and the score breakdown are\nalways returned regardless of this field.\n"
11863
+ },
11864
+ "limit": {
11865
+ "type": "integer",
11866
+ "minimum": 1,
11867
+ "maximum": 100,
11868
+ "default": 10,
11869
+ "description": "Maximum number of results to return."
11870
+ },
11871
+ "cursor": {
11872
+ "type": "string",
11873
+ "description": "Opaque pagination cursor from a prior response's `meta.cursor`."
11874
+ }
11875
+ }
11876
+ },
11471
11877
  "responseSchema": {
11472
11878
  "type": "array",
11473
11879
  "items": {
11474
- "description": "One recipient-scope rule describing a destination the caller\nmay send to. Discriminated on `type`. Each rule carries a\nhuman-prose `description` field intended for display.\n\nRule kinds are stable within an SDK release. A response\ncontaining a `type` value not enumerated in this schema\nmeans the server is running a newer version than the SDK;\nupgrade the SDK to the release that matches the server's\nschema. Strict-parsing SDKs (Go, Python) will raise a\ndecode error in that case rather than silently dropping\nthe unknown rule, since silent drops would let an outbound\nagent reason from an incomplete view of its own permissions.\n",
11475
- "discriminator": {
11476
- "propertyName": "type",
11477
- "mapping": {
11478
- "any_recipient": "#/components/schemas/SendPermissionAnyRecipient",
11479
- "managed_zone": "#/components/schemas/SendPermissionManagedZone",
11480
- "your_domain": "#/components/schemas/SendPermissionYourDomain",
11481
- "address": "#/components/schemas/SendPermissionAddress"
11482
- }
11483
- },
11484
- "oneOf": [
11485
- {
11486
- "type": "object",
11487
- "description": "The caller can send to any recipient. When this rule is\npresent, every other rule in the response is redundant.\n",
11488
- "properties": {
11489
- "type": {
11880
+ "type": "object",
11881
+ "properties": {
11882
+ "source_type": {
11883
+ "type": "string",
11884
+ "enum": ["inbound_email", "sent_email"],
11885
+ "description": "Whether this row is a received or sent message."
11886
+ },
11887
+ "id": {
11888
+ "type": "string",
11889
+ "description": "Message id. Combine with `api_url` to fetch the full record."
11890
+ },
11891
+ "subject": { "type": ["string", "null"] },
11892
+ "from": { "type": ["string", "null"] },
11893
+ "to": { "type": ["string", "null"] },
11894
+ "timestamp": {
11895
+ "type": "string",
11896
+ "description": "Message timestamp (received_at for inbound, created_at for sent)."
11897
+ },
11898
+ "status": {
11899
+ "type": "string",
11900
+ "description": "Lifecycle status of the message."
11901
+ },
11902
+ "score": {
11903
+ "type": "number",
11904
+ "description": "Overall relevance score; the `score_breakdown` components account for it."
11905
+ },
11906
+ "semantic_score": {
11907
+ "type": ["number", "null"],
11908
+ "description": "Raw semantic similarity signal, or null when not applicable."
11909
+ },
11910
+ "keyword_score": {
11911
+ "type": ["number", "null"],
11912
+ "description": "Raw keyword (lexical) signal, or null when not applicable."
11913
+ },
11914
+ "matched_fields": {
11915
+ "type": "array",
11916
+ "items": {
11917
+ "type": "string",
11918
+ "enum": [
11919
+ "subject",
11920
+ "headers",
11921
+ "addresses",
11922
+ "body"
11923
+ ],
11924
+ "description": "A searchable email field."
11925
+ },
11926
+ "description": "Fields where the query matched."
11927
+ },
11928
+ "snippets": {
11929
+ "type": "array",
11930
+ "items": {
11931
+ "type": "object",
11932
+ "properties": {
11933
+ "field": {
11934
+ "type": "string",
11935
+ "description": "The field this excerpt came from."
11936
+ },
11937
+ "text": {
11938
+ "type": "string",
11939
+ "description": "Plain-text excerpt centered on the match (no markup)."
11940
+ }
11941
+ },
11942
+ "required": ["field", "text"]
11943
+ },
11944
+ "description": "Match-centered excerpts, one per matched field."
11945
+ },
11946
+ "score_breakdown": {
11947
+ "type": "object",
11948
+ "description": "Additive contributions to `score`. `semantic` and `keyword` are the\nraw signals times the mode's weight (null when not applicable);\nthese plus `field_boost` and `recency` sum to `score` before each\nvalue is independently rounded to 5 decimal places.\n",
11949
+ "properties": {
11950
+ "semantic": { "type": ["number", "null"] },
11951
+ "keyword": { "type": ["number", "null"] },
11952
+ "field_boost": { "type": "number" },
11953
+ "recency": { "type": "number" }
11954
+ },
11955
+ "required": [
11956
+ "semantic",
11957
+ "keyword",
11958
+ "field_boost",
11959
+ "recency"
11960
+ ]
11961
+ },
11962
+ "api_url": {
11963
+ "type": ["string", "null"],
11964
+ "description": "Relative API path to fetch the full message."
11965
+ }
11966
+ },
11967
+ "required": [
11968
+ "source_type",
11969
+ "id",
11970
+ "subject",
11971
+ "from",
11972
+ "to",
11973
+ "timestamp",
11974
+ "status",
11975
+ "score",
11976
+ "semantic_score",
11977
+ "keyword_score",
11978
+ "matched_fields",
11979
+ "snippets",
11980
+ "score_breakdown",
11981
+ "api_url"
11982
+ ]
11983
+ }
11984
+ },
11985
+ "sdkName": "semanticSearch",
11986
+ "summary": "Semantic search across received and sent mail",
11987
+ "tag": "Search",
11988
+ "tagCommand": "search"
11989
+ },
11990
+ {
11991
+ "binaryResponse": false,
11992
+ "bodyRequired": false,
11993
+ "command": "get-send-permissions",
11994
+ "description": "Returns a flat list of rules describing every recipient the\ncaller may send to. Each rule has a `type`, a kind-specific\npayload, and a human-readable `description`. If any rule\nmatches the recipient, /send-mail will accept the send under\nthe recipient-scope check.\n\nThe endpoint is the answer to \"where can I send\" without\nexposing internal entitlement names. Agents that don't\nrecognize a `type` can still read the `description` prose\nand act on it.\n\nRule kinds, ordered broadest-first so an agent can stop\nscanning at the first match:\n\n 1. `any_recipient` (one entry, only when the org can send\n anywhere): every other rule below it is redundant.\n 2. `managed_zone` (always emitted, one per Primitive-managed\n zone): sends to any address at *.primitive.email or\n *.email.works always succeed; no entitlement required.\n 3. `your_domain` (one per active verified outbound domain\n owned by the org): sends to that domain are approved.\n 4. `address` (one per address that has authenticated\n inbound mail to the org, capped at `meta.address_cap`):\n sends to that exact address are approved.\n\nThe list is informational, not an authorization check.\n/send-mail remains the source of truth on whether an\nindividual send will succeed (it also enforces the\nfrom-address and the `send_mail` entitlement, which are\nnot recipient-scope concerns and are not represented here).\n",
11995
+ "hasJsonBody": false,
11996
+ "method": "GET",
11997
+ "operationId": "getSendPermissions",
11998
+ "path": "/send-permissions",
11999
+ "pathParams": [],
12000
+ "queryParams": [],
12001
+ "requestSchema": null,
12002
+ "responseSchema": {
12003
+ "type": "array",
12004
+ "items": {
12005
+ "description": "One recipient-scope rule describing a destination the caller\nmay send to. Discriminated on `type`. Each rule carries a\nhuman-prose `description` field intended for display.\n\nRule kinds are stable within an SDK release. A response\ncontaining a `type` value not enumerated in this schema\nmeans the server is running a newer version than the SDK;\nupgrade the SDK to the release that matches the server's\nschema. Strict-parsing SDKs (Go, Python) will raise a\ndecode error in that case rather than silently dropping\nthe unknown rule, since silent drops would let an outbound\nagent reason from an incomplete view of its own permissions.\n",
12006
+ "discriminator": {
12007
+ "propertyName": "type",
12008
+ "mapping": {
12009
+ "any_recipient": "#/components/schemas/SendPermissionAnyRecipient",
12010
+ "managed_zone": "#/components/schemas/SendPermissionManagedZone",
12011
+ "your_domain": "#/components/schemas/SendPermissionYourDomain",
12012
+ "address": "#/components/schemas/SendPermissionAddress"
12013
+ }
12014
+ },
12015
+ "oneOf": [
12016
+ {
12017
+ "type": "object",
12018
+ "description": "The caller can send to any recipient. When this rule is\npresent, every other rule in the response is redundant.\n",
12019
+ "properties": {
12020
+ "type": {
11490
12021
  "type": "string",
11491
12022
  "enum": ["any_recipient"]
11492
12023
  },
@@ -12108,7 +12639,7 @@ const operationManifest = [
12108
12639
  "binaryResponse": false,
12109
12640
  "bodyRequired": true,
12110
12641
  "command": "reply-to-email",
12111
- "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 and optional `wait` flag; passing any header or recipient\noverride is rejected by the schema (`additionalProperties:\nfalse`).\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",
12642
+ "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",
12112
12643
  "hasJsonBody": true,
12113
12644
  "method": "POST",
12114
12645
  "operationId": "replyToEmail",
@@ -12143,6 +12674,36 @@ const operationManifest = [
12143
12674
  "wait": {
12144
12675
  "type": "boolean",
12145
12676
  "description": "When true, wait for the first downstream SMTP delivery outcome before returning, mirroring the send-mail `wait` semantics."
12677
+ },
12678
+ "attachments": {
12679
+ "type": "array",
12680
+ "maxItems": 100,
12681
+ "description": "Inline attachments for this reply. Use https://api.primitive.dev/v1 for replies with attachments. Combined raw decoded attachment bytes must be at most 31457280.",
12682
+ "items": {
12683
+ "type": "object",
12684
+ "additionalProperties": false,
12685
+ "properties": {
12686
+ "filename": {
12687
+ "type": "string",
12688
+ "minLength": 1,
12689
+ "maxLength": 255,
12690
+ "description": "Attachment filename. Control characters are rejected."
12691
+ },
12692
+ "content_type": {
12693
+ "type": "string",
12694
+ "minLength": 1,
12695
+ "maxLength": 255,
12696
+ "description": "Optional MIME content type. Control characters are rejected."
12697
+ },
12698
+ "content_base64": {
12699
+ "type": "string",
12700
+ "minLength": 1,
12701
+ "maxLength": 44040192,
12702
+ "description": "Base64-encoded attachment bytes."
12703
+ }
12704
+ },
12705
+ "required": ["filename", "content_base64"]
12706
+ }
12146
12707
  }
12147
12708
  }
12148
12709
  },
@@ -12243,7 +12804,7 @@ const operationManifest = [
12243
12804
  "binaryResponse": false,
12244
12805
  "bodyRequired": true,
12245
12806
  "command": "send-email",
12246
- "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",
12807
+ "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",
12247
12808
  "hasJsonBody": true,
12248
12809
  "method": "POST",
12249
12810
  "operationId": "sendEmail",
@@ -12740,19 +13301,15 @@ function cliApiHeadersFromEnv(env = process.env) {
12740
13301
  }
12741
13302
  function resolveCliApiRequestConfig(params) {
12742
13303
  const currentEnvironment = resolveConfigEnvironment(loadCliConfig(params.configDir));
12743
- const configuredApiBaseUrl1 = currentEnvironment?.config.api_base_url_1;
12744
- const configuredApiBaseUrl2 = currentEnvironment?.config.api_base_url_2;
12745
- 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 });
12746
- const apiBaseUrl1 = params.apiBaseUrl1 !== void 0 ? normalizeApiBaseUrl1(params.apiBaseUrl1) : configuredApiBaseUrl1;
12747
- const apiBaseUrl2 = params.apiBaseUrl2 !== void 0 ? normalizeApiBaseUrl2(params.apiBaseUrl2) : configuredApiBaseUrl2;
13304
+ const configuredApiBaseUrl = currentEnvironment?.config.api_base_url;
13305
+ 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 });
13306
+ const apiBaseUrl = params.apiBaseUrl !== void 0 ? normalizeApiBaseUrl(params.apiBaseUrl) : configuredApiBaseUrl;
12748
13307
  return {
12749
- apiBaseUrl1,
12750
- apiBaseUrl2,
12751
- baseUrlOverridden: apiBaseUrl1 !== void 0 || apiBaseUrl2 !== void 0,
13308
+ apiBaseUrl,
13309
+ baseUrlOverridden: apiBaseUrl !== void 0,
12752
13310
  environmentName: currentEnvironment?.name ?? null,
12753
13311
  headers: mergeHeaders(currentEnvironment?.config.headers, cliApiHeadersFromEnv(params.env)),
12754
- resolvedApiBaseUrl1: normalizeApiBaseUrl1(apiBaseUrl1),
12755
- resolvedApiBaseUrl2: normalizeApiBaseUrl2(apiBaseUrl2)
13312
+ resolvedApiBaseUrl: normalizeApiBaseUrl(apiBaseUrl)
12756
13313
  };
12757
13314
  }
12758
13315
  function createCliApiClient(params) {
@@ -12760,15 +13317,14 @@ function createCliApiClient(params) {
12760
13317
  return {
12761
13318
  apiClient: new PrimitiveApiClient({
12762
13319
  apiKey: params.apiKey,
12763
- apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
12764
- apiBaseUrl2: requestConfig.resolvedApiBaseUrl2,
13320
+ apiBaseUrl: requestConfig.resolvedApiBaseUrl,
12765
13321
  headers: requestConfig.headers
12766
13322
  }),
12767
13323
  requestConfig
12768
13324
  };
12769
13325
  }
12770
- function oauthTokenEndpoint(apiBaseUrl1) {
12771
- const url = new URL(apiBaseUrl1);
13326
+ function oauthTokenEndpoint(apiBaseUrl) {
13327
+ const url = new URL(apiBaseUrl);
12772
13328
  url.pathname = "/oauth/token";
12773
13329
  url.search = "";
12774
13330
  url.hash = "";
@@ -12816,7 +13372,7 @@ async function refreshStoredCliCredentials(params) {
12816
13372
  });
12817
13373
  let response;
12818
13374
  try {
12819
- response = await fetchImpl(oauthTokenEndpoint(params.apiBaseUrl1), {
13375
+ response = await fetchImpl(oauthTokenEndpoint(params.apiBaseUrl), {
12820
13376
  body,
12821
13377
  headers: {
12822
13378
  ...params.headers ?? {},
@@ -12856,13 +13412,12 @@ async function createAuthenticatedCliApiClient(params) {
12856
13412
  const requestConfig = resolveCliApiRequestConfig(params);
12857
13413
  let auth = resolveCliAuth({
12858
13414
  apiKey: params.apiKey,
12859
- apiBaseUrl1: requestConfig.apiBaseUrl1,
12860
- apiBaseUrl2: requestConfig.apiBaseUrl2,
13415
+ apiBaseUrl: requestConfig.apiBaseUrl,
12861
13416
  configDir: params.configDir
12862
13417
  });
12863
13418
  if (auth.source === "stored" && auth.credentials) {
12864
13419
  const refreshed = await refreshStoredCliCredentials({
12865
- apiBaseUrl1: auth.apiBaseUrl1,
13420
+ apiBaseUrl: auth.apiBaseUrl,
12866
13421
  configDir: params.configDir,
12867
13422
  credentials: auth.credentials,
12868
13423
  credentialsLockHeld: params.credentialsLockHeld,
@@ -12879,8 +13434,7 @@ async function createAuthenticatedCliApiClient(params) {
12879
13434
  return {
12880
13435
  apiClient: new PrimitiveApiClient({
12881
13436
  apiKey: auth.apiKey,
12882
- apiBaseUrl1: auth.apiBaseUrl1,
12883
- apiBaseUrl2: auth.apiBaseUrl2,
13437
+ apiBaseUrl: auth.apiBaseUrl,
12884
13438
  headers: requestConfig.headers
12885
13439
  }),
12886
13440
  auth,
@@ -13196,7 +13750,7 @@ const NETWORK_ERROR_HINTS = {
13196
13750
  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.",
13197
13751
  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.",
13198
13752
  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.",
13199
- 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."
13753
+ 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."
13200
13754
  };
13201
13755
  function writeErrorWithHints(payload) {
13202
13756
  process.stderr.write(`${formatErrorPayload(payload)}\n`);
@@ -13225,8 +13779,8 @@ function writeErrorWithHints(payload) {
13225
13779
  */
13226
13780
  function surfaceUnauthorizedHint(params) {
13227
13781
  if (extractErrorCode(params.payload) !== API_ERROR_CODES.unauthorized || params.auth.source !== "stored") return;
13228
- if (params.baseUrlOverridden && params.auth.credentials !== null && params.auth.apiBaseUrl1 !== params.auth.credentials.api_base_url_1) {
13229
- 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");
13782
+ if (params.baseUrlOverridden && params.auth.credentials !== null && params.auth.apiBaseUrl !== params.auth.credentials.api_base_url) {
13783
+ 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");
13230
13784
  return;
13231
13785
  }
13232
13786
  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");
@@ -13247,13 +13801,10 @@ async function runWithTiming(enabled, fn) {
13247
13801
  }
13248
13802
  }
13249
13803
  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.";
13250
- const API_BASE_URL_1_FLAG_DESCRIPTION = "Override the primary API base URL. Internal testing only; not documented to customers.";
13251
- const API_BASE_URL_2_FLAG_DESCRIPTION = "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.";
13252
- const HOST_2_OPERATIONS = new Set(["sendEmail"]);
13804
+ const API_BASE_URL_FLAG_DESCRIPTION = "Override the API base URL. Internal testing only; not documented to customers.";
13253
13805
  const RESERVED_FLAG_NAMES = new Set([
13254
13806
  "api-key",
13255
- "api-base-url-1",
13256
- "api-base-url-2",
13807
+ "api-base-url",
13257
13808
  "raw-body",
13258
13809
  "body-file",
13259
13810
  "envelope",
@@ -13285,14 +13836,9 @@ function buildFlags(operation) {
13285
13836
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
13286
13837
  env: "PRIMITIVE_API_KEY"
13287
13838
  }),
13288
- "api-base-url-1": Flags.string({
13289
- description: "Override the primary API base URL. Internal testing only; not documented to customers.",
13290
- env: "PRIMITIVE_API_BASE_URL_1",
13291
- hidden: true
13292
- }),
13293
- "api-base-url-2": Flags.string({
13294
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
13295
- env: "PRIMITIVE_API_BASE_URL_2",
13839
+ "api-base-url": Flags.string({
13840
+ description: "Override the API base URL. Internal testing only; not documented to customers.",
13841
+ env: "PRIMITIVE_API_BASE_URL",
13296
13842
  hidden: true
13297
13843
  }),
13298
13844
  time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
@@ -13380,8 +13926,7 @@ function createOperationCommand(operation) {
13380
13926
  await runWithTiming(parsedFlags.time === true, async () => {
13381
13927
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
13382
13928
  apiKey: typeof parsedFlags["api-key"] === "string" ? parsedFlags["api-key"] : void 0,
13383
- apiBaseUrl1: typeof parsedFlags["api-base-url-1"] === "string" ? parsedFlags["api-base-url-1"] : void 0,
13384
- apiBaseUrl2: typeof parsedFlags["api-base-url-2"] === "string" ? parsedFlags["api-base-url-2"] : void 0,
13929
+ apiBaseUrl: typeof parsedFlags["api-base-url"] === "string" ? parsedFlags["api-base-url"] : void 0,
13385
13930
  configDir: this.config.configDir
13386
13931
  });
13387
13932
  let body;
@@ -13402,10 +13947,9 @@ function createOperationCommand(operation) {
13402
13947
  }
13403
13948
  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.`);
13404
13949
  const operationFn = sdk_gen_exports[operation.sdkName];
13405
- const targetClient = HOST_2_OPERATIONS.has(operation.sdkName) ? apiClient._sendClient : apiClient.client;
13406
13950
  const result = await operationFn({
13407
13951
  body,
13408
- client: targetClient,
13952
+ client: apiClient.client,
13409
13953
  parseAs: operation.binaryResponse ? "blob" : "auto",
13410
13954
  path: collectValues(operation.pathParams, parsedFlags),
13411
13955
  query: collectValues(operation.queryParams, parsedFlags),
@@ -13480,6 +14024,39 @@ function canonicalizeCliReferences(description) {
13480
14024
  return description.replaceAll("`primitive emails:latest`", "`primitive emails latest`").replaceAll("`primitive describe emails:get-email | jq '.responseSchema.properties'`", "`primitive describe emails:get | jq '.responseSchema.properties'`");
13481
14025
  }
13482
14026
  //#endregion
14027
+ //#region src/oclif/attachments.ts
14028
+ function readAttachmentBytes(path, readFile) {
14029
+ try {
14030
+ return Buffer.from(readFile(path));
14031
+ } catch (error) {
14032
+ const detail = error instanceof Error ? error.message : String(error);
14033
+ throw new Errors.CLIError(`Could not read --attachment ${path}: ${detail}`, { exit: 1 });
14034
+ }
14035
+ }
14036
+ function hasControlCharacter(value) {
14037
+ return Array.from(value).some((character) => {
14038
+ const code = character.charCodeAt(0);
14039
+ return code <= 31 || code >= 127 && code <= 159;
14040
+ });
14041
+ }
14042
+ function validateAttachmentFilename(path, filename) {
14043
+ if (!filename) throw new Errors.CLIError(`Could not derive an attachment filename from ${path}. Pass a file path.`, { exit: 1 });
14044
+ if (hasControlCharacter(filename)) throw new Errors.CLIError(`Attachment filename ${filename} contains control characters.`, { exit: 1 });
14045
+ }
14046
+ function readAttachmentFiles(paths, readFile = readFileSync) {
14047
+ if (!paths || paths.length === 0) return void 0;
14048
+ return paths.map((path) => {
14049
+ const filename = basename(path);
14050
+ validateAttachmentFilename(path, filename);
14051
+ const bytes = readAttachmentBytes(path, readFile);
14052
+ if (bytes.length === 0) throw new Errors.CLIError(`Attachment file ${path} is empty. Attachments must contain at least one byte.`, { exit: 1 });
14053
+ return {
14054
+ content_base64: bytes.toString("base64"),
14055
+ filename
14056
+ };
14057
+ });
14058
+ }
14059
+ //#endregion
13483
14060
  //#region src/oclif/outbound-defaults.ts
13484
14061
  const SUBJECT_MAX_LENGTH = 200;
13485
14062
  function deriveSubject(body) {
@@ -13630,6 +14207,30 @@ async function readStdinToString(missingMessage = "No message provided. Pass the
13630
14207
  for await (const chunk of process.stdin) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
13631
14208
  return Buffer.concat(chunks).toString("utf8");
13632
14209
  }
14210
+ function chatColor(color, text) {
14211
+ return ux.colorize(color, text);
14212
+ }
14213
+ function chatCommandText(command) {
14214
+ return chatColor("cyan", command);
14215
+ }
14216
+ function chatDetailLine(line) {
14217
+ return chatColor("dim", line);
14218
+ }
14219
+ function chatFailureText(message) {
14220
+ return chatColor("red", message);
14221
+ }
14222
+ function chatHeading(text) {
14223
+ return chatColor("bold", text);
14224
+ }
14225
+ function chatNoticeText(message) {
14226
+ return chatColor("yellow", message);
14227
+ }
14228
+ function chatProgressText(message) {
14229
+ return chatColor("cyan", message);
14230
+ }
14231
+ function chatSuccessText(message) {
14232
+ return chatColor("greenBright", message);
14233
+ }
13633
14234
  var ChatProgressIndicator = class {
13634
14235
  currentMessage = null;
13635
14236
  frameIndex = 0;
@@ -13650,7 +14251,7 @@ var ChatProgressIndicator = class {
13650
14251
  this.timer.unref?.();
13651
14252
  return;
13652
14253
  }
13653
- this.stream.write(`${message}\n`);
14254
+ this.stream.write(`${chatProgressText(message)}\n`);
13654
14255
  }
13655
14256
  update(message, options = {}) {
13656
14257
  this.currentMessage = message;
@@ -13663,10 +14264,10 @@ var ChatProgressIndicator = class {
13663
14264
  return;
13664
14265
  }
13665
14266
  this.stopTimer();
13666
- this.stream.write(`${message}\n`);
14267
+ this.stream.write(`${chatProgressText(message)}\n`);
13667
14268
  if (options.heartbeatMs !== void 0) {
13668
14269
  this.timer = setInterval(() => {
13669
- this.stream.write(`${formatWaitingHeartbeat(message, this.now() - this.startedAt, options.timeoutSeconds)}\n`);
14270
+ this.stream.write(`${chatProgressText(formatWaitingHeartbeat(message, this.now() - this.startedAt, options.timeoutSeconds))}\n`);
13670
14271
  }, options.heartbeatMs);
13671
14272
  this.timer.unref?.();
13672
14273
  }
@@ -13675,17 +14276,17 @@ var ChatProgressIndicator = class {
13675
14276
  if (this.stream.isTTY) {
13676
14277
  const currentMessage = this.currentMessage;
13677
14278
  this.clearLine();
13678
- this.stream.write(`${message}\n`);
14279
+ this.stream.write(`${chatNoticeText(message)}\n`);
13679
14280
  if (currentMessage !== null && this.timer !== null) this.render(currentMessage);
13680
14281
  return;
13681
14282
  }
13682
- this.stream.write(`${message}\n`);
14283
+ this.stream.write(`${chatNoticeText(message)}\n`);
13683
14284
  }
13684
14285
  succeed(message) {
13685
- this.finish(`${message} after ${formatElapsed(this.now() - this.startedAt)}.`);
14286
+ this.finish(chatSuccessText(`${message} after ${formatElapsed(this.now() - this.startedAt)}.`));
13686
14287
  }
13687
14288
  fail(message) {
13688
- this.finish(message);
14289
+ this.finish(chatFailureText(message));
13689
14290
  }
13690
14291
  finish(message) {
13691
14292
  this.stopTimer();
@@ -13702,9 +14303,10 @@ var ChatProgressIndicator = class {
13702
14303
  ];
13703
14304
  const frame = frames[this.frameIndex % frames.length];
13704
14305
  this.frameIndex += 1;
13705
- const line = `${frame} ${message} (${formatElapsed(this.now() - this.startedAt)})`;
13706
- this.lastLineLength = Math.max(this.lastLineLength, line.length);
13707
- this.stream.write(`\r${line}`);
14306
+ const elapsed = `(${formatElapsed(this.now() - this.startedAt)})`;
14307
+ const plainLine = `${frame} ${message} ${elapsed}`;
14308
+ this.lastLineLength = Math.max(this.lastLineLength, plainLine.length);
14309
+ this.stream.write(`\r${chatProgressText(`${frame} ${message}`)} ${chatColor("dim", elapsed)}`);
13708
14310
  }
13709
14311
  clearLine() {
13710
14312
  if (this.lastLineLength > 0) {
@@ -13950,45 +14552,46 @@ function formatChatResponse(context) {
13950
14552
  const accepted = context.sent.accepted.join(", ") || context.recipient;
13951
14553
  const responseBody = resolveChatResponseBody(context.reply);
13952
14554
  const lines = [
13953
- "Reply received",
14555
+ chatSuccessText("Reply received"),
13954
14556
  "",
13955
- "Sent",
13956
- ` To: ${accepted}`,
13957
- ` From: ${context.sent.from || context.from}`,
13958
- ` Subject: ${context.subject}`,
13959
- ` Sent email id: ${context.sent.id}`,
13960
- ` Delivery status: ${context.sent.delivery_status ?? context.sent.status}`,
14557
+ chatHeading("Sent"),
14558
+ chatDetailLine(` To: ${accepted}`),
14559
+ chatDetailLine(` From: ${context.sent.from || context.from}`),
14560
+ chatDetailLine(` Subject: ${context.subject}`),
14561
+ chatDetailLine(` Sent email id: ${context.sent.id}`),
14562
+ chatColor("green", ` Delivery status: ${context.sent.delivery_status ?? context.sent.status}`),
13961
14563
  "",
13962
- "Reply",
13963
- ` Email id: ${context.reply.id}`,
13964
- ` From: ${context.reply.from_email}`,
13965
- ` To: ${context.reply.to_email}`,
13966
- ` Subject: ${context.reply.subject ?? "(no subject)"}`,
13967
- ` Received: ${context.reply.received_at}`,
13968
- ` Match: ${matchDescription(context.matchStrategy)}`
14564
+ chatHeading("Reply"),
14565
+ chatDetailLine(` Email id: ${context.reply.id}`),
14566
+ chatDetailLine(` From: ${context.reply.from_email}`),
14567
+ chatDetailLine(` To: ${context.reply.to_email}`),
14568
+ chatDetailLine(` Subject: ${context.reply.subject ?? "(no subject)"}`),
14569
+ chatDetailLine(` Received: ${context.reply.received_at}`),
14570
+ chatDetailLine(` Match: ${matchDescription(context.matchStrategy)}`)
13969
14571
  ];
13970
- if (context.reply.reply_to_sent_email_id) lines.push(` Reply to sent email id: ${context.reply.reply_to_sent_email_id}`);
13971
- if (context.reply.message_id) lines.push(` Message-Id: ${context.reply.message_id}`);
13972
- if (context.localChatId !== void 0) lines.push(` Local chat id: ${context.localChatId}`);
13973
- lines.push("", "Helpful follow-up commands", " Replace <message> before running commands that include it.", " Commands are templates; use --json for parse-safe output.", " When shown, --strict-only prefers timing out over matching the wrong reply.");
13974
- for (const { description, command } of buildChatFollowUpCommands(context)) lines.push(` ${description}:`, ` ${command}`);
13975
- lines.push("", `Response body (${responseBody.format}; use --json for parsing)`, "----- BEGIN RESPONSE -----", responseBody.body || "(empty response)", "----- END RESPONSE -----");
14572
+ if (context.reply.reply_to_sent_email_id) lines.push(chatDetailLine(` Reply to sent email id: ${context.reply.reply_to_sent_email_id}`));
14573
+ if (context.reply.message_id) lines.push(chatDetailLine(` Message-Id: ${context.reply.message_id}`));
14574
+ if (context.localChatId !== void 0) lines.push(chatDetailLine(` Local chat id: ${context.localChatId}`));
14575
+ lines.push("", chatHeading("Helpful follow-up commands"), chatDetailLine(" Replace <message> before running commands that include it."), chatDetailLine(" Commands are templates; use --json for parse-safe output."), chatDetailLine(" When shown, --strict-only prefers timing out over matching the wrong reply."));
14576
+ for (const { description, command } of buildChatFollowUpCommands(context)) lines.push(chatHeading(` ${description}:`), ` ${chatCommandText(command)}`);
14577
+ lines.push("", chatHeading(`Response body (${responseBody.format}; use --json for parsing)`), "----- BEGIN RESPONSE -----", responseBody.body || "(empty response)", "----- END RESPONSE -----");
13976
14578
  return lines.join("\n");
13977
14579
  }
13978
14580
  function formatChatRecoveryContext(context) {
14581
+ const accepted = context.sent.accepted.join(", ") || context.recipient;
13979
14582
  const lines = [
13980
14583
  "",
13981
- "Sent message context",
13982
- ` To: ${context.sent.accepted.join(", ") || context.recipient}`,
13983
- ` From: ${context.sent.from || context.from}`,
13984
- ` Subject: ${context.subject}`,
13985
- ` Sent email id: ${context.sent.id}`,
13986
- ` Delivery status: ${context.sent.delivery_status ?? context.sent.status}`,
13987
- ` Poll since: ${context.sentAtIso}`,
14584
+ chatHeading("Sent message context"),
14585
+ chatDetailLine(` To: ${accepted}`),
14586
+ chatDetailLine(` From: ${context.sent.from || context.from}`),
14587
+ chatDetailLine(` Subject: ${context.subject}`),
14588
+ chatDetailLine(` Sent email id: ${context.sent.id}`),
14589
+ chatColor("green", ` Delivery status: ${context.sent.delivery_status ?? context.sent.status}`),
14590
+ chatDetailLine(` Poll since: ${context.sentAtIso}`),
13988
14591
  "",
13989
- "Helpful recovery commands"
14592
+ chatHeading("Helpful recovery commands")
13990
14593
  ];
13991
- for (const { description, command } of buildChatRecoveryCommands(context)) lines.push(` ${description}:`, ` ${command}`);
14594
+ for (const { description, command } of buildChatRecoveryCommands(context)) lines.push(chatHeading(` ${description}:`), ` ${chatCommandText(command)}`);
13992
14595
  return lines.join("\n");
13993
14596
  }
13994
14597
  async function loadInboundEmailDetail(params) {
@@ -14081,8 +14684,10 @@ var ChatCommand = class ChatCommand extends Command {
14081
14684
  "<%= config.bin %> chat help@agent.acme.dev 'how do I rotate my API key?'",
14082
14685
  "cat error.log | <%= config.bin %> chat help@agent.acme.dev",
14083
14686
  "<%= config.bin %> chat reply 'one more thing'",
14687
+ "<%= config.bin %> chat reply 'see attached' --attachment ./report.pdf",
14084
14688
  "<%= config.bin %> chat help@agent.acme.dev --reply 'one more thing'",
14085
14689
  "<%= config.bin %> chat help@agent.acme.dev --reply 'one more thing' --reply-to-email-id <inbound-email-id>",
14690
+ "<%= config.bin %> chat help@agent.acme.dev 'can you review this?' --attachment ./report.pdf",
14086
14691
  "<%= config.bin %> chat help@agent.acme.dev 'follow up question' --json",
14087
14692
  "<%= config.bin %> chat help@agent.acme.dev 'one more thing' --timeout 300"
14088
14693
  ];
@@ -14098,14 +14703,9 @@ var ChatCommand = class ChatCommand extends Command {
14098
14703
  description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive signin` credentials)",
14099
14704
  env: "PRIMITIVE_API_KEY"
14100
14705
  }),
14101
- "api-base-url-1": Flags.string({
14706
+ "api-base-url": Flags.string({
14102
14707
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
14103
- env: "PRIMITIVE_API_BASE_URL_1",
14104
- hidden: true
14105
- }),
14106
- "api-base-url-2": Flags.string({
14107
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
14108
- env: "PRIMITIVE_API_BASE_URL_2",
14708
+ env: "PRIMITIVE_API_BASE_URL",
14109
14709
  hidden: true
14110
14710
  }),
14111
14711
  from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
@@ -14116,6 +14716,11 @@ var ChatCommand = class ChatCommand extends Command {
14116
14716
  reply: Flags.string({ description: "Reply body. Continues the latest inbound email from the recipient to your sender address; pass --reply-to-email-id for an exact thread." }),
14117
14717
  "reply-to-email-id": Flags.string({ description: "Inbound email id to continue exactly. Uses Primitive's reply endpoint, so recipient, subject, and threading headers are derived from the inbound email." }),
14118
14718
  "in-reply-to": Flags.string({ description: "Raw Message-Id of the parent email to thread a new send against. Prefer --reply-to-email-id with --reply when continuing an inbound email stored by Primitive." }),
14719
+ attachment: Flags.string({
14720
+ char: "a",
14721
+ description: "Attach a file to this chat message. Repeat --attachment to attach multiple files.",
14722
+ multiple: true
14723
+ }),
14119
14724
  "chat-local-id": Flags.integer({
14120
14725
  description: "Local chat id to update after this command succeeds. Internal plumbing for `primitive chat reply`.",
14121
14726
  hidden: true,
@@ -14159,8 +14764,7 @@ var ChatCommand = class ChatCommand extends Command {
14159
14764
  await runWithTiming(flags.time, async () => {
14160
14765
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
14161
14766
  apiKey: flags["api-key"],
14162
- apiBaseUrl1: flags["api-base-url-1"],
14163
- apiBaseUrl2: flags["api-base-url-2"],
14767
+ apiBaseUrl: flags["api-base-url"],
14164
14768
  configDir: this.config.configDir
14165
14769
  });
14166
14770
  const authFailureContext = {
@@ -14169,6 +14773,7 @@ var ChatCommand = class ChatCommand extends Command {
14169
14773
  configDir: this.config.configDir
14170
14774
  };
14171
14775
  const progress = flags.quiet ? null : new ChatProgressIndicator(process.stderr);
14776
+ const attachments = readAttachmentFiles(flags.attachment);
14172
14777
  let from;
14173
14778
  let parentReply;
14174
14779
  let subject;
@@ -14227,7 +14832,8 @@ var ChatCommand = class ChatCommand extends Command {
14227
14832
  const sendResult = parentReply !== void 0 ? await replyToEmail({
14228
14833
  body: {
14229
14834
  body_text: message,
14230
- from
14835
+ from,
14836
+ ...attachments !== void 0 ? { attachments } : {}
14231
14837
  },
14232
14838
  client: apiClient.client,
14233
14839
  path: { id: parentReply.id },
@@ -14238,9 +14844,10 @@ var ChatCommand = class ChatCommand extends Command {
14238
14844
  to: args.recipient,
14239
14845
  subject,
14240
14846
  body_text: message,
14241
- ...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {}
14847
+ ...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {},
14848
+ ...attachments !== void 0 ? { attachments } : {}
14242
14849
  },
14243
- client: apiClient._sendClient,
14850
+ client: apiClient.client,
14244
14851
  responseStyle: "fields"
14245
14852
  });
14246
14853
  if (sendResult.error) {
@@ -14353,6 +14960,7 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
14353
14960
  "<%= config.bin %> chat reply 'one more thing'",
14354
14961
  "<%= config.bin %> chat reply 0 'one more thing'",
14355
14962
  "<%= config.bin %> chat reply --id 0 'one more thing'",
14963
+ "<%= config.bin %> chat reply 'see attached' --attachment ./report.pdf",
14356
14964
  "cat follow-up.txt | <%= config.bin %> chat reply"
14357
14965
  ];
14358
14966
  static args = {
@@ -14364,14 +14972,9 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
14364
14972
  description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive signin` credentials)",
14365
14973
  env: "PRIMITIVE_API_KEY"
14366
14974
  }),
14367
- "api-base-url-1": Flags.string({
14975
+ "api-base-url": Flags.string({
14368
14976
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
14369
- env: "PRIMITIVE_API_BASE_URL_1",
14370
- hidden: true
14371
- }),
14372
- "api-base-url-2": Flags.string({
14373
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
14374
- env: "PRIMITIVE_API_BASE_URL_2",
14977
+ env: "PRIMITIVE_API_BASE_URL",
14375
14978
  hidden: true
14376
14979
  }),
14377
14980
  id: Flags.integer({
@@ -14389,6 +14992,11 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
14389
14992
  min: 1
14390
14993
  }),
14391
14994
  "strict-only": Flags.boolean({ description: "Disable the time-window fallback. If the active chat was saved from a strict match, this is already the default." }),
14995
+ attachment: Flags.string({
14996
+ char: "a",
14997
+ description: "Attach a file to the reply. Repeat --attachment to attach multiple files.",
14998
+ multiple: true
14999
+ }),
14392
15000
  interval: Flags.integer({
14393
15001
  description: "Seconds between polls while waiting for the reply.",
14394
15002
  min: 1
@@ -14431,10 +15039,10 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
14431
15039
  String(state.local_id)
14432
15040
  ];
14433
15041
  if (flags["api-key"] !== void 0) argv.push("--api-key", flags["api-key"]);
14434
- if (flags["api-base-url-1"] !== void 0) argv.push("--api-base-url-1", flags["api-base-url-1"]);
14435
- if (flags["api-base-url-2"] !== void 0) argv.push("--api-base-url-2", flags["api-base-url-2"]);
15042
+ if (flags["api-base-url"] !== void 0) argv.push("--api-base-url", flags["api-base-url"]);
14436
15043
  if (flags.json) argv.push("--json");
14437
15044
  if (flags.quiet) argv.push("--quiet");
15045
+ for (const attachment of flags.attachment ?? []) argv.push("--attachment", attachment);
14438
15046
  if (state.strict_only || flags["strict-only"]) argv.push("--strict-only");
14439
15047
  if (flags.time) argv.push("--time");
14440
15048
  await ChatCommand.run(argv, { root: this.config.root });
@@ -14546,8 +15154,7 @@ function upsertCliEnvironmentAndClearCredentialsIfSwitched(params) {
14546
15154
  const previousActiveEnvironment = resolveConfigEnvironment(previousConfig);
14547
15155
  const previousEnvironment = previousActiveEnvironment?.name ?? null;
14548
15156
  const config = upsertCliEnvironment({
14549
- apiBaseUrl1: params.apiBaseUrl1,
14550
- apiBaseUrl2: params.apiBaseUrl2,
15157
+ apiBaseUrl: params.apiBaseUrl,
14551
15158
  config: previousConfig,
14552
15159
  environmentName: params.environmentName,
14553
15160
  headers: params.headers,
@@ -14555,7 +15162,7 @@ function upsertCliEnvironmentAndClearCredentialsIfSwitched(params) {
14555
15162
  });
14556
15163
  const activeEnvironment = resolveConfigEnvironment(config);
14557
15164
  const environment = activeEnvironment?.name ?? null;
14558
- const shouldClearCredentials = existsSync(credentialsPath(params.configDir)) && (previousEnvironment !== environment || previousActiveEnvironment?.config.api_base_url_1 !== activeEnvironment?.config.api_base_url_1);
15165
+ const shouldClearCredentials = existsSync(credentialsPath(params.configDir)) && (previousEnvironment !== environment || previousActiveEnvironment?.config.api_base_url !== activeEnvironment?.config.api_base_url);
14559
15166
  let removedCredentials = false;
14560
15167
  if (shouldClearCredentials) {
14561
15168
  const releaseLock = acquireCliCredentialsLock(params.configDir);
@@ -14605,10 +15212,9 @@ var ConfigSetCommand = class ConfigSetCommand extends Command {
14605
15212
  static flags = {
14606
15213
  environment: Flags.string({
14607
15214
  char: "e",
14608
- description: "Environment name to create or update"
15215
+ description: "Environment name to create or update. Defaults to the active environment, or default when none is active."
14609
15216
  }),
14610
- "api-base-url-1": Flags.string({ description: "Primary API base URL" }),
14611
- "api-base-url-2": Flags.string({ description: "Attachments-supporting API base URL" }),
15217
+ "api-base-url": Flags.string({ description: "API base URL" }),
14612
15218
  header: Flags.string({
14613
15219
  description: "Request header in name=value form. Repeatable.",
14614
15220
  multiple: true
@@ -14621,10 +15227,9 @@ var ConfigSetCommand = class ConfigSetCommand extends Command {
14621
15227
  async run() {
14622
15228
  const { flags } = await this.parse(ConfigSetCommand);
14623
15229
  const headers = flags.header ?? [];
14624
- 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 });
15230
+ 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 });
14625
15231
  const { environment, removedCredentials } = upsertCliEnvironmentAndClearCredentialsIfSwitched({
14626
- apiBaseUrl1: flags["api-base-url-1"],
14627
- apiBaseUrl2: flags["api-base-url-2"],
15232
+ apiBaseUrl: flags["api-base-url"],
14628
15233
  configDir: this.config.configDir,
14629
15234
  environmentName: flags.environment,
14630
15235
  headers,
@@ -14672,8 +15277,7 @@ var ConfigListCommand = class ConfigListCommand extends Command {
14672
15277
  const active = activeEnvironment === name ? "*" : " ";
14673
15278
  const headerNames = Object.keys(environment.headers ?? {});
14674
15279
  this.log(`${active} ${name}`);
14675
- if (environment.api_base_url_1) this.log(` api_base_url_1: ${environment.api_base_url_1}`);
14676
- if (environment.api_base_url_2) this.log(` api_base_url_2: ${environment.api_base_url_2}`);
15280
+ if (environment.api_base_url) this.log(` api_base_url: ${environment.api_base_url}`);
14677
15281
  this.log(` headers: ${headerNames.length > 0 ? headerNames.join(", ") : "(none)"}`);
14678
15282
  }
14679
15283
  }
@@ -14845,7 +15449,7 @@ async function checkAccount(client) {
14845
15449
  } catch (error) {
14846
15450
  const code = error instanceof Error && error.cause && typeof error.cause === "object" && typeof error.cause.code === "string" ? error.cause.code : void 0;
14847
15451
  const message = error instanceof Error ? error.message : String(error);
14848
- 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.";
15452
+ 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.";
14849
15453
  return {
14850
15454
  outcome: {
14851
15455
  status: "fail",
@@ -14899,14 +15503,9 @@ var DoctorCommand = class DoctorCommand extends Command {
14899
15503
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
14900
15504
  env: "PRIMITIVE_API_KEY"
14901
15505
  }),
14902
- "api-base-url-1": Flags.string({
15506
+ "api-base-url": Flags.string({
14903
15507
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
14904
- env: "PRIMITIVE_API_BASE_URL_1",
14905
- hidden: true
14906
- }),
14907
- "api-base-url-2": Flags.string({
14908
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
14909
- env: "PRIMITIVE_API_BASE_URL_2",
15508
+ env: "PRIMITIVE_API_BASE_URL",
14910
15509
  hidden: true
14911
15510
  })
14912
15511
  };
@@ -14932,8 +15531,7 @@ var DoctorCommand = class DoctorCommand extends Command {
14932
15531
  if (apiKeyCheck.status !== "fail") {
14933
15532
  const { apiClient, auth } = await createAuthenticatedCliApiClient({
14934
15533
  apiKey: flags["api-key"],
14935
- apiBaseUrl1: flags["api-base-url-1"],
14936
- apiBaseUrl2: flags["api-base-url-2"],
15534
+ apiBaseUrl: flags["api-base-url"],
14937
15535
  configDir: this.config.configDir
14938
15536
  });
14939
15537
  if (auth.apiKey !== void 0) {
@@ -15019,14 +15617,9 @@ var DomainsZoneFileCommand = class DomainsZoneFileCommand extends Command {
15019
15617
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
15020
15618
  env: "PRIMITIVE_API_KEY"
15021
15619
  }),
15022
- "api-base-url-1": Flags.string({
15620
+ "api-base-url": Flags.string({
15023
15621
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
15024
- env: "PRIMITIVE_API_BASE_URL_1",
15025
- hidden: true
15026
- }),
15027
- "api-base-url-2": Flags.string({
15028
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
15029
- env: "PRIMITIVE_API_BASE_URL_2",
15622
+ env: "PRIMITIVE_API_BASE_URL",
15030
15623
  hidden: true
15031
15624
  }),
15032
15625
  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`." }),
@@ -15045,8 +15638,7 @@ var DomainsZoneFileCommand = class DomainsZoneFileCommand extends Command {
15045
15638
  await runWithTiming(flags.time, async () => {
15046
15639
  const { apiClient, auth, baseUrlOverridden, requestConfig } = await createAuthenticatedCliApiClient({
15047
15640
  apiKey: flags["api-key"],
15048
- apiBaseUrl1: flags["api-base-url-1"],
15049
- apiBaseUrl2: flags["api-base-url-2"],
15641
+ apiBaseUrl: flags["api-base-url"],
15050
15642
  configDir: this.config.configDir
15051
15643
  });
15052
15644
  let domainId = flags.id;
@@ -15073,7 +15665,7 @@ var DomainsZoneFileCommand = class DomainsZoneFileCommand extends Command {
15073
15665
  if (!domainId) throw new Errors.CLIError("Could not resolve a domain id.", { exit: 1 });
15074
15666
  let response;
15075
15667
  try {
15076
- response = await fetch(zoneFileUrl(requestConfig.resolvedApiBaseUrl1, domainId, flags["outbound-only"]), { headers: {
15668
+ response = await fetch(zoneFileUrl(requestConfig.resolvedApiBaseUrl, domainId, flags["outbound-only"]), { headers: {
15077
15669
  ...requestConfig.headers ?? {},
15078
15670
  ...auth.apiKey ? { authorization: `Bearer ${auth.apiKey}` } : {}
15079
15671
  } });
@@ -15108,14 +15700,14 @@ var DomainsZoneFileCommand = class DomainsZoneFileCommand extends Command {
15108
15700
  };
15109
15701
  //#endregion
15110
15702
  //#region src/oclif/commands/emails-latest.ts
15111
- const DEFAULT_LIMIT = 10;
15112
- const MAX_LIMIT = 100;
15703
+ const DEFAULT_LIMIT$1 = 10;
15704
+ const MAX_LIMIT$1 = 100;
15113
15705
  const SUBJECT_DISPLAY_WIDTH = 50;
15114
15706
  const ADDRESS_DISPLAY_WIDTH = 32;
15115
15707
  const ID_DISPLAY_WIDTH_SHORT = 8;
15116
15708
  const ID_DISPLAY_WIDTH_FULL = 36;
15117
15709
  const RECEIVED_DISPLAY_WIDTH = 19;
15118
- function truncate$1(value, width) {
15710
+ function truncate$2(value, width) {
15119
15711
  if (value.length <= width) return value.padEnd(width);
15120
15712
  return `${value.slice(0, width - 3)}...`;
15121
15713
  }
@@ -15129,10 +15721,10 @@ function formatReceivedAt(value) {
15129
15721
  function pickIdWidth(isTty) {
15130
15722
  return isTty ? ID_DISPLAY_WIDTH_SHORT : ID_DISPLAY_WIDTH_FULL;
15131
15723
  }
15132
- function formatRow(email, idWidth) {
15133
- 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)}`;
15724
+ function formatRow$1(email, idWidth) {
15725
+ 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)}`;
15134
15726
  }
15135
- function formatHeader(idWidth) {
15727
+ function formatHeader$1(idWidth) {
15136
15728
  return `${"ID".padEnd(idWidth)} ${"RECEIVED (UTC)".padEnd(RECEIVED_DISPLAY_WIDTH)} ${"FROM".padEnd(ADDRESS_DISPLAY_WIDTH)} ${"TO".padEnd(ADDRESS_DISPLAY_WIDTH)} SUBJECT`;
15137
15729
  }
15138
15730
  var EmailsLatestCommand = class EmailsLatestCommand extends Command {
@@ -15153,21 +15745,16 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
15153
15745
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
15154
15746
  env: "PRIMITIVE_API_KEY"
15155
15747
  }),
15156
- "api-base-url-1": Flags.string({
15748
+ "api-base-url": Flags.string({
15157
15749
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
15158
- env: "PRIMITIVE_API_BASE_URL_1",
15159
- hidden: true
15160
- }),
15161
- "api-base-url-2": Flags.string({
15162
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
15163
- env: "PRIMITIVE_API_BASE_URL_2",
15750
+ env: "PRIMITIVE_API_BASE_URL",
15164
15751
  hidden: true
15165
15752
  }),
15166
15753
  limit: Flags.integer({
15167
- description: `Number of rows to print (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}).`,
15168
- default: DEFAULT_LIMIT,
15754
+ description: `Number of rows to print (1-${MAX_LIMIT$1}, default ${DEFAULT_LIMIT$1}).`,
15755
+ default: DEFAULT_LIMIT$1,
15169
15756
  min: 1,
15170
- max: MAX_LIMIT
15757
+ max: MAX_LIMIT$1
15171
15758
  }),
15172
15759
  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." }),
15173
15760
  time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
@@ -15177,8 +15764,7 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
15177
15764
  await runWithTiming(flags.time, async () => {
15178
15765
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
15179
15766
  apiKey: flags["api-key"],
15180
- apiBaseUrl1: flags["api-base-url-1"],
15181
- apiBaseUrl2: flags["api-base-url-2"],
15767
+ apiBaseUrl: flags["api-base-url"],
15182
15768
  configDir: this.config.configDir
15183
15769
  });
15184
15770
  const result = await listEmails({
@@ -15209,8 +15795,8 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
15209
15795
  return;
15210
15796
  }
15211
15797
  const idWidth = pickIdWidth(Boolean(process.stdout.isTTY));
15212
- process.stderr.write(`${formatHeader(idWidth)}\n`);
15213
- for (const row of rows) this.log(formatRow(row, idWidth));
15798
+ process.stderr.write(`${formatHeader$1(idWidth)}\n`);
15799
+ for (const row of rows) this.log(formatRow$1(row, idWidth));
15214
15800
  });
15215
15801
  }
15216
15802
  };
@@ -15233,14 +15819,9 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
15233
15819
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
15234
15820
  env: "PRIMITIVE_API_KEY"
15235
15821
  }),
15236
- "api-base-url-1": Flags.string({
15822
+ "api-base-url": Flags.string({
15237
15823
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
15238
- env: "PRIMITIVE_API_BASE_URL_1",
15239
- hidden: true
15240
- }),
15241
- "api-base-url-2": Flags.string({
15242
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
15243
- env: "PRIMITIVE_API_BASE_URL_2",
15824
+ env: "PRIMITIVE_API_BASE_URL",
15244
15825
  hidden: true
15245
15826
  }),
15246
15827
  body: Flags.string({ description: "Full-text body filter" }),
@@ -15284,8 +15865,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
15284
15865
  const { flags } = await this.parse(EmailsWaitCommand);
15285
15866
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
15286
15867
  apiKey: flags["api-key"],
15287
- apiBaseUrl1: flags["api-base-url-1"],
15288
- apiBaseUrl2: flags["api-base-url-2"],
15868
+ apiBaseUrl: flags["api-base-url"],
15289
15869
  configDir: this.config.configDir
15290
15870
  });
15291
15871
  let since;
@@ -15327,10 +15907,10 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
15327
15907
  for (const email of collectNewAcceptedEmails(page.rows, seenIds)) {
15328
15908
  if (flags.table) {
15329
15909
  if (!headerPrinted) {
15330
- process.stderr.write(`${formatHeader(idWidth)}\n`);
15910
+ process.stderr.write(`${formatHeader$1(idWidth)}\n`);
15331
15911
  headerPrinted = true;
15332
15912
  }
15333
- this.log(formatRow(email, idWidth));
15913
+ this.log(formatRow$1(email, idWidth));
15334
15914
  } else this.log(JSON.stringify(email));
15335
15915
  matched += 1;
15336
15916
  if (matched >= flags.number) return;
@@ -15361,14 +15941,9 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
15361
15941
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
15362
15942
  env: "PRIMITIVE_API_KEY"
15363
15943
  }),
15364
- "api-base-url-1": Flags.string({
15944
+ "api-base-url": Flags.string({
15365
15945
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
15366
- env: "PRIMITIVE_API_BASE_URL_1",
15367
- hidden: true
15368
- }),
15369
- "api-base-url-2": Flags.string({
15370
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
15371
- env: "PRIMITIVE_API_BASE_URL_2",
15946
+ env: "PRIMITIVE_API_BASE_URL",
15372
15947
  hidden: true
15373
15948
  }),
15374
15949
  body: Flags.string({ description: "Full-text body filter" }),
@@ -15408,8 +15983,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
15408
15983
  const { flags } = await this.parse(EmailsWatchCommand);
15409
15984
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
15410
15985
  apiKey: flags["api-key"],
15411
- apiBaseUrl1: flags["api-base-url-1"],
15412
- apiBaseUrl2: flags["api-base-url-2"],
15986
+ apiBaseUrl: flags["api-base-url"],
15413
15987
  configDir: this.config.configDir
15414
15988
  });
15415
15989
  let since;
@@ -15452,10 +16026,10 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
15452
16026
  if (flags.jsonl) this.log(JSON.stringify(email));
15453
16027
  else {
15454
16028
  if (!headerPrinted) {
15455
- process.stderr.write(`${formatHeader(idWidth)}\n`);
16029
+ process.stderr.write(`${formatHeader$1(idWidth)}\n`);
15456
16030
  headerPrinted = true;
15457
16031
  }
15458
- this.log(formatRow(email, idWidth));
16032
+ this.log(formatRow$1(email, idWidth));
15459
16033
  }
15460
16034
  printed += 1;
15461
16035
  if (flags.number && printed >= flags.number) return;
@@ -16154,14 +16728,9 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
16154
16728
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
16155
16729
  env: "PRIMITIVE_API_KEY"
16156
16730
  }),
16157
- "api-base-url-1": Flags.string({
16731
+ "api-base-url": Flags.string({
16158
16732
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
16159
- env: "PRIMITIVE_API_BASE_URL_1",
16160
- hidden: true
16161
- }),
16162
- "api-base-url-2": Flags.string({
16163
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
16164
- env: "PRIMITIVE_API_BASE_URL_2",
16733
+ env: "PRIMITIVE_API_BASE_URL",
16165
16734
  hidden: true
16166
16735
  }),
16167
16736
  name: Flags.string({
@@ -16239,8 +16808,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
16239
16808
  emitRawSendMailFetchWarning(code, (chunk) => process.stderr.write(chunk));
16240
16809
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
16241
16810
  apiKey: flags["api-key"],
16242
- apiBaseUrl1: flags["api-base-url-1"],
16243
- apiBaseUrl2: flags["api-base-url-2"],
16811
+ apiBaseUrl: flags["api-base-url"],
16244
16812
  configDir: this.config.configDir
16245
16813
  });
16246
16814
  const authFailureContext = {
@@ -16354,8 +16922,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
16354
16922
  }
16355
16923
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
16356
16924
  apiKey: flags["api-key"],
16357
- apiBaseUrl1: flags["api-base-url-1"],
16358
- apiBaseUrl2: flags["api-base-url-2"],
16925
+ apiBaseUrl: flags["api-base-url"],
16359
16926
  configDir: this.config.configDir
16360
16927
  });
16361
16928
  const authFailureContext = {
@@ -16444,8 +17011,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
16444
17011
  name: "Primitive Team",
16445
17012
  url: "https://primitive.dev"
16446
17013
  };
16447
- const SDK_VERSION_RANGE = "^0.34.0";
16448
- const CLI_VERSION_RANGE = "^0.34.0";
17014
+ const SDK_VERSION_RANGE = "^0.35.1";
17015
+ const CLI_VERSION_RANGE = "^0.35.1";
16449
17016
  const ESBUILD_VERSION_RANGE = "^0.27.0";
16450
17017
  function renderHandler() {
16451
17018
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -16602,7 +17169,7 @@ export default {
16602
17169
 
16603
17170
  const client = createPrimitiveClient({
16604
17171
  apiKey: env.PRIMITIVE_API_KEY,
16605
- apiBaseUrl1: env.PRIMITIVE_API_BASE_URL,
17172
+ apiBaseUrl: env.PRIMITIVE_API_BASE_URL,
16606
17173
  });
16607
17174
 
16608
17175
  // To add an LLM or another API, store its key as a Function secret.
@@ -16626,7 +17193,7 @@ export default {
16626
17193
  // route "support@" to a ticketing flow and "sales@" to a lead
16627
17194
  // capture flow before calling client.reply.
16628
17195
 
16629
- // client.reply routes through POST /api/v1/emails/{id}/reply
17196
+ // client.reply routes through POST /v1/emails/{id}/reply
16630
17197
  // (NOT /send-mail) so the server derives recipients, the
16631
17198
  // \`Re: <parent>\` subject, threading headers, and the
16632
17199
  // in_reply_to_email_id foreign key automatically. The FK is
@@ -17007,14 +17574,9 @@ var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
17007
17574
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
17008
17575
  env: "PRIMITIVE_API_KEY"
17009
17576
  }),
17010
- "api-base-url-1": Flags.string({
17577
+ "api-base-url": Flags.string({
17011
17578
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
17012
- env: "PRIMITIVE_API_BASE_URL_1",
17013
- hidden: true
17014
- }),
17015
- "api-base-url-2": Flags.string({
17016
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
17017
- env: "PRIMITIVE_API_BASE_URL_2",
17579
+ env: "PRIMITIVE_API_BASE_URL",
17018
17580
  hidden: true
17019
17581
  }),
17020
17582
  id: Flags.string({
@@ -17045,8 +17607,7 @@ var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
17045
17607
  await runWithTiming(flags.time, async () => {
17046
17608
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
17047
17609
  apiKey: flags["api-key"],
17048
- apiBaseUrl1: flags["api-base-url-1"],
17049
- apiBaseUrl2: flags["api-base-url-2"],
17610
+ apiBaseUrl: flags["api-base-url"],
17050
17611
  configDir: this.config.configDir
17051
17612
  });
17052
17613
  const seenIds = /* @__PURE__ */ new Set();
@@ -17205,14 +17766,9 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
17205
17766
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
17206
17767
  env: "PRIMITIVE_API_KEY"
17207
17768
  }),
17208
- "api-base-url-1": Flags.string({
17769
+ "api-base-url": Flags.string({
17209
17770
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
17210
- env: "PRIMITIVE_API_BASE_URL_1",
17211
- hidden: true
17212
- }),
17213
- "api-base-url-2": Flags.string({
17214
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
17215
- env: "PRIMITIVE_API_BASE_URL_2",
17771
+ env: "PRIMITIVE_API_BASE_URL",
17216
17772
  hidden: true
17217
17773
  }),
17218
17774
  id: Flags.string({
@@ -17281,8 +17837,7 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
17281
17837
  emitRawSendMailFetchWarning(code, (chunk) => process.stderr.write(chunk));
17282
17838
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
17283
17839
  apiKey: flags["api-key"],
17284
- apiBaseUrl1: flags["api-base-url-1"],
17285
- apiBaseUrl2: flags["api-base-url-2"],
17840
+ apiBaseUrl: flags["api-base-url"],
17286
17841
  configDir: this.config.configDir
17287
17842
  });
17288
17843
  const authFailureContext = {
@@ -17465,14 +18020,9 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
17465
18020
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
17466
18021
  env: "PRIMITIVE_API_KEY"
17467
18022
  }),
17468
- "api-base-url-1": Flags.string({
18023
+ "api-base-url": Flags.string({
17469
18024
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
17470
- env: "PRIMITIVE_API_BASE_URL_1",
17471
- hidden: true
17472
- }),
17473
- "api-base-url-2": Flags.string({
17474
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
17475
- env: "PRIMITIVE_API_BASE_URL_2",
18025
+ env: "PRIMITIVE_API_BASE_URL",
17476
18026
  hidden: true
17477
18027
  }),
17478
18028
  id: Flags.string({
@@ -17496,8 +18046,7 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
17496
18046
  await runWithTiming(flags.time, async () => {
17497
18047
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
17498
18048
  apiKey: flags["api-key"],
17499
- apiBaseUrl1: flags["api-base-url-1"],
17500
- apiBaseUrl2: flags["api-base-url-2"],
18049
+ apiBaseUrl: flags["api-base-url"],
17501
18050
  configDir: this.config.configDir
17502
18051
  });
17503
18052
  const authFailureContext = {
@@ -17694,14 +18243,9 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
17694
18243
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
17695
18244
  env: "PRIMITIVE_API_KEY"
17696
18245
  }),
17697
- "api-base-url-1": Flags.string({
17698
- description: API_BASE_URL_1_FLAG_DESCRIPTION,
17699
- env: "PRIMITIVE_API_BASE_URL_1",
17700
- hidden: true
17701
- }),
17702
- "api-base-url-2": Flags.string({
17703
- description: API_BASE_URL_2_FLAG_DESCRIPTION,
17704
- env: "PRIMITIVE_API_BASE_URL_2",
18246
+ "api-base-url": Flags.string({
18247
+ description: API_BASE_URL_FLAG_DESCRIPTION,
18248
+ env: "PRIMITIVE_API_BASE_URL",
17705
18249
  hidden: true
17706
18250
  }),
17707
18251
  id: Flags.string({
@@ -17729,8 +18273,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
17729
18273
  const shouldShowSends = flags["show-sends"];
17730
18274
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
17731
18275
  apiKey: flags["api-key"],
17732
- apiBaseUrl1: flags["api-base-url-1"],
17733
- apiBaseUrl2: flags["api-base-url-2"],
18276
+ apiBaseUrl: flags["api-base-url"],
17734
18277
  configDir: this.config.configDir
17735
18278
  });
17736
18279
  await runWithTiming(flags.time, async () => {
@@ -17818,6 +18361,7 @@ const DOMAIN_DISPLAY_WIDTH = 34;
17818
18361
  const STATUS_DISPLAY_WIDTH = 12;
17819
18362
  const BOOL_DISPLAY_WIDTH = 7;
17820
18363
  const NUM_DISPLAY_WIDTH = 6;
18364
+ const DEFAULT_PRIMITIVE_LOCAL_PART = "agent";
17821
18365
  function plural(count, singular, pluralValue = `${singular}s`) {
17822
18366
  return `${count} ${count === 1 ? singular : pluralValue}`;
17823
18367
  }
@@ -17840,7 +18384,7 @@ function formatInboxDate(value) {
17840
18384
  const pad = (n) => String(n).padStart(2, "0");
17841
18385
  return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())} UTC`;
17842
18386
  }
17843
- function truncate(value, width) {
18387
+ function truncate$1(value, width) {
17844
18388
  if (value.length <= width) return value.padEnd(width);
17845
18389
  return `${value.slice(0, width - 3)}...`;
17846
18390
  }
@@ -17853,6 +18397,14 @@ function domainSummary(domain) {
17853
18397
  default: return `${domain.domain} has status ${String(domain.status)}.`;
17854
18398
  }
17855
18399
  }
18400
+ function findSuggestedPrimitiveAddress(domains) {
18401
+ const domain = domains.find((entry) => entry.managed && entry.active && entry.receiving_ready);
18402
+ if (!domain) return null;
18403
+ return {
18404
+ address: `${DEFAULT_PRIMITIVE_LOCAL_PART}@${domain.domain}`,
18405
+ domain: domain.domain
18406
+ };
18407
+ }
17856
18408
  function focusInboxStatus(status, domainName) {
17857
18409
  const normalized = domainName.toLowerCase();
17858
18410
  const domain = status.domains.find((entry) => entry.domain.toLowerCase() === normalized);
@@ -17882,7 +18434,7 @@ function formatDomainHeader() {
17882
18434
  }
17883
18435
  function formatDomainRow(domain) {
17884
18436
  return [
17885
- truncate(domain.domain, DOMAIN_DISPLAY_WIDTH),
18437
+ truncate$1(domain.domain, DOMAIN_DISPLAY_WIDTH),
17886
18438
  statusText(domain.status).padEnd(STATUS_DISPLAY_WIDTH),
17887
18439
  yesNo(domain.receiving_ready).padEnd(BOOL_DISPLAY_WIDTH),
17888
18440
  yesNo(domain.processing_ready).padEnd(BOOL_DISPLAY_WIDTH),
@@ -17899,12 +18451,14 @@ function formatInboxStatus(status) {
17899
18451
  "",
17900
18452
  "Domains"
17901
18453
  ];
18454
+ const suggestedAddress = findSuggestedPrimitiveAddress(status.domains);
17902
18455
  if (status.domains.length === 0) lines.push("No domains configured.");
17903
18456
  else {
17904
18457
  lines.push(formatDomainHeader());
17905
18458
  for (const domain of status.domains) lines.push(formatDomainRow(domain));
17906
18459
  }
17907
18460
  lines.push("", `Endpoints: ${status.endpoints.enabled}/${status.endpoints.total} enabled (${status.endpoints.fallback_enabled} fallback, ${status.endpoints.domain_scoped_enabled} domain-scoped, ${status.endpoints.function_enabled} function)`, `Functions: ${status.functions.deployed}/${status.functions.total} deployed (${status.functions.pending} pending, ${status.functions.failed} failed)`, `Recent inbound: ${plural(status.recent_emails.total, "email")} latest ${formatInboxDate(status.recent_emails.latest_received_at)}`);
18461
+ if (suggestedAddress) lines.push("", `Primitive address: ${suggestedAddress.address}`, ` Any local-part at ${suggestedAddress.domain} can receive mail.`, ` Try: primitive send --to ${suggestedAddress.address} --subject "hello" --body "test"`);
17908
18462
  if (status.next_actions.length > 0) {
17909
18463
  lines.push("", "Next actions");
17910
18464
  for (const action of status.next_actions) lines.push(formatNextAction(action));
@@ -17926,14 +18480,9 @@ var InboxStatusCommand = class InboxStatusCommand extends Command {
17926
18480
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
17927
18481
  env: "PRIMITIVE_API_KEY"
17928
18482
  }),
17929
- "api-base-url-1": Flags.string({
17930
- description: API_BASE_URL_1_FLAG_DESCRIPTION,
17931
- env: "PRIMITIVE_API_BASE_URL_1",
17932
- hidden: true
17933
- }),
17934
- "api-base-url-2": Flags.string({
17935
- description: API_BASE_URL_2_FLAG_DESCRIPTION,
17936
- env: "PRIMITIVE_API_BASE_URL_2",
18483
+ "api-base-url": Flags.string({
18484
+ description: API_BASE_URL_FLAG_DESCRIPTION,
18485
+ env: "PRIMITIVE_API_BASE_URL",
17937
18486
  hidden: true
17938
18487
  }),
17939
18488
  domain: Flags.string({ description: "Focus domain readiness and recent email fields on one domain returned by the inbox status API." }),
@@ -17945,8 +18494,7 @@ var InboxStatusCommand = class InboxStatusCommand extends Command {
17945
18494
  await runWithTiming(flags.time, async () => {
17946
18495
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
17947
18496
  apiKey: flags["api-key"],
17948
- apiBaseUrl1: flags["api-base-url-1"],
17949
- apiBaseUrl2: flags["api-base-url-2"],
18497
+ apiBaseUrl: flags["api-base-url"],
17950
18498
  configDir: this.config.configDir
17951
18499
  });
17952
18500
  const result = await getInboxStatus({
@@ -17981,6 +18529,174 @@ var InboxStatusCommand = class InboxStatusCommand extends Command {
17981
18529
  }
17982
18530
  };
17983
18531
  //#endregion
18532
+ //#region src/oclif/commands/inbox-setup.ts
18533
+ const DEFAULT_FUNCTION_NAME = "inbound-reply";
18534
+ const DEFAULT_LOCAL_PART = "inbox";
18535
+ const FUNCTION_ID_PLACEHOLDER = "<function-id>";
18536
+ function firstUsableManagedDomain(status) {
18537
+ return status.domains.find((domain) => domain.managed && domain.receiving_ready && domain.active) ?? status.domains.find((domain) => domain.managed && domain.receiving_ready) ?? null;
18538
+ }
18539
+ function buildInboxSetupCommands(functionName = DEFAULT_FUNCTION_NAME) {
18540
+ return {
18541
+ scaffold: [
18542
+ `primitive functions init ${functionName}`,
18543
+ `cd ${functionName}`,
18544
+ "npm install",
18545
+ "npm run build",
18546
+ `primitive functions deploy --name ${functionName} --file ./dist/handler.js --wait`,
18547
+ `primitive functions test --id ${FUNCTION_ID_PLACEHOLDER} --wait --show-sends`
18548
+ ],
18549
+ logs: `primitive functions logs --id ${FUNCTION_ID_PLACEHOLDER}`,
18550
+ status: "primitive inbox status"
18551
+ };
18552
+ }
18553
+ function buildInboxSetupProof(commands) {
18554
+ return {
18555
+ after_test: [
18556
+ "inbound id for the generated test email",
18557
+ "function id matching the deployed Function",
18558
+ "invocation status completed, failed, or send_failed",
18559
+ "reply/send result emitted by the handler"
18560
+ ],
18561
+ logs_command: commands.logs
18562
+ };
18563
+ }
18564
+ function buildInboxSetupGuide(status) {
18565
+ const domain = firstUsableManagedDomain(status);
18566
+ const commands = buildInboxSetupCommands();
18567
+ const mode = !status.receiving_ready ? "not_receiving" : status.processing_ready ? "actively_processed" : "stored_only";
18568
+ return {
18569
+ readiness: {
18570
+ ready: status.ready,
18571
+ receiving_ready: status.receiving_ready,
18572
+ processing_ready: status.processing_ready,
18573
+ mode,
18574
+ summary: status.summary
18575
+ },
18576
+ receive: {
18577
+ address: domain ? `${DEFAULT_LOCAL_PART}@${domain.domain}` : null,
18578
+ domain: domain?.domain ?? null,
18579
+ managed: domain?.managed ?? false,
18580
+ placeholder_local_part: domain ? DEFAULT_LOCAL_PART : null
18581
+ },
18582
+ processing: {
18583
+ stored_only: status.receiving_ready && !status.processing_ready,
18584
+ active: status.processing_ready,
18585
+ enabled_endpoints: status.endpoints.enabled,
18586
+ deployed_functions: status.functions.deployed
18587
+ },
18588
+ commands,
18589
+ proof: buildInboxSetupProof(commands),
18590
+ status
18591
+ };
18592
+ }
18593
+ function formatReadiness(guide) {
18594
+ const readiness = guide.readiness.ready ? "ready" : "not ready";
18595
+ const receiving = guide.readiness.receiving_ready ? "yes" : "no";
18596
+ const processing = guide.readiness.processing_ready ? "yes" : "no";
18597
+ const mode = guide.readiness.mode === "actively_processed" ? "actively processed" : guide.readiness.mode === "stored_only" ? "stored-only" : "not receiving";
18598
+ return [
18599
+ `Readiness: ${readiness}`,
18600
+ `Receiving: ${receiving}`,
18601
+ `Processing: ${processing}`,
18602
+ `Mode: ${mode}`
18603
+ ].join("\n");
18604
+ }
18605
+ function formatReceiveAddress(guide) {
18606
+ if (!guide.receive.domain || !guide.receive.address) return "Receive address: none found on a receiving-ready Primitive-managed domain";
18607
+ return [`Receive address: ${guide.receive.address}`, `Receive domain: ${guide.receive.domain} (Primitive-managed)`].join("\n");
18608
+ }
18609
+ function formatDomainDetails(status) {
18610
+ if (status.domains.length === 0) return ["Domains: none configured"];
18611
+ return status.domains.map((domain) => `- ${domain.domain}: ${statusText(domain.status)}, receive ${domain.receiving_ready ? "yes" : "no"}, process ${domain.processing_ready ? "yes" : "no"}, routes ${domain.processing_route_count}`);
18612
+ }
18613
+ function formatScaffoldCommands(commands) {
18614
+ return commands.scaffold.map((command) => ` ${command}`);
18615
+ }
18616
+ function formatInboxSetupGuide(guide) {
18617
+ const lines = [
18618
+ "Inbound setup",
18619
+ "",
18620
+ guide.readiness.summary,
18621
+ "",
18622
+ formatReadiness(guide),
18623
+ "",
18624
+ formatReceiveAddress(guide),
18625
+ "",
18626
+ "Domains",
18627
+ ...formatDomainDetails(guide.status),
18628
+ "",
18629
+ `Processing routes: ${guide.processing.enabled_endpoints} enabled endpoint(s), ${guide.processing.deployed_functions} deployed Function(s)`
18630
+ ];
18631
+ if (guide.readiness.mode === "not_receiving") lines.push("", "Next actions", "Make a receiving-ready domain available, then re-run:", ` ${guide.commands.status}`);
18632
+ else if (!guide.processing.active) lines.push("", "Next actions", "No processing route is enabled. Scaffold, deploy, and test an email Function:", ...formatScaffoldCommands(guide.commands));
18633
+ else lines.push("", "Next actions", "Inbound mail has an active processing route. Run a Function test when you know the Function id:", ` primitive functions test --id ${FUNCTION_ID_PLACEHOLDER} --wait --show-sends`);
18634
+ if (guide.status.next_actions.length > 0) {
18635
+ lines.push("", "API suggested actions");
18636
+ for (const action of guide.status.next_actions) lines.push(action.command ? `- ${action.message}\n ${action.command}` : `- ${action.message}`);
18637
+ }
18638
+ lines.push("", "Proof after functions test", "- Inbound id: the generated test email should have an inbound id.", "- Function id: the run should point at the Function id you deployed.", "- Invocation status: expect completed; failed or send_failed identifies the failing stage.", "- Reply/send result: --show-sends should show the handler's outbound result when it replies or sends.", "- Logs:", ` ${guide.proof.logs_command}`);
18639
+ return lines.join("\n");
18640
+ }
18641
+ var InboxSetupCommand = class InboxSetupCommand extends Command {
18642
+ static description = `Guide inbound email setup from the server-owned inbox status API.
18643
+
18644
+ This command does not scaffold, deploy, or run tests. It verifies auth, fetches inbox readiness, shows the first usable Primitive-managed receive address/domain, explains whether inbound mail is stored-only or actively processed, and prints the exact commands to add a Function processing route when one is missing.`;
18645
+ static summary = "Guide inbound email setup";
18646
+ static examples = ["<%= config.bin %> inbox setup", "<%= config.bin %> inbox setup --json"];
18647
+ static flags = {
18648
+ "api-key": Flags.string({
18649
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
18650
+ env: "PRIMITIVE_API_KEY"
18651
+ }),
18652
+ "api-base-url": Flags.string({
18653
+ description: API_BASE_URL_FLAG_DESCRIPTION,
18654
+ env: "PRIMITIVE_API_BASE_URL",
18655
+ hidden: true
18656
+ }),
18657
+ json: Flags.boolean({ description: "Print structured readiness, receive address, commands, proof metadata, and raw status as JSON." }),
18658
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
18659
+ };
18660
+ async run() {
18661
+ const { flags } = await this.parse(InboxSetupCommand);
18662
+ await runWithTiming(flags.time, async () => {
18663
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
18664
+ apiKey: flags["api-key"],
18665
+ apiBaseUrl: flags["api-base-url"],
18666
+ configDir: this.config.configDir
18667
+ });
18668
+ const result = await getInboxStatus({
18669
+ client: apiClient.client,
18670
+ responseStyle: "fields"
18671
+ });
18672
+ if (result.error) {
18673
+ const errorPayload = extractErrorPayload(result.error);
18674
+ writeErrorWithHints(errorPayload);
18675
+ surfaceUnauthorizedHint({
18676
+ auth,
18677
+ baseUrlOverridden,
18678
+ configDir: this.config.configDir,
18679
+ payload: errorPayload
18680
+ });
18681
+ process.exitCode = 1;
18682
+ return;
18683
+ }
18684
+ const envelope = result.data ?? {};
18685
+ const status = envelope.data;
18686
+ if (!status) throw new Errors.CLIError("Primitive API returned no inbox status.", { exit: 1 });
18687
+ const guide = buildInboxSetupGuide(status);
18688
+ if (flags.json) {
18689
+ this.log(JSON.stringify({
18690
+ ...envelope,
18691
+ data: guide
18692
+ }, null, 2));
18693
+ return;
18694
+ }
18695
+ this.log(formatInboxSetupGuide(guide));
18696
+ });
18697
+ }
18698
+ };
18699
+ //#endregion
17984
18700
  //#region src/oclif/commands/login.ts
17985
18701
  const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
17986
18702
  function cliError$3(message) {
@@ -18013,14 +18729,14 @@ function retryAfterSeconds$1(result) {
18013
18729
  }
18014
18730
  async function checkExistingLogin(params) {
18015
18731
  const requestConfig = resolveCliApiRequestConfig({
18016
- apiBaseUrl1: params.apiBaseUrl1,
18732
+ apiBaseUrl: params.apiBaseUrl,
18017
18733
  configDir: params.configDir
18018
18734
  });
18019
- const probeApiBaseUrl1 = requestConfig.apiBaseUrl1 ?? params.credentials.api_base_url_1;
18735
+ const probeApiBaseUrl = requestConfig.apiBaseUrl ?? params.credentials.api_base_url;
18020
18736
  let credentials = params.credentials;
18021
18737
  try {
18022
18738
  credentials = await refreshStoredCliCredentials({
18023
- apiBaseUrl1: probeApiBaseUrl1,
18739
+ apiBaseUrl: probeApiBaseUrl,
18024
18740
  configDir: params.configDir,
18025
18741
  credentials,
18026
18742
  credentialsLockHeld: params.credentialsLockHeld,
@@ -18036,8 +18752,7 @@ async function checkExistingLogin(params) {
18036
18752
  }
18037
18753
  const apiClient = new PrimitiveApiClient({
18038
18754
  apiKey: credentials.access_token,
18039
- apiBaseUrl1: probeApiBaseUrl1,
18040
- apiBaseUrl2: requestConfig.resolvedApiBaseUrl2,
18755
+ apiBaseUrl: probeApiBaseUrl,
18041
18756
  headers: requestConfig.headers
18042
18757
  });
18043
18758
  const result = await (params.checkAccount ?? ((client) => getAccount({
@@ -18047,7 +18762,7 @@ async function checkExistingLogin(params) {
18047
18762
  if (!result.error) return { status: "valid" };
18048
18763
  const payload = extractErrorPayload(result.error);
18049
18764
  const code = extractErrorCode(payload);
18050
- const baseUrlDiffersFromSaved = requestConfig.baseUrlOverridden && requestConfig.apiBaseUrl1 !== params.credentials.api_base_url_1;
18765
+ const baseUrlDiffersFromSaved = requestConfig.baseUrlOverridden && requestConfig.apiBaseUrl !== params.credentials.api_base_url;
18051
18766
  if (code === API_ERROR_CODES.unauthorized && !baseUrlDiffersFromSaved) {
18052
18767
  deleteCliCredentials(params.configDir);
18053
18768
  process.stderr.write("Removed saved Primitive CLI OAuth credentials because the existing session was rejected during login. Continuing with a fresh login.\n");
@@ -18068,9 +18783,9 @@ var LoginCommand$1 = class extends Command {
18068
18783
  "<%= config.bin %> login --force"
18069
18784
  ];
18070
18785
  static flags = {
18071
- "api-base-url-1": Flags.string({
18786
+ "api-base-url": Flags.string({
18072
18787
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
18073
- env: "PRIMITIVE_API_BASE_URL_1",
18788
+ env: "PRIMITIVE_API_BASE_URL",
18074
18789
  hidden: true
18075
18790
  }),
18076
18791
  "device-name": Flags.string({ description: "Device name shown in the browser approval screen" }),
@@ -18103,10 +18818,10 @@ var LoginCommand$1 = class extends Command {
18103
18818
  }
18104
18819
  async runWithCredentialLock(flags, retryCommand) {
18105
18820
  const { apiClient, requestConfig } = createCliApiClient({
18106
- apiBaseUrl1: flags["api-base-url-1"],
18821
+ apiBaseUrl: flags["api-base-url"],
18107
18822
  configDir: this.config.configDir
18108
18823
  });
18109
- const apiBaseUrl1 = requestConfig.resolvedApiBaseUrl1;
18824
+ const apiBaseUrl = requestConfig.resolvedApiBaseUrl;
18110
18825
  let existing;
18111
18826
  try {
18112
18827
  existing = loadCliCredentials(this.config.configDir);
@@ -18119,7 +18834,7 @@ var LoginCommand$1 = class extends Command {
18119
18834
  if (existing && flags.force) process.stderr.write("Replacing saved Primitive CLI credentials after browser approval because --force was set.\n");
18120
18835
  else if (existing) {
18121
18836
  const existingStatus = await checkExistingLogin({
18122
- apiBaseUrl1: flags["api-base-url-1"],
18837
+ apiBaseUrl: flags["api-base-url"],
18123
18838
  configDir: this.config.configDir,
18124
18839
  credentials: existing,
18125
18840
  credentialsLockHeld: true
@@ -18165,7 +18880,7 @@ var LoginCommand$1 = class extends Command {
18165
18880
  deleteChatState(this.config.configDir);
18166
18881
  saveCliCredentials(this.config.configDir, {
18167
18882
  access_token: login.access_token,
18168
- api_base_url_1: apiBaseUrl1,
18883
+ api_base_url: apiBaseUrl,
18169
18884
  auth_method: "oauth",
18170
18885
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
18171
18886
  expires_at: cliAccessTokenExpiresAt(login.expires_in),
@@ -18229,9 +18944,11 @@ function normalizeEmail(email) {
18229
18944
  }
18230
18945
  function pendingSignupFromJson(value) {
18231
18946
  if (!isRecord(value)) return null;
18232
- 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;
18947
+ 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;
18948
+ const apiBaseUrl = value.api_base_url ?? value.api_base_url_1;
18949
+ if (typeof apiBaseUrl !== "string") return null;
18233
18950
  return {
18234
- api_base_url_1: value.api_base_url_1,
18951
+ api_base_url: apiBaseUrl,
18235
18952
  created_at: value.created_at,
18236
18953
  email: value.email,
18237
18954
  expires_at: value.expires_at,
@@ -18247,20 +18964,20 @@ function pendingSignupPath(configDir) {
18247
18964
  function deletePendingAgentSignup(configDir) {
18248
18965
  rmSync(pendingSignupPath(configDir), { force: true });
18249
18966
  }
18250
- function pendingSignupFromStart(start, apiBaseUrl1) {
18967
+ function pendingSignupFromStart(start, apiBaseUrl) {
18251
18968
  return {
18252
18969
  ...start,
18253
- api_base_url_1: apiBaseUrl1,
18970
+ api_base_url: apiBaseUrl,
18254
18971
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
18255
18972
  expires_at: new Date(Date.now() + start.expires_in * 1e3).toISOString()
18256
18973
  };
18257
18974
  }
18258
- function savePendingAgentSignup(configDir, start, apiBaseUrl1) {
18975
+ function savePendingAgentSignup(configDir, start, apiBaseUrl) {
18259
18976
  mkdirSync(configDir, {
18260
18977
  mode: 448,
18261
18978
  recursive: true
18262
18979
  });
18263
- const pending = pendingSignupFromStart(start, apiBaseUrl1);
18980
+ const pending = pendingSignupFromStart(start, apiBaseUrl);
18264
18981
  const path = pendingSignupPath(configDir);
18265
18982
  const tempPath = join(configDir, `${PENDING_SIGNUP_FILE}.${process$1.pid}.${randomUUID()}.tmp`);
18266
18983
  try {
@@ -18274,7 +18991,7 @@ function savePendingAgentSignup(configDir, start, apiBaseUrl1) {
18274
18991
  throw error;
18275
18992
  }
18276
18993
  }
18277
- function loadPendingAgentSignup(configDir, apiBaseUrl1) {
18994
+ function loadPendingAgentSignup(configDir, apiBaseUrl) {
18278
18995
  const path = pendingSignupPath(configDir);
18279
18996
  let contents;
18280
18997
  try {
@@ -18293,7 +19010,7 @@ function loadPendingAgentSignup(configDir, apiBaseUrl1) {
18293
19010
  deletePendingAgentSignup(configDir);
18294
19011
  return null;
18295
19012
  }
18296
- if (pending.api_base_url_1 !== apiBaseUrl1) return null;
19013
+ if (pending.api_base_url !== apiBaseUrl) return null;
18297
19014
  if (new Date(pending.expires_at).getTime() <= Date.now()) {
18298
19015
  deletePendingAgentSignup(configDir);
18299
19016
  return null;
@@ -18303,11 +19020,100 @@ function loadPendingAgentSignup(configDir, apiBaseUrl1) {
18303
19020
  expires_in: Math.max(0, Math.ceil((new Date(pending.expires_at).getTime() - Date.now()) / 1e3))
18304
19021
  };
18305
19022
  }
19023
+ function readPendingAgentSignupState(configDir, apiBaseUrl) {
19024
+ const path = pendingSignupPath(configDir);
19025
+ let contents;
19026
+ try {
19027
+ contents = readFileSync(path, "utf8");
19028
+ } catch (error) {
19029
+ if (error && typeof error === "object" && error.code === "ENOENT") return null;
19030
+ throw error;
19031
+ }
19032
+ let pending;
19033
+ try {
19034
+ pending = pendingSignupFromJson(JSON.parse(contents));
19035
+ } catch {
19036
+ pending = null;
19037
+ }
19038
+ if (!pending) {
19039
+ deletePendingAgentSignup(configDir);
19040
+ return null;
19041
+ }
19042
+ if (pending.api_base_url !== apiBaseUrl) return null;
19043
+ return pending;
19044
+ }
19045
+ function pendingSignupStartCommand(email) {
19046
+ return `primitive signup ${email ?? "<email>"} --signup-code <invite-code> --accept-terms`;
19047
+ }
19048
+ function buildSignupStatus(params) {
19049
+ const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
19050
+ const pending = readPendingAgentSignupState(params.configDir, params.apiBaseUrl);
19051
+ if (!pending) return {
19052
+ code_length: null,
19053
+ confirm_command: null,
19054
+ email: null,
19055
+ expired: false,
19056
+ expires_at: null,
19057
+ expires_in: null,
19058
+ pending: false,
19059
+ resend_after: null,
19060
+ resend_command: null,
19061
+ signup_command: pendingSignupStartCommand(params.email)
19062
+ };
19063
+ if (params.email && normalizeEmail(pending.email) !== normalizeEmail(params.email)) throw cliError$2(`Pending ${copy.actionNoun} is for ${pending.email}, not ${params.email}. Run \`primitive signup status\` without an email argument to inspect it.`);
19064
+ const expiresAtMs = new Date(pending.expires_at).getTime();
19065
+ const expiresIn = Number.isFinite(expiresAtMs) ? Math.ceil((expiresAtMs - Date.now()) / 1e3) : null;
19066
+ return {
19067
+ code_length: pending.verification_code_length,
19068
+ confirm_command: `primitive ${copy.confirmCommand(pending.email)}`,
19069
+ email: pending.email,
19070
+ expired: expiresIn !== null && expiresIn <= 0,
19071
+ expires_at: pending.expires_at,
19072
+ expires_in: expiresIn === null ? null : Math.max(0, expiresIn),
19073
+ pending: true,
19074
+ resend_after: pending.resend_after,
19075
+ resend_command: `primitive ${copy.resendCommand(pending.email)}`
19076
+ };
19077
+ }
19078
+ function writeSignupStatus(status) {
19079
+ if (!status.pending) {
19080
+ process$1.stdout.write("No pending Primitive signup found.\n");
19081
+ process$1.stdout.write(`Start one with \`${status.signup_command ?? pendingSignupStartCommand()}\`.\n`);
19082
+ return;
19083
+ }
19084
+ process$1.stdout.write(`Pending Primitive signup for ${status.email}.\n`);
19085
+ if (status.code_length !== null) process$1.stdout.write(`Verification code length: ${status.code_length}\n`);
19086
+ if (status.expires_at) if (status.expired) process$1.stdout.write(`Expired at: ${status.expires_at}\n`);
19087
+ else {
19088
+ process$1.stdout.write(`Expires at: ${status.expires_at}\n`);
19089
+ process$1.stdout.write(`Expires in: ${formatSignupSeconds(status.expires_in)}\n`);
19090
+ }
19091
+ if (status.resend_after !== null) process$1.stdout.write(`Resend after: ${formatSignupSeconds(status.resend_after)}\n`);
19092
+ if (status.confirm_command) process$1.stdout.write(`Confirm: ${status.confirm_command}\n`);
19093
+ if (status.resend_command) process$1.stdout.write(`Resend: ${status.resend_command}\n`);
19094
+ }
19095
+ function runSignupStatus(params) {
19096
+ const { requestConfig } = createCliApiClient({
19097
+ apiBaseUrl: params.flags["api-base-url"],
19098
+ configDir: params.configDir
19099
+ });
19100
+ const status = buildSignupStatus({
19101
+ apiBaseUrl: requestConfig.resolvedApiBaseUrl,
19102
+ configDir: params.configDir,
19103
+ copy: params.copy,
19104
+ email: params.email
19105
+ });
19106
+ if (params.flags.json) {
19107
+ process$1.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
19108
+ return;
19109
+ }
19110
+ writeSignupStatus(status);
19111
+ }
18306
19112
  function requirePendingSignupForEmail(params) {
18307
19113
  const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
18308
- const pending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl1);
18309
- if (!pending) throw cliError$2(`No pending ${copy.actionNoun} for ${params.email}. Run \`primitive ${copy.startCommand(params.email)}\` first.`);
18310
- if (normalizeEmail(pending.email) !== normalizeEmail(params.email)) throw cliError$2(`Pending ${copy.actionNoun} is for ${pending.email}, not ${params.email}. Run \`primitive ${copy.startCommand(params.email)} --force\` to replace it.`);
19114
+ const pending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl);
19115
+ 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.`);
19116
+ 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.`);
18311
19117
  return pending;
18312
19118
  }
18313
19119
  function retryAfterSeconds(result) {
@@ -18368,7 +19174,7 @@ async function checkExistingCredentials(params) {
18368
19174
  }
18369
19175
  if (!existing) return;
18370
19176
  const existingStatus = await checkExistingLoginFn({
18371
- apiBaseUrl1: params.apiBaseUrl1,
19177
+ apiBaseUrl: params.apiBaseUrl,
18372
19178
  configDir: params.configDir,
18373
19179
  credentials: existing,
18374
19180
  credentialsLockHeld: true
@@ -18387,7 +19193,7 @@ function saveSignupCredentials(params) {
18387
19193
  deleteChatState(params.configDir);
18388
19194
  saveCliCredentials(params.configDir, {
18389
19195
  access_token: params.signup.access_token,
18390
- api_base_url_1: params.apiBaseUrl1,
19196
+ api_base_url: params.apiBaseUrl,
18391
19197
  auth_method: "oauth",
18392
19198
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
18393
19199
  expires_at: cliAccessTokenExpiresAt(params.signup.expires_in),
@@ -18406,17 +19212,17 @@ function writeStartInstructions(start, copy = DEFAULT_SIGNUP_COMMAND_COPY) {
18406
19212
  }
18407
19213
  async function startSignup(params) {
18408
19214
  const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
18409
- const existingPending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl1);
19215
+ const existingPending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl);
18410
19216
  if (existingPending && !params.flags.force) {
18411
19217
  if (normalizeEmail(existingPending.email) === normalizeEmail(params.email)) {
18412
19218
  process$1.stderr.write(`Continuing pending Primitive ${copy.actionNoun} for ${existingPending.email}.\n`);
18413
- process$1.stderr.write(`Run \`primitive ${copy.confirmCommand(existingPending.email)}\` to finish, or \`primitive ${copy.resendCommand(existingPending.email)}\` to send a new code.\n`);
19219
+ process$1.stderr.write(`Run \`primitive ${copy.confirmCommand(existingPending.email)}\` to finish, \`primitive ${copy.resendCommand(existingPending.email)}\` to send a new code, or \`primitive signup status\` to inspect it.\n`);
18414
19220
  return {
18415
19221
  pending: existingPending,
18416
19222
  started: false
18417
19223
  };
18418
19224
  }
18419
- throw cliError$2(`Pending ${copy.actionNoun} is for ${existingPending.email}. Run \`primitive ${copy.startCommand(params.email)} --force\` to replace it.`);
19225
+ throw cliError$2(`Pending ${copy.actionNoun} is for ${existingPending.email}. Run \`primitive signup status\` to inspect it, or \`primitive ${copy.startCommand(params.email)} --force\` to replace it.`);
18420
19226
  }
18421
19227
  if (params.flags.force) deletePendingAgentSignup(params.configDir);
18422
19228
  const promptRequiredFn = params.deps.promptRequired ?? promptRequired;
@@ -18441,7 +19247,7 @@ async function startSignup(params) {
18441
19247
  const startResult = unwrapData$1(started.data);
18442
19248
  if (!startResult) throw cliError$2("Primitive API returned an empty agent signup response.");
18443
19249
  return {
18444
- pending: savePendingAgentSignup(params.configDir, startResult, params.apiBaseUrl1),
19250
+ pending: savePendingAgentSignup(params.configDir, startResult, params.apiBaseUrl),
18445
19251
  started: true
18446
19252
  };
18447
19253
  }
@@ -18461,7 +19267,7 @@ async function resendVerificationCode(params) {
18461
19267
  verification_code_length: resend.verification_code_length
18462
19268
  } : params.start;
18463
19269
  return {
18464
- pending: savePendingAgentSignup(params.configDir, next, params.apiBaseUrl1),
19270
+ pending: savePendingAgentSignup(params.configDir, next, params.apiBaseUrl),
18465
19271
  resent: true
18466
19272
  };
18467
19273
  }
@@ -18485,18 +19291,18 @@ async function runSignupStartWithCredentialLock(params) {
18485
19291
  const promptRequiredFn = deps.promptRequired ?? promptRequired;
18486
19292
  const email = params.email ?? await promptRequiredFn("Email: ");
18487
19293
  await checkExistingCredentials({
18488
- apiBaseUrl1: flags["api-base-url-1"],
19294
+ apiBaseUrl: flags["api-base-url"],
18489
19295
  configDir,
18490
19296
  copy: params.copy,
18491
19297
  deps,
18492
19298
  flags
18493
19299
  });
18494
19300
  const { apiClient, requestConfig } = createCliApiClient({
18495
- apiBaseUrl1: flags["api-base-url-1"],
19301
+ apiBaseUrl: flags["api-base-url"],
18496
19302
  configDir
18497
19303
  });
18498
19304
  const start = await startSignup({
18499
- apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
19305
+ apiBaseUrl: requestConfig.resolvedApiBaseUrl,
18500
19306
  apiClient,
18501
19307
  configDir,
18502
19308
  copy: params.copy,
@@ -18510,19 +19316,19 @@ async function runSignupConfirmWithCredentialLock(params) {
18510
19316
  const { configDir, flags } = params;
18511
19317
  const deps = params.deps ?? {};
18512
19318
  if (!params.skipExistingCredentialCheck) await checkExistingCredentials({
18513
- apiBaseUrl1: flags["api-base-url-1"],
19319
+ apiBaseUrl: flags["api-base-url"],
18514
19320
  configDir,
18515
19321
  copy: params.copy,
18516
19322
  deps,
18517
19323
  flags
18518
19324
  });
18519
19325
  const { apiClient, requestConfig } = createCliApiClient({
18520
- apiBaseUrl1: flags["api-base-url-1"],
19326
+ apiBaseUrl: flags["api-base-url"],
18521
19327
  configDir
18522
19328
  });
18523
- const apiBaseUrl1 = requestConfig.resolvedApiBaseUrl1;
19329
+ const apiBaseUrl = requestConfig.resolvedApiBaseUrl;
18524
19330
  const pending = requirePendingSignupForEmail({
18525
- apiBaseUrl1,
19331
+ apiBaseUrl,
18526
19332
  copy: params.copy,
18527
19333
  configDir,
18528
19334
  email: params.email
@@ -18540,7 +19346,7 @@ async function runSignupConfirmWithCredentialLock(params) {
18540
19346
  const signup = unwrapData$1(verified.data);
18541
19347
  if (!signup) throw cliError$2("Primitive API returned an empty agent signup verification response.");
18542
19348
  saveSignupCredentials({
18543
- apiBaseUrl1,
19349
+ apiBaseUrl,
18544
19350
  configDir,
18545
19351
  signup
18546
19352
  });
@@ -18552,25 +19358,27 @@ async function runSignupConfirmWithCredentialLock(params) {
18552
19358
  }
18553
19359
  const payload = extractErrorPayload(verified.error);
18554
19360
  const code = extractErrorCode(payload);
18555
- if (code === INVALID_VERIFICATION_CODE) throw cliError$2(`Invalid verification code. Try again or run ${(params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY).resendCommand(params.email)}.`);
19361
+ if (code === INVALID_VERIFICATION_CODE) throw cliError$2(`Invalid verification code. Try again, run ${(params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY).resendCommand(params.email)}, or run primitive signup status.`);
18556
19362
  if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(configDir);
18557
19363
  writeErrorWithHints(payload);
18558
19364
  throw cliError$2("Primitive agent signup failed while verifying the account.");
18559
19365
  }
18560
19366
  async function runSignupResendWithCredentialLock(params) {
18561
19367
  const deps = params.deps ?? {};
19368
+ const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
18562
19369
  const { apiClient, requestConfig } = createCliApiClient({
18563
- apiBaseUrl1: params.flags["api-base-url-1"],
19370
+ apiBaseUrl: params.flags["api-base-url"],
18564
19371
  configDir: params.configDir
18565
19372
  });
18566
- const pending = requirePendingSignupForEmail({
18567
- apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
18568
- copy: params.copy,
19373
+ const pending = params.email ? requirePendingSignupForEmail({
19374
+ apiBaseUrl: requestConfig.resolvedApiBaseUrl,
19375
+ copy,
18569
19376
  configDir: params.configDir,
18570
19377
  email: params.email
18571
- });
19378
+ }) : loadPendingAgentSignup(params.configDir, requestConfig.resolvedApiBaseUrl);
19379
+ if (!pending) throw cliError$2(`No pending ${copy.actionNoun} found. Run \`primitive signup status\` to inspect pending state, or start one with \`${pendingSignupStartCommand()}\`.`);
18572
19380
  const resend = await resendVerificationCode({
18573
- apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
19381
+ apiBaseUrl: requestConfig.resolvedApiBaseUrl,
18574
19382
  apiClient,
18575
19383
  configDir: params.configDir,
18576
19384
  deps,
@@ -18583,20 +19391,20 @@ async function runSignupInteractiveWithCredentialLock(params) {
18583
19391
  const deps = params.deps ?? {};
18584
19392
  const promptRequiredFn = deps.promptRequired ?? promptRequired;
18585
19393
  await checkExistingCredentials({
18586
- apiBaseUrl1: flags["api-base-url-1"],
19394
+ apiBaseUrl: flags["api-base-url"],
18587
19395
  configDir,
18588
19396
  deps,
18589
19397
  flags
18590
19398
  });
18591
19399
  const { apiClient, requestConfig } = createCliApiClient({
18592
- apiBaseUrl1: flags["api-base-url-1"],
19400
+ apiBaseUrl: flags["api-base-url"],
18593
19401
  configDir
18594
19402
  });
18595
- const apiBaseUrl1 = requestConfig.resolvedApiBaseUrl1;
18596
- let start = flags.force ? null : loadPendingAgentSignup(configDir, apiBaseUrl1);
19403
+ const apiBaseUrl = requestConfig.resolvedApiBaseUrl;
19404
+ let start = flags.force ? null : loadPendingAgentSignup(configDir, apiBaseUrl);
18597
19405
  if (start) process$1.stderr.write(`Continuing pending Primitive signup for ${start.email}.\n`);
18598
19406
  else start = (await startSignup({
18599
- apiBaseUrl1,
19407
+ apiBaseUrl,
18600
19408
  apiClient,
18601
19409
  configDir,
18602
19410
  deps,
@@ -18610,7 +19418,7 @@ async function runSignupInteractiveWithCredentialLock(params) {
18610
19418
  const verificationCode = await promptRequiredFn(`Verification code (${start.verification_code_length} digits): `);
18611
19419
  if (verificationCode.toLowerCase() === "resend") {
18612
19420
  const resend = await resendVerificationCode({
18613
- apiBaseUrl1,
19421
+ apiBaseUrl,
18614
19422
  apiClient,
18615
19423
  configDir,
18616
19424
  deps,
@@ -18627,7 +19435,7 @@ async function runSignupInteractiveWithCredentialLock(params) {
18627
19435
  deps,
18628
19436
  email: start.email,
18629
19437
  flags: {
18630
- "api-base-url-1": flags["api-base-url-1"],
19438
+ "api-base-url": flags["api-base-url"],
18631
19439
  force: true
18632
19440
  },
18633
19441
  skipExistingCredentialCheck: true
@@ -18645,9 +19453,9 @@ async function runSignupInteractiveWithCredentialLock(params) {
18645
19453
  function commonStartFlags() {
18646
19454
  return {
18647
19455
  "accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy" }),
18648
- "api-base-url-1": Flags.string({
19456
+ "api-base-url": Flags.string({
18649
19457
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
18650
- env: "PRIMITIVE_API_BASE_URL_1",
19458
+ env: "PRIMITIVE_API_BASE_URL",
18651
19459
  hidden: true
18652
19460
  }),
18653
19461
  "device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
@@ -18708,9 +19516,9 @@ var SignupConfirmCommand = class SignupConfirmCommand extends Command {
18708
19516
  static summary = "Confirm account signup";
18709
19517
  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"];
18710
19518
  static flags = {
18711
- "api-base-url-1": Flags.string({
19519
+ "api-base-url": Flags.string({
18712
19520
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
18713
- env: "PRIMITIVE_API_BASE_URL_1",
19521
+ env: "PRIMITIVE_API_BASE_URL",
18714
19522
  hidden: true
18715
19523
  }),
18716
19524
  force: Flags.boolean({
@@ -18741,15 +19549,15 @@ var SignupConfirmCommand = class SignupConfirmCommand extends Command {
18741
19549
  };
18742
19550
  var SignupResendCommand = class SignupResendCommand extends Command {
18743
19551
  static args = { email: Args.string({
18744
- description: "Email address used to start signup",
18745
- required: true
19552
+ description: "Email address used to start signup. Defaults to the saved pending signup.",
19553
+ required: false
18746
19554
  }) };
18747
19555
  static description = "Resend the verification code for a pending signup.";
18748
19556
  static summary = "Resend signup verification code";
18749
- static examples = ["<%= config.bin %> signup resend user@example.com"];
18750
- static flags = { "api-base-url-1": Flags.string({
19557
+ static examples = ["<%= config.bin %> signup resend", "<%= config.bin %> signup resend user@example.com"];
19558
+ static flags = { "api-base-url": Flags.string({
18751
19559
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
18752
- env: "PRIMITIVE_API_BASE_URL_1",
19560
+ env: "PRIMITIVE_API_BASE_URL",
18753
19561
  hidden: true
18754
19562
  }) };
18755
19563
  async run() {
@@ -18771,6 +19579,35 @@ var SignupResendCommand = class SignupResendCommand extends Command {
18771
19579
  }
18772
19580
  }
18773
19581
  };
19582
+ var SignupStatusCommand = class SignupStatusCommand extends Command {
19583
+ static args = { email: Args.string({
19584
+ description: "Email address expected in the pending signup",
19585
+ required: false
19586
+ }) };
19587
+ static description = "Inspect the locally saved pending Primitive signup state.";
19588
+ static summary = "Show pending signup status";
19589
+ static examples = [
19590
+ "<%= config.bin %> signup status",
19591
+ "<%= config.bin %> signup status user@example.com",
19592
+ "<%= config.bin %> signup status --json"
19593
+ ];
19594
+ static flags = {
19595
+ "api-base-url": Flags.string({
19596
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
19597
+ env: "PRIMITIVE_API_BASE_URL",
19598
+ hidden: true
19599
+ }),
19600
+ json: Flags.boolean({ description: "Print pending signup status as JSON" })
19601
+ };
19602
+ async run() {
19603
+ const { args, flags } = await this.parse(SignupStatusCommand);
19604
+ runSignupStatus({
19605
+ configDir: this.config.configDir,
19606
+ email: args.email,
19607
+ flags
19608
+ });
19609
+ }
19610
+ };
18774
19611
  var SignupInteractiveCommand = class SignupInteractiveCommand extends Command {
18775
19612
  static description = "Run the full signup flow in one interactive terminal session.";
18776
19613
  static summary = "Run interactive account signup";
@@ -18825,7 +19662,7 @@ async function runLogoutWithCredentialLock(params) {
18825
19662
  let authenticated;
18826
19663
  try {
18827
19664
  authenticated = await deps.createAuthenticatedCliApiClient({
18828
- apiBaseUrl1: params.flags["api-base-url-1"],
19665
+ apiBaseUrl: params.flags["api-base-url"],
18829
19666
  configDir: params.configDir,
18830
19667
  credentialsLockHeld: true
18831
19668
  });
@@ -18889,9 +19726,9 @@ var LogoutCommand = class LogoutCommand extends Command {
18889
19726
  static summary = "Log out and revoke the saved CLI OAuth grant";
18890
19727
  static examples = ["<%= config.bin %> logout", "<%= config.bin %> logout --force"];
18891
19728
  static flags = {
18892
- "api-base-url-1": Flags.string({
19729
+ "api-base-url": Flags.string({
18893
19730
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
18894
- env: "PRIMITIVE_API_BASE_URL_1",
19731
+ env: "PRIMITIVE_API_BASE_URL",
18895
19732
  hidden: true
18896
19733
  }),
18897
19734
  force: Flags.boolean({
@@ -19031,6 +19868,7 @@ var ReplyCommand = class ReplyCommand extends Command {
19031
19868
  static examples = [
19032
19869
  "<%= config.bin %> reply --id <inbound-email-id> --body 'Thanks, got it.'",
19033
19870
  "<%= config.bin %> reply --id <inbound-email-id> --body-file ./reply.txt",
19871
+ "<%= config.bin %> reply --id <inbound-email-id> --body 'See attached.' --attachment ./report.pdf",
19034
19872
  "<%= config.bin %> reply --id <inbound-email-id> --html '<p>Thanks, got it.</p>' --wait",
19035
19873
  "<%= config.bin %> reply --id <inbound-email-id> --from 'Support <support@example.com>' --body 'Thanks!'"
19036
19874
  ];
@@ -19039,14 +19877,9 @@ var ReplyCommand = class ReplyCommand extends Command {
19039
19877
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
19040
19878
  env: "PRIMITIVE_API_KEY"
19041
19879
  }),
19042
- "api-base-url-1": Flags.string({
19043
- description: "Override the primary API base URL. Internal testing only; not documented to customers.",
19044
- env: "PRIMITIVE_API_BASE_URL_1",
19045
- hidden: true
19046
- }),
19047
- "api-base-url-2": Flags.string({
19048
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
19049
- env: "PRIMITIVE_API_BASE_URL_2",
19880
+ "api-base-url": Flags.string({
19881
+ description: "Override the API base URL. Internal testing only; not documented to customers.",
19882
+ env: "PRIMITIVE_API_BASE_URL",
19050
19883
  hidden: true
19051
19884
  }),
19052
19885
  id: Flags.string({
@@ -19054,12 +19887,17 @@ var ReplyCommand = class ReplyCommand extends Command {
19054
19887
  required: true
19055
19888
  }),
19056
19889
  body: Flags.string({ description: "Plain-text reply body. Either --body or --html (or both) is required." }),
19057
- "body-file": Flags.string({ description: "Read the plain-text reply body from a UTF-8 file. Mutually exclusive with --body and --body-stdin." }),
19890
+ "body-file": Flags.string({ description: "Read the plain-text reply body from a UTF-8 file; this does not attach the file. Use --attachment for attachments. Mutually exclusive with --body and --body-stdin." }),
19058
19891
  "body-stdin": Flags.boolean({ description: "Read the plain-text reply body from stdin. Mutually exclusive with --body and --body-file. Stdin can only be consumed once." }),
19059
19892
  html: Flags.string({ description: "HTML reply body. Either --body or --html (or both) is required." }),
19060
- "html-file": Flags.string({ description: "Read the HTML reply body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
19893
+ "html-file": Flags.string({ description: "Read the HTML reply body from a UTF-8 file; this does not attach the file. Use --attachment for attachments. Mutually exclusive with --html and --html-stdin." }),
19061
19894
  "html-stdin": Flags.boolean({ description: "Read the HTML reply body from stdin. Mutually exclusive with --html and --html-file. Stdin can only be consumed once." }),
19062
19895
  from: Flags.string({ description: "Optional From header override. Defaults to the inbound recipient." }),
19896
+ attachment: Flags.string({
19897
+ char: "a",
19898
+ description: "Attach a file to the reply. Repeat --attachment to attach multiple files.",
19899
+ multiple: true
19900
+ }),
19063
19901
  wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the reply for delivery." }),
19064
19902
  time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
19065
19903
  };
@@ -19077,15 +19915,16 @@ var ReplyCommand = class ReplyCommand extends Command {
19077
19915
  await runWithTiming(flags.time, async () => {
19078
19916
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
19079
19917
  apiKey: flags["api-key"],
19080
- apiBaseUrl1: flags["api-base-url-1"],
19081
- apiBaseUrl2: flags["api-base-url-2"],
19918
+ apiBaseUrl: flags["api-base-url"],
19082
19919
  configDir: this.config.configDir
19083
19920
  });
19921
+ const attachments = readAttachmentFiles(flags.attachment);
19084
19922
  const result = await replyToEmail({
19085
19923
  body: {
19086
19924
  ...bodies.body !== void 0 ? { body_text: bodies.body } : {},
19087
19925
  ...bodies.html !== void 0 ? { body_html: bodies.html } : {},
19088
19926
  ...flags.from !== void 0 ? { from: flags.from } : {},
19927
+ ...attachments !== void 0 ? { attachments } : {},
19089
19928
  ...flags.wait !== void 0 ? { wait: flags.wait } : {}
19090
19929
  },
19091
19930
  client: apiClient.client,
@@ -19113,38 +19952,133 @@ var ReplyCommand = class ReplyCommand extends Command {
19113
19952
  }
19114
19953
  };
19115
19954
  //#endregion
19116
- //#region src/oclif/attachments.ts
19117
- function readAttachmentBytes(path, readFile) {
19118
- try {
19119
- return Buffer.from(readFile(path));
19120
- } catch (error) {
19121
- const detail = error instanceof Error ? error.message : String(error);
19122
- throw new Errors.CLIError(`Could not read --attachment ${path}: ${detail}`, { exit: 1 });
19123
- }
19955
+ //#region src/oclif/commands/semantic-search.ts
19956
+ const DEFAULT_LIMIT = 10;
19957
+ const MAX_LIMIT = 100;
19958
+ const SCORE_WIDTH = 7;
19959
+ const SOURCE_WIDTH = 4;
19960
+ const SUBJECT_WIDTH = 40;
19961
+ const FROM_WIDTH = 26;
19962
+ const SNIPPET_WIDTH = 60;
19963
+ function truncate(value, width) {
19964
+ if (value.length <= width) return value.padEnd(width);
19965
+ return `${value.slice(0, width - 3)}...`;
19124
19966
  }
19125
- function hasControlCharacter(value) {
19126
- return Array.from(value).some((character) => {
19127
- const code = character.charCodeAt(0);
19128
- return code <= 31 || code >= 127 && code <= 159;
19129
- });
19967
+ function sourceLabel(t) {
19968
+ return t === "inbound_email" ? "in" : "out";
19130
19969
  }
19131
- function validateAttachmentFilename(path, filename) {
19132
- if (!filename) throw new Errors.CLIError(`Could not derive an attachment filename from ${path}. Pass a file path.`, { exit: 1 });
19133
- if (hasControlCharacter(filename)) throw new Errors.CLIError(`Attachment filename ${filename} contains control characters.`, { exit: 1 });
19970
+ function formatRow(r) {
19971
+ 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)}`;
19134
19972
  }
19135
- function readAttachmentFiles(paths, readFile = readFileSync) {
19136
- if (!paths || paths.length === 0) return void 0;
19137
- return paths.map((path) => {
19138
- const filename = basename(path);
19139
- validateAttachmentFilename(path, filename);
19140
- const bytes = readAttachmentBytes(path, readFile);
19141
- if (bytes.length === 0) throw new Errors.CLIError(`Attachment file ${path} is empty. Attachments must contain at least one byte.`, { exit: 1 });
19142
- return {
19143
- content_base64: bytes.toString("base64"),
19144
- filename
19145
- };
19146
- });
19973
+ function formatHeader() {
19974
+ return `${"SCORE".padStart(SCORE_WIDTH)} ${"SRC".padEnd(SOURCE_WIDTH)} ${"SUBJECT".padEnd(SUBJECT_WIDTH)} ${"FROM".padEnd(FROM_WIDTH)} EXCERPT`;
19147
19975
  }
19976
+ var SemanticSearchCommand = class SemanticSearchCommand extends Command {
19977
+ static description = `Search received and sent mail by meaning or keywords.
19978
+
19979
+ Returns ranked rows. Each row carries a relevance score, the fields it
19980
+ matched, and a match-centered excerpt. Defaults to hybrid mode (blends
19981
+ semantic and keyword signals); use \`--mode keyword\` for plain
19982
+ full-text matching and \`--mode semantic\` for embedding-only.
19983
+
19984
+ Requires the Pro plan with the semantic_search_enabled entitlement.`;
19985
+ static summary = "Semantic / hybrid / keyword search across received and sent mail";
19986
+ static examples = [
19987
+ "<%= config.bin %> semantic-search \"invoice from acme\"",
19988
+ "<%= config.bin %> semantic-search \"shipping update\" --mode keyword",
19989
+ "<%= config.bin %> semantic-search \"kickoff\" --corpus inbound --limit 25",
19990
+ "<%= config.bin %> semantic-search renewal --json | jq '.data[].id'"
19991
+ ];
19992
+ static args = { query: Args.string({
19993
+ description: "The search query.",
19994
+ required: true
19995
+ }) };
19996
+ static flags = {
19997
+ "api-key": Flags.string({
19998
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
19999
+ env: "PRIMITIVE_API_KEY"
20000
+ }),
20001
+ "api-base-url": Flags.string({
20002
+ description: API_BASE_URL_FLAG_DESCRIPTION,
20003
+ env: "PRIMITIVE_API_BASE_URL",
20004
+ hidden: true
20005
+ }),
20006
+ mode: Flags.string({
20007
+ description: "Ranking strategy.",
20008
+ options: [
20009
+ "hybrid",
20010
+ "semantic",
20011
+ "keyword"
20012
+ ],
20013
+ default: "hybrid"
20014
+ }),
20015
+ corpus: Flags.string({
20016
+ description: "Restrict to inbound or outbound. Pass twice to include both (the default).",
20017
+ options: ["inbound", "outbound"],
20018
+ multiple: true
20019
+ }),
20020
+ "date-from": Flags.string({ description: "Only include mail at or after this ISO-8601 timestamp." }),
20021
+ "date-to": Flags.string({ description: "Only include mail at or before this ISO-8601 timestamp." }),
20022
+ limit: Flags.integer({
20023
+ description: `Maximum results to return (1-${MAX_LIMIT}, default ${DEFAULT_LIMIT}).`,
20024
+ default: DEFAULT_LIMIT,
20025
+ min: 1,
20026
+ max: MAX_LIMIT
20027
+ }),
20028
+ cursor: Flags.string({ description: "Opaque pagination cursor from a prior response's meta.cursor." }),
20029
+ json: Flags.boolean({ description: "Print the raw response envelope as JSON on STDOUT instead of the text table." }),
20030
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
20031
+ };
20032
+ async run() {
20033
+ const { args, flags } = await this.parse(SemanticSearchCommand);
20034
+ await runWithTiming(flags.time, async () => {
20035
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
20036
+ apiKey: flags["api-key"],
20037
+ apiBaseUrl: flags["api-base-url"],
20038
+ configDir: this.config.configDir
20039
+ });
20040
+ const result = await semanticSearch({
20041
+ client: apiClient.client,
20042
+ body: {
20043
+ query: args.query,
20044
+ mode: flags.mode,
20045
+ ...flags.corpus ? { corpus: flags.corpus } : {},
20046
+ ...flags["date-from"] ? { date_from: flags["date-from"] } : {},
20047
+ ...flags["date-to"] ? { date_to: flags["date-to"] } : {},
20048
+ limit: flags.limit,
20049
+ ...flags.cursor ? { cursor: flags.cursor } : {}
20050
+ },
20051
+ responseStyle: "fields"
20052
+ });
20053
+ if (result.error) {
20054
+ const errorPayload = extractErrorPayload(result.error);
20055
+ writeErrorWithHints(errorPayload);
20056
+ surfaceUnauthorizedHint({
20057
+ auth,
20058
+ baseUrlOverridden,
20059
+ configDir: this.config.configDir,
20060
+ payload: errorPayload
20061
+ });
20062
+ process.exitCode = 1;
20063
+ return;
20064
+ }
20065
+ const envelope = result.data;
20066
+ if (flags.json) {
20067
+ this.log(JSON.stringify(envelope ?? null, null, 2));
20068
+ return;
20069
+ }
20070
+ const rows = envelope?.data ?? [];
20071
+ if (rows.length === 0) {
20072
+ process.stderr.write("No matching mail.\n");
20073
+ return;
20074
+ }
20075
+ process.stderr.write(`${formatHeader()}\n`);
20076
+ for (const row of rows) this.log(formatRow(row));
20077
+ const nextCursor = envelope?.meta?.cursor ?? null;
20078
+ if (nextCursor) process.stderr.write(`\nNext page: pass --cursor ${nextCursor}\n`);
20079
+ });
20080
+ }
20081
+ };
19148
20082
  //#endregion
19149
20083
  //#region src/oclif/commands/send.ts
19150
20084
  var SendCommand = class SendCommand extends Command {
@@ -19171,14 +20105,9 @@ var SendCommand = class SendCommand extends Command {
19171
20105
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
19172
20106
  env: "PRIMITIVE_API_KEY"
19173
20107
  }),
19174
- "api-base-url-1": Flags.string({
19175
- description: "Override the primary API base URL. Internal testing only; not documented to customers.",
19176
- env: "PRIMITIVE_API_BASE_URL_1",
19177
- hidden: true
19178
- }),
19179
- "api-base-url-2": Flags.string({
19180
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
19181
- env: "PRIMITIVE_API_BASE_URL_2",
20108
+ "api-base-url": Flags.string({
20109
+ description: "Override the API base URL. Internal testing only; not documented to customers.",
20110
+ env: "PRIMITIVE_API_BASE_URL",
19182
20111
  hidden: true
19183
20112
  }),
19184
20113
  to: Flags.string({
@@ -19217,8 +20146,7 @@ var SendCommand = class SendCommand extends Command {
19217
20146
  await runWithTiming(flags.time, async () => {
19218
20147
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
19219
20148
  apiKey: flags["api-key"],
19220
- apiBaseUrl1: flags["api-base-url-1"],
19221
- apiBaseUrl2: flags["api-base-url-2"],
20149
+ apiBaseUrl: flags["api-base-url"],
19222
20150
  configDir: this.config.configDir
19223
20151
  });
19224
20152
  const authFailureContext = {
@@ -19240,7 +20168,7 @@ var SendCommand = class SendCommand extends Command {
19240
20168
  ...flags.wait !== void 0 ? { wait: flags.wait } : {},
19241
20169
  ...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
19242
20170
  },
19243
- client: apiClient._sendClient,
20171
+ client: apiClient.client,
19244
20172
  responseStyle: "fields"
19245
20173
  });
19246
20174
  if (result.error) {
@@ -19311,9 +20239,9 @@ function acquireCredentialsLock(configDir) {
19311
20239
  function commonOtpStartFlags() {
19312
20240
  return {
19313
20241
  "accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy" }),
19314
- "api-base-url-1": Flags.string({
20242
+ "api-base-url": Flags.string({
19315
20243
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
19316
- env: "PRIMITIVE_API_BASE_URL_1",
20244
+ env: "PRIMITIVE_API_BASE_URL",
19317
20245
  hidden: true
19318
20246
  }),
19319
20247
  "device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
@@ -19492,9 +20420,9 @@ var SigninOtpConfirmCommand = class extends Command {
19492
20420
  static summary = "Confirm OTP sign-in";
19493
20421
  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"];
19494
20422
  static flags = {
19495
- "api-base-url-1": Flags.string({
20423
+ "api-base-url": Flags.string({
19496
20424
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
19497
- env: "PRIMITIVE_API_BASE_URL_1",
20425
+ env: "PRIMITIVE_API_BASE_URL",
19498
20426
  hidden: true
19499
20427
  }),
19500
20428
  force: Flags.boolean({
@@ -19571,9 +20499,9 @@ var SigninOtpResendCommand = class extends Command {
19571
20499
  static description = "Resend the verification code for a pending OTP sign-in.";
19572
20500
  static summary = "Resend OTP sign-in code";
19573
20501
  static examples = ["<%= config.bin %> signin otp resend user@example.com"];
19574
- static flags = { "api-base-url-1": Flags.string({
20502
+ static flags = { "api-base-url": Flags.string({
19575
20503
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
19576
- env: "PRIMITIVE_API_BASE_URL_1",
20504
+ env: "PRIMITIVE_API_BASE_URL",
19577
20505
  hidden: true
19578
20506
  }) };
19579
20507
  async run() {
@@ -19659,14 +20587,9 @@ var WhoamiCommand = class WhoamiCommand extends Command {
19659
20587
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
19660
20588
  env: "PRIMITIVE_API_KEY"
19661
20589
  }),
19662
- "api-base-url-1": Flags.string({
19663
- description: API_BASE_URL_1_FLAG_DESCRIPTION,
19664
- env: "PRIMITIVE_API_BASE_URL_1",
19665
- hidden: true
19666
- }),
19667
- "api-base-url-2": Flags.string({
19668
- description: API_BASE_URL_2_FLAG_DESCRIPTION,
19669
- env: "PRIMITIVE_API_BASE_URL_2",
20590
+ "api-base-url": Flags.string({
20591
+ description: API_BASE_URL_FLAG_DESCRIPTION,
20592
+ env: "PRIMITIVE_API_BASE_URL",
19670
20593
  hidden: true
19671
20594
  }),
19672
20595
  json: Flags.boolean({ description: "Print the full account JSON response. Default output hides setup and billing internals." }),
@@ -19677,8 +20600,7 @@ var WhoamiCommand = class WhoamiCommand extends Command {
19677
20600
  await runWithTiming(flags.time, async () => {
19678
20601
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
19679
20602
  apiKey: flags["api-key"],
19680
- apiBaseUrl1: flags["api-base-url-1"],
19681
- apiBaseUrl2: flags["api-base-url-2"],
20603
+ apiBaseUrl: flags["api-base-url"],
19682
20604
  configDir: this.config.configDir
19683
20605
  });
19684
20606
  const result = await getAccount({
@@ -20002,14 +20924,17 @@ const COMMANDS = {
20002
20924
  "signup:confirm": SignupConfirmCommand,
20003
20925
  "signup:interactive": SignupInteractiveCommand,
20004
20926
  "signup:resend": SignupResendCommand,
20927
+ "signup:status": SignupStatusCommand,
20005
20928
  logout: LogoutCommand,
20006
20929
  whoami: WhoamiCommand,
20007
20930
  doctor: DoctorCommand,
20008
20931
  "emails:latest": EmailsLatestCommand,
20009
20932
  "emails:watch": EmailsWatchCommand,
20010
20933
  "emails:wait": EmailsWaitCommand,
20934
+ "semantic-search": SemanticSearchCommand,
20011
20935
  "domains:zone-file": DomainsZoneFileCommand,
20012
20936
  "domains:download-domain-zone-file": DomainsZoneFileCommand,
20937
+ "inbox:setup": InboxSetupCommand,
20013
20938
  "inbox:status": InboxStatusCommand,
20014
20939
  "inbox:get-inbox-status": InboxStatusCommand,
20015
20940
  "functions:init": FunctionsInitCommand,