@primitivedotdev/cli 0.34.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/oclif/index.js
CHANGED
|
@@ -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-
|
|
2
|
-
import { Args, Command, Errors, Flags } from "@oclif/core";
|
|
1
|
+
import { A as createClient, C as saveCliCredentials, D as loadChatConversationByLocalId, E as loadActiveChatState, O as saveActiveChatState, S as resolveCliAuth, T as deleteChatState, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as normalizeApiBaseUrl1, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as createConfig, k as PrimitiveApiClient, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as chatStatePath, x as normalizeApiBaseUrl2, y as loadCliCredentials } from "../cli-config-SktG2dzR.js";
|
|
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";
|
|
@@ -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
|
|
599
|
-
*
|
|
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
|
|
@@ -956,6 +957,42 @@ const sendEmail = (options) => (options.client ?? client).post({
|
|
|
956
957
|
}
|
|
957
958
|
});
|
|
958
959
|
/**
|
|
960
|
+
* Semantic search across received and sent mail
|
|
961
|
+
*
|
|
962
|
+
* Ranked search across both received and sent mail. The `mode`
|
|
963
|
+
* field selects the ranking strategy:
|
|
964
|
+
*
|
|
965
|
+
* - `keyword`: lexical full-text matching only (no embeddings).
|
|
966
|
+
* - `semantic`: meaning-based matching using vector embeddings.
|
|
967
|
+
* - `hybrid` (default): blends the semantic and keyword signals.
|
|
968
|
+
*
|
|
969
|
+
* Results are ordered by a relevance `score`. Every row reports the
|
|
970
|
+
* fields it matched (`matched_fields`), a match-centered excerpt per
|
|
971
|
+
* field (`snippets`), and a `score_breakdown` whose components account
|
|
972
|
+
* for the `score`. Page through results by passing the prior
|
|
973
|
+
* response's `meta.cursor` back as `cursor`.
|
|
974
|
+
*
|
|
975
|
+
* Requires the Pro plan and the `semantic_search_enabled`
|
|
976
|
+
* entitlement; callers without them receive `403`.
|
|
977
|
+
*
|
|
978
|
+
* Host routing: this operation is served only by the search host
|
|
979
|
+
* (`https://api.primitive.dev/v1`). The typed SDKs route it there
|
|
980
|
+
* automatically.
|
|
981
|
+
*
|
|
982
|
+
*/
|
|
983
|
+
const semanticSearch = (options) => (options.client ?? client).post({
|
|
984
|
+
security: [{
|
|
985
|
+
scheme: "bearer",
|
|
986
|
+
type: "http"
|
|
987
|
+
}],
|
|
988
|
+
url: "/semantic-search",
|
|
989
|
+
...options,
|
|
990
|
+
headers: {
|
|
991
|
+
...options.body !== void 0 && { "Content-Type": "application/json" },
|
|
992
|
+
...options.headers
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
/**
|
|
959
996
|
* List outbound sent emails
|
|
960
997
|
*
|
|
961
998
|
* Returns a paginated list of OUTBOUND emails the caller's
|
|
@@ -1378,6 +1415,10 @@ const openapiDocument = {
|
|
|
1378
1415
|
"name": "Emails",
|
|
1379
1416
|
"description": "List, inspect, and manage received emails"
|
|
1380
1417
|
},
|
|
1418
|
+
{
|
|
1419
|
+
"name": "Search",
|
|
1420
|
+
"description": "Semantic and hybrid search across received and sent mail"
|
|
1421
|
+
},
|
|
1381
1422
|
{
|
|
1382
1423
|
"name": "Sending",
|
|
1383
1424
|
"description": "Send outbound emails through the Primitive API"
|
|
@@ -2366,7 +2407,14 @@ const openapiDocument = {
|
|
|
2366
2407
|
"post": {
|
|
2367
2408
|
"operationId": "replyToEmail",
|
|
2368
2409
|
"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
|
|
2410
|
+
"description": "Sends an outbound reply to the inbound email identified by `id`.\nThreading headers (`In-Reply-To`, `References`), recipient\nderivation (Reply-To, then From, then bare sender), and the\n`Re:` subject prefix are all derived server-side from the\nstored inbound row. The request body carries only the message\nbody, optional From override, optional attachments, and optional\n`wait` flag; passing any header or recipient override is\nrejected by the schema (`additionalProperties: false`).\n\nForwards through the same gates as `/send-mail`: the response\nstatus, error envelope, and `idempotent_replay` flag mirror\nthe send-mail contract verbatim.\n",
|
|
2411
|
+
"servers": [{
|
|
2412
|
+
"url": "https://api.primitive.dev/v1",
|
|
2413
|
+
"description": "Attachments-supporting send host (recommended)"
|
|
2414
|
+
}, {
|
|
2415
|
+
"url": "https://www.primitive.dev/api/v1",
|
|
2416
|
+
"description": "Primary host (attachment-free replies only)"
|
|
2417
|
+
}],
|
|
2370
2418
|
"tags": ["Sending"],
|
|
2371
2419
|
"requestBody": {
|
|
2372
2420
|
"required": true,
|
|
@@ -2816,6 +2864,42 @@ const openapiDocument = {
|
|
|
2816
2864
|
"503": { "$ref": "#/components/responses/ServiceUnavailable" }
|
|
2817
2865
|
}
|
|
2818
2866
|
} },
|
|
2867
|
+
"/semantic-search": { "post": {
|
|
2868
|
+
"operationId": "semanticSearch",
|
|
2869
|
+
"summary": "Semantic search across received and sent mail",
|
|
2870
|
+
"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",
|
|
2871
|
+
"servers": [{
|
|
2872
|
+
"url": "https://api.primitive.dev/v1",
|
|
2873
|
+
"description": "Search host"
|
|
2874
|
+
}],
|
|
2875
|
+
"tags": ["Search"],
|
|
2876
|
+
"requestBody": {
|
|
2877
|
+
"required": true,
|
|
2878
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/SemanticSearchInput" } } }
|
|
2879
|
+
},
|
|
2880
|
+
"responses": {
|
|
2881
|
+
"200": {
|
|
2882
|
+
"description": "Ranked search results",
|
|
2883
|
+
"content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
|
|
2884
|
+
"type": "object",
|
|
2885
|
+
"properties": {
|
|
2886
|
+
"data": {
|
|
2887
|
+
"type": "array",
|
|
2888
|
+
"items": { "$ref": "#/components/schemas/SemanticSearchResult" }
|
|
2889
|
+
},
|
|
2890
|
+
"meta": { "$ref": "#/components/schemas/SemanticSearchMeta" }
|
|
2891
|
+
},
|
|
2892
|
+
"required": ["data", "meta"]
|
|
2893
|
+
}] } } }
|
|
2894
|
+
},
|
|
2895
|
+
"400": { "$ref": "#/components/responses/ValidationError" },
|
|
2896
|
+
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
2897
|
+
"403": { "$ref": "#/components/responses/Forbidden" },
|
|
2898
|
+
"429": { "$ref": "#/components/responses/RateLimited" },
|
|
2899
|
+
"500": { "$ref": "#/components/responses/InternalError" },
|
|
2900
|
+
"503": { "$ref": "#/components/responses/ServiceUnavailable" }
|
|
2901
|
+
}
|
|
2902
|
+
} },
|
|
2819
2903
|
"/sent-emails": { "get": {
|
|
2820
2904
|
"operationId": "listSentEmails",
|
|
2821
2905
|
"summary": "List outbound sent emails",
|
|
@@ -5660,6 +5744,236 @@ const openapiDocument = {
|
|
|
5660
5744
|
"body_size_bytes"
|
|
5661
5745
|
]
|
|
5662
5746
|
},
|
|
5747
|
+
"SemanticSearchField": {
|
|
5748
|
+
"type": "string",
|
|
5749
|
+
"enum": [
|
|
5750
|
+
"subject",
|
|
5751
|
+
"headers",
|
|
5752
|
+
"addresses",
|
|
5753
|
+
"body"
|
|
5754
|
+
],
|
|
5755
|
+
"description": "A searchable email field."
|
|
5756
|
+
},
|
|
5757
|
+
"SemanticSearchInput": {
|
|
5758
|
+
"type": "object",
|
|
5759
|
+
"properties": {
|
|
5760
|
+
"query": {
|
|
5761
|
+
"type": "string",
|
|
5762
|
+
"minLength": 1,
|
|
5763
|
+
"maxLength": 2048,
|
|
5764
|
+
"description": "Free-text query. Required for `semantic` and `hybrid` modes;\noptional for `keyword` mode.\n"
|
|
5765
|
+
},
|
|
5766
|
+
"mode": {
|
|
5767
|
+
"type": "string",
|
|
5768
|
+
"enum": [
|
|
5769
|
+
"hybrid",
|
|
5770
|
+
"semantic",
|
|
5771
|
+
"keyword"
|
|
5772
|
+
],
|
|
5773
|
+
"default": "hybrid",
|
|
5774
|
+
"description": "Ranking strategy. `keyword` is lexical only, `semantic` is\nembedding-based, `hybrid` blends both.\n"
|
|
5775
|
+
},
|
|
5776
|
+
"corpus": {
|
|
5777
|
+
"type": "array",
|
|
5778
|
+
"items": {
|
|
5779
|
+
"type": "string",
|
|
5780
|
+
"enum": ["inbound", "outbound"]
|
|
5781
|
+
},
|
|
5782
|
+
"minItems": 1,
|
|
5783
|
+
"maxItems": 2,
|
|
5784
|
+
"description": "Which mail to search. Defaults to both received (`inbound`)\nand sent (`outbound`).\n"
|
|
5785
|
+
},
|
|
5786
|
+
"search_in": {
|
|
5787
|
+
"type": "array",
|
|
5788
|
+
"items": { "$ref": "#/components/schemas/SemanticSearchField" },
|
|
5789
|
+
"description": "Restrict matching to these fields. Defaults to all."
|
|
5790
|
+
},
|
|
5791
|
+
"exclude": {
|
|
5792
|
+
"type": "array",
|
|
5793
|
+
"items": { "$ref": "#/components/schemas/SemanticSearchField" },
|
|
5794
|
+
"description": "Exclude these fields from matching."
|
|
5795
|
+
},
|
|
5796
|
+
"date_from": {
|
|
5797
|
+
"type": "string",
|
|
5798
|
+
"format": "date-time",
|
|
5799
|
+
"description": "Only include mail at or after this timestamp."
|
|
5800
|
+
},
|
|
5801
|
+
"date_to": {
|
|
5802
|
+
"type": "string",
|
|
5803
|
+
"format": "date-time",
|
|
5804
|
+
"description": "Only include mail at or before this timestamp."
|
|
5805
|
+
},
|
|
5806
|
+
"include": {
|
|
5807
|
+
"type": "array",
|
|
5808
|
+
"items": {
|
|
5809
|
+
"type": "string",
|
|
5810
|
+
"enum": ["coverage"]
|
|
5811
|
+
},
|
|
5812
|
+
"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"
|
|
5813
|
+
},
|
|
5814
|
+
"limit": {
|
|
5815
|
+
"type": "integer",
|
|
5816
|
+
"minimum": 1,
|
|
5817
|
+
"maximum": 100,
|
|
5818
|
+
"default": 10,
|
|
5819
|
+
"description": "Maximum number of results to return."
|
|
5820
|
+
},
|
|
5821
|
+
"cursor": {
|
|
5822
|
+
"type": "string",
|
|
5823
|
+
"description": "Opaque pagination cursor from a prior response's `meta.cursor`."
|
|
5824
|
+
}
|
|
5825
|
+
}
|
|
5826
|
+
},
|
|
5827
|
+
"SemanticSearchSnippet": {
|
|
5828
|
+
"type": "object",
|
|
5829
|
+
"properties": {
|
|
5830
|
+
"field": {
|
|
5831
|
+
"type": "string",
|
|
5832
|
+
"description": "The field this excerpt came from."
|
|
5833
|
+
},
|
|
5834
|
+
"text": {
|
|
5835
|
+
"type": "string",
|
|
5836
|
+
"description": "Plain-text excerpt centered on the match (no markup)."
|
|
5837
|
+
}
|
|
5838
|
+
},
|
|
5839
|
+
"required": ["field", "text"]
|
|
5840
|
+
},
|
|
5841
|
+
"SemanticSearchScoreBreakdown": {
|
|
5842
|
+
"type": "object",
|
|
5843
|
+
"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",
|
|
5844
|
+
"properties": {
|
|
5845
|
+
"semantic": { "type": ["number", "null"] },
|
|
5846
|
+
"keyword": { "type": ["number", "null"] },
|
|
5847
|
+
"field_boost": { "type": "number" },
|
|
5848
|
+
"recency": { "type": "number" }
|
|
5849
|
+
},
|
|
5850
|
+
"required": [
|
|
5851
|
+
"semantic",
|
|
5852
|
+
"keyword",
|
|
5853
|
+
"field_boost",
|
|
5854
|
+
"recency"
|
|
5855
|
+
]
|
|
5856
|
+
},
|
|
5857
|
+
"SemanticSearchResult": {
|
|
5858
|
+
"type": "object",
|
|
5859
|
+
"properties": {
|
|
5860
|
+
"source_type": {
|
|
5861
|
+
"type": "string",
|
|
5862
|
+
"enum": ["inbound_email", "sent_email"],
|
|
5863
|
+
"description": "Whether this row is a received or sent message."
|
|
5864
|
+
},
|
|
5865
|
+
"id": {
|
|
5866
|
+
"type": "string",
|
|
5867
|
+
"description": "Message id. Combine with `api_url` to fetch the full record."
|
|
5868
|
+
},
|
|
5869
|
+
"subject": { "type": ["string", "null"] },
|
|
5870
|
+
"from": { "type": ["string", "null"] },
|
|
5871
|
+
"to": { "type": ["string", "null"] },
|
|
5872
|
+
"timestamp": {
|
|
5873
|
+
"type": "string",
|
|
5874
|
+
"description": "Message timestamp (received_at for inbound, created_at for sent)."
|
|
5875
|
+
},
|
|
5876
|
+
"status": {
|
|
5877
|
+
"type": "string",
|
|
5878
|
+
"description": "Lifecycle status of the message."
|
|
5879
|
+
},
|
|
5880
|
+
"score": {
|
|
5881
|
+
"type": "number",
|
|
5882
|
+
"description": "Overall relevance score; the `score_breakdown` components account for it."
|
|
5883
|
+
},
|
|
5884
|
+
"semantic_score": {
|
|
5885
|
+
"type": ["number", "null"],
|
|
5886
|
+
"description": "Raw semantic similarity signal, or null when not applicable."
|
|
5887
|
+
},
|
|
5888
|
+
"keyword_score": {
|
|
5889
|
+
"type": ["number", "null"],
|
|
5890
|
+
"description": "Raw keyword (lexical) signal, or null when not applicable."
|
|
5891
|
+
},
|
|
5892
|
+
"matched_fields": {
|
|
5893
|
+
"type": "array",
|
|
5894
|
+
"items": { "$ref": "#/components/schemas/SemanticSearchField" },
|
|
5895
|
+
"description": "Fields where the query matched."
|
|
5896
|
+
},
|
|
5897
|
+
"snippets": {
|
|
5898
|
+
"type": "array",
|
|
5899
|
+
"items": { "$ref": "#/components/schemas/SemanticSearchSnippet" },
|
|
5900
|
+
"description": "Match-centered excerpts, one per matched field."
|
|
5901
|
+
},
|
|
5902
|
+
"score_breakdown": { "$ref": "#/components/schemas/SemanticSearchScoreBreakdown" },
|
|
5903
|
+
"api_url": {
|
|
5904
|
+
"type": ["string", "null"],
|
|
5905
|
+
"description": "Relative API path to fetch the full message."
|
|
5906
|
+
}
|
|
5907
|
+
},
|
|
5908
|
+
"required": [
|
|
5909
|
+
"source_type",
|
|
5910
|
+
"id",
|
|
5911
|
+
"subject",
|
|
5912
|
+
"from",
|
|
5913
|
+
"to",
|
|
5914
|
+
"timestamp",
|
|
5915
|
+
"status",
|
|
5916
|
+
"score",
|
|
5917
|
+
"semantic_score",
|
|
5918
|
+
"keyword_score",
|
|
5919
|
+
"matched_fields",
|
|
5920
|
+
"snippets",
|
|
5921
|
+
"score_breakdown",
|
|
5922
|
+
"api_url"
|
|
5923
|
+
]
|
|
5924
|
+
},
|
|
5925
|
+
"SemanticSearchCoverage": {
|
|
5926
|
+
"type": "object",
|
|
5927
|
+
"description": "Index-coverage snapshot for the org, returned only when the `coverage` include option is requested.",
|
|
5928
|
+
"properties": {
|
|
5929
|
+
"embedded_chunks": { "type": "integer" },
|
|
5930
|
+
"pending_chunks": { "type": "integer" },
|
|
5931
|
+
"skipped_plan_chunks": { "type": "integer" },
|
|
5932
|
+
"skipped_quota_chunks": { "type": "integer" },
|
|
5933
|
+
"unsupported_attachment_chunks": { "type": "integer" },
|
|
5934
|
+
"failed_chunks": { "type": "integer" }
|
|
5935
|
+
},
|
|
5936
|
+
"required": [
|
|
5937
|
+
"embedded_chunks",
|
|
5938
|
+
"pending_chunks",
|
|
5939
|
+
"skipped_plan_chunks",
|
|
5940
|
+
"skipped_quota_chunks",
|
|
5941
|
+
"unsupported_attachment_chunks",
|
|
5942
|
+
"failed_chunks"
|
|
5943
|
+
]
|
|
5944
|
+
},
|
|
5945
|
+
"SemanticSearchMeta": {
|
|
5946
|
+
"type": "object",
|
|
5947
|
+
"properties": {
|
|
5948
|
+
"limit": {
|
|
5949
|
+
"type": "integer",
|
|
5950
|
+
"description": "Page size used for this request."
|
|
5951
|
+
},
|
|
5952
|
+
"cursor": {
|
|
5953
|
+
"type": ["string", "null"],
|
|
5954
|
+
"description": "Cursor for the next page, or null if there are no more results."
|
|
5955
|
+
},
|
|
5956
|
+
"mode": {
|
|
5957
|
+
"type": "string",
|
|
5958
|
+
"enum": [
|
|
5959
|
+
"hybrid",
|
|
5960
|
+
"semantic",
|
|
5961
|
+
"keyword"
|
|
5962
|
+
],
|
|
5963
|
+
"description": "Ranking mode used for this response."
|
|
5964
|
+
},
|
|
5965
|
+
"coverage": {
|
|
5966
|
+
"oneOf": [{ "$ref": "#/components/schemas/SemanticSearchCoverage" }, { "type": "null" }],
|
|
5967
|
+
"description": "Index-coverage snapshot, present only when requested via\n`include: [coverage]`; otherwise null.\n"
|
|
5968
|
+
}
|
|
5969
|
+
},
|
|
5970
|
+
"required": [
|
|
5971
|
+
"limit",
|
|
5972
|
+
"cursor",
|
|
5973
|
+
"mode",
|
|
5974
|
+
"coverage"
|
|
5975
|
+
]
|
|
5976
|
+
},
|
|
5663
5977
|
"SentEmailDetail": {
|
|
5664
5978
|
"description": "Full sent-email record, including `body_text` and\n`body_html`. Returned by /sent-emails/{id}.\n",
|
|
5665
5979
|
"allOf": [{ "$ref": "#/components/schemas/SentEmailSummary" }, {
|
|
@@ -5698,6 +6012,12 @@ const openapiDocument = {
|
|
|
5698
6012
|
"wait": {
|
|
5699
6013
|
"type": "boolean",
|
|
5700
6014
|
"description": "When true, wait for the first downstream SMTP delivery outcome before returning, mirroring the send-mail `wait` semantics."
|
|
6015
|
+
},
|
|
6016
|
+
"attachments": {
|
|
6017
|
+
"type": "array",
|
|
6018
|
+
"maxItems": 100,
|
|
6019
|
+
"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.",
|
|
6020
|
+
"items": { "$ref": "#/components/schemas/SendMailAttachment" }
|
|
5701
6021
|
}
|
|
5702
6022
|
}
|
|
5703
6023
|
},
|
|
@@ -11458,70 +11778,282 @@ const operationManifest = [
|
|
|
11458
11778
|
},
|
|
11459
11779
|
{
|
|
11460
11780
|
"binaryResponse": false,
|
|
11461
|
-
"bodyRequired":
|
|
11462
|
-
"command": "
|
|
11463
|
-
"description": "
|
|
11464
|
-
"hasJsonBody":
|
|
11465
|
-
"method": "
|
|
11466
|
-
"operationId": "
|
|
11467
|
-
"path": "/
|
|
11781
|
+
"bodyRequired": true,
|
|
11782
|
+
"command": "semantic-search",
|
|
11783
|
+
"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",
|
|
11784
|
+
"hasJsonBody": true,
|
|
11785
|
+
"method": "POST",
|
|
11786
|
+
"operationId": "semanticSearch",
|
|
11787
|
+
"path": "/semantic-search",
|
|
11468
11788
|
"pathParams": [],
|
|
11469
11789
|
"queryParams": [],
|
|
11470
|
-
"requestSchema":
|
|
11471
|
-
|
|
11472
|
-
"
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
"
|
|
11477
|
-
"
|
|
11478
|
-
"any_recipient": "#/components/schemas/SendPermissionAnyRecipient",
|
|
11479
|
-
"managed_zone": "#/components/schemas/SendPermissionManagedZone",
|
|
11480
|
-
"your_domain": "#/components/schemas/SendPermissionYourDomain",
|
|
11481
|
-
"address": "#/components/schemas/SendPermissionAddress"
|
|
11482
|
-
}
|
|
11790
|
+
"requestSchema": {
|
|
11791
|
+
"type": "object",
|
|
11792
|
+
"properties": {
|
|
11793
|
+
"query": {
|
|
11794
|
+
"type": "string",
|
|
11795
|
+
"minLength": 1,
|
|
11796
|
+
"maxLength": 2048,
|
|
11797
|
+
"description": "Free-text query. Required for `semantic` and `hybrid` modes;\noptional for `keyword` mode.\n"
|
|
11483
11798
|
},
|
|
11484
|
-
"
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
"
|
|
11488
|
-
"
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
"
|
|
11799
|
+
"mode": {
|
|
11800
|
+
"type": "string",
|
|
11801
|
+
"enum": [
|
|
11802
|
+
"hybrid",
|
|
11803
|
+
"semantic",
|
|
11804
|
+
"keyword"
|
|
11805
|
+
],
|
|
11806
|
+
"default": "hybrid",
|
|
11807
|
+
"description": "Ranking strategy. `keyword` is lexical only, `semantic` is\nembedding-based, `hybrid` blends both.\n"
|
|
11808
|
+
},
|
|
11809
|
+
"corpus": {
|
|
11810
|
+
"type": "array",
|
|
11811
|
+
"items": {
|
|
11812
|
+
"type": "string",
|
|
11813
|
+
"enum": ["inbound", "outbound"]
|
|
11499
11814
|
},
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
|
|
11512
|
-
"
|
|
11513
|
-
|
|
11514
|
-
|
|
11515
|
-
}
|
|
11516
|
-
},
|
|
11517
|
-
"required": [
|
|
11518
|
-
"type",
|
|
11519
|
-
"zone",
|
|
11520
|
-
"description"
|
|
11521
|
-
]
|
|
11815
|
+
"minItems": 1,
|
|
11816
|
+
"maxItems": 2,
|
|
11817
|
+
"description": "Which mail to search. Defaults to both received (`inbound`)\nand sent (`outbound`).\n"
|
|
11818
|
+
},
|
|
11819
|
+
"search_in": {
|
|
11820
|
+
"type": "array",
|
|
11821
|
+
"items": {
|
|
11822
|
+
"type": "string",
|
|
11823
|
+
"enum": [
|
|
11824
|
+
"subject",
|
|
11825
|
+
"headers",
|
|
11826
|
+
"addresses",
|
|
11827
|
+
"body"
|
|
11828
|
+
],
|
|
11829
|
+
"description": "A searchable email field."
|
|
11522
11830
|
},
|
|
11523
|
-
|
|
11524
|
-
|
|
11831
|
+
"description": "Restrict matching to these fields. Defaults to all."
|
|
11832
|
+
},
|
|
11833
|
+
"exclude": {
|
|
11834
|
+
"type": "array",
|
|
11835
|
+
"items": {
|
|
11836
|
+
"type": "string",
|
|
11837
|
+
"enum": [
|
|
11838
|
+
"subject",
|
|
11839
|
+
"headers",
|
|
11840
|
+
"addresses",
|
|
11841
|
+
"body"
|
|
11842
|
+
],
|
|
11843
|
+
"description": "A searchable email field."
|
|
11844
|
+
},
|
|
11845
|
+
"description": "Exclude these fields from matching."
|
|
11846
|
+
},
|
|
11847
|
+
"date_from": {
|
|
11848
|
+
"type": "string",
|
|
11849
|
+
"format": "date-time",
|
|
11850
|
+
"description": "Only include mail at or after this timestamp."
|
|
11851
|
+
},
|
|
11852
|
+
"date_to": {
|
|
11853
|
+
"type": "string",
|
|
11854
|
+
"format": "date-time",
|
|
11855
|
+
"description": "Only include mail at or before this timestamp."
|
|
11856
|
+
},
|
|
11857
|
+
"include": {
|
|
11858
|
+
"type": "array",
|
|
11859
|
+
"items": {
|
|
11860
|
+
"type": "string",
|
|
11861
|
+
"enum": ["coverage"]
|
|
11862
|
+
},
|
|
11863
|
+
"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"
|
|
11864
|
+
},
|
|
11865
|
+
"limit": {
|
|
11866
|
+
"type": "integer",
|
|
11867
|
+
"minimum": 1,
|
|
11868
|
+
"maximum": 100,
|
|
11869
|
+
"default": 10,
|
|
11870
|
+
"description": "Maximum number of results to return."
|
|
11871
|
+
},
|
|
11872
|
+
"cursor": {
|
|
11873
|
+
"type": "string",
|
|
11874
|
+
"description": "Opaque pagination cursor from a prior response's `meta.cursor`."
|
|
11875
|
+
}
|
|
11876
|
+
}
|
|
11877
|
+
},
|
|
11878
|
+
"responseSchema": {
|
|
11879
|
+
"type": "array",
|
|
11880
|
+
"items": {
|
|
11881
|
+
"type": "object",
|
|
11882
|
+
"properties": {
|
|
11883
|
+
"source_type": {
|
|
11884
|
+
"type": "string",
|
|
11885
|
+
"enum": ["inbound_email", "sent_email"],
|
|
11886
|
+
"description": "Whether this row is a received or sent message."
|
|
11887
|
+
},
|
|
11888
|
+
"id": {
|
|
11889
|
+
"type": "string",
|
|
11890
|
+
"description": "Message id. Combine with `api_url` to fetch the full record."
|
|
11891
|
+
},
|
|
11892
|
+
"subject": { "type": ["string", "null"] },
|
|
11893
|
+
"from": { "type": ["string", "null"] },
|
|
11894
|
+
"to": { "type": ["string", "null"] },
|
|
11895
|
+
"timestamp": {
|
|
11896
|
+
"type": "string",
|
|
11897
|
+
"description": "Message timestamp (received_at for inbound, created_at for sent)."
|
|
11898
|
+
},
|
|
11899
|
+
"status": {
|
|
11900
|
+
"type": "string",
|
|
11901
|
+
"description": "Lifecycle status of the message."
|
|
11902
|
+
},
|
|
11903
|
+
"score": {
|
|
11904
|
+
"type": "number",
|
|
11905
|
+
"description": "Overall relevance score; the `score_breakdown` components account for it."
|
|
11906
|
+
},
|
|
11907
|
+
"semantic_score": {
|
|
11908
|
+
"type": ["number", "null"],
|
|
11909
|
+
"description": "Raw semantic similarity signal, or null when not applicable."
|
|
11910
|
+
},
|
|
11911
|
+
"keyword_score": {
|
|
11912
|
+
"type": ["number", "null"],
|
|
11913
|
+
"description": "Raw keyword (lexical) signal, or null when not applicable."
|
|
11914
|
+
},
|
|
11915
|
+
"matched_fields": {
|
|
11916
|
+
"type": "array",
|
|
11917
|
+
"items": {
|
|
11918
|
+
"type": "string",
|
|
11919
|
+
"enum": [
|
|
11920
|
+
"subject",
|
|
11921
|
+
"headers",
|
|
11922
|
+
"addresses",
|
|
11923
|
+
"body"
|
|
11924
|
+
],
|
|
11925
|
+
"description": "A searchable email field."
|
|
11926
|
+
},
|
|
11927
|
+
"description": "Fields where the query matched."
|
|
11928
|
+
},
|
|
11929
|
+
"snippets": {
|
|
11930
|
+
"type": "array",
|
|
11931
|
+
"items": {
|
|
11932
|
+
"type": "object",
|
|
11933
|
+
"properties": {
|
|
11934
|
+
"field": {
|
|
11935
|
+
"type": "string",
|
|
11936
|
+
"description": "The field this excerpt came from."
|
|
11937
|
+
},
|
|
11938
|
+
"text": {
|
|
11939
|
+
"type": "string",
|
|
11940
|
+
"description": "Plain-text excerpt centered on the match (no markup)."
|
|
11941
|
+
}
|
|
11942
|
+
},
|
|
11943
|
+
"required": ["field", "text"]
|
|
11944
|
+
},
|
|
11945
|
+
"description": "Match-centered excerpts, one per matched field."
|
|
11946
|
+
},
|
|
11947
|
+
"score_breakdown": {
|
|
11948
|
+
"type": "object",
|
|
11949
|
+
"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",
|
|
11950
|
+
"properties": {
|
|
11951
|
+
"semantic": { "type": ["number", "null"] },
|
|
11952
|
+
"keyword": { "type": ["number", "null"] },
|
|
11953
|
+
"field_boost": { "type": "number" },
|
|
11954
|
+
"recency": { "type": "number" }
|
|
11955
|
+
},
|
|
11956
|
+
"required": [
|
|
11957
|
+
"semantic",
|
|
11958
|
+
"keyword",
|
|
11959
|
+
"field_boost",
|
|
11960
|
+
"recency"
|
|
11961
|
+
]
|
|
11962
|
+
},
|
|
11963
|
+
"api_url": {
|
|
11964
|
+
"type": ["string", "null"],
|
|
11965
|
+
"description": "Relative API path to fetch the full message."
|
|
11966
|
+
}
|
|
11967
|
+
},
|
|
11968
|
+
"required": [
|
|
11969
|
+
"source_type",
|
|
11970
|
+
"id",
|
|
11971
|
+
"subject",
|
|
11972
|
+
"from",
|
|
11973
|
+
"to",
|
|
11974
|
+
"timestamp",
|
|
11975
|
+
"status",
|
|
11976
|
+
"score",
|
|
11977
|
+
"semantic_score",
|
|
11978
|
+
"keyword_score",
|
|
11979
|
+
"matched_fields",
|
|
11980
|
+
"snippets",
|
|
11981
|
+
"score_breakdown",
|
|
11982
|
+
"api_url"
|
|
11983
|
+
]
|
|
11984
|
+
}
|
|
11985
|
+
},
|
|
11986
|
+
"sdkName": "semanticSearch",
|
|
11987
|
+
"summary": "Semantic search across received and sent mail",
|
|
11988
|
+
"tag": "Search",
|
|
11989
|
+
"tagCommand": "search"
|
|
11990
|
+
},
|
|
11991
|
+
{
|
|
11992
|
+
"binaryResponse": false,
|
|
11993
|
+
"bodyRequired": false,
|
|
11994
|
+
"command": "get-send-permissions",
|
|
11995
|
+
"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",
|
|
11996
|
+
"hasJsonBody": false,
|
|
11997
|
+
"method": "GET",
|
|
11998
|
+
"operationId": "getSendPermissions",
|
|
11999
|
+
"path": "/send-permissions",
|
|
12000
|
+
"pathParams": [],
|
|
12001
|
+
"queryParams": [],
|
|
12002
|
+
"requestSchema": null,
|
|
12003
|
+
"responseSchema": {
|
|
12004
|
+
"type": "array",
|
|
12005
|
+
"items": {
|
|
12006
|
+
"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",
|
|
12007
|
+
"discriminator": {
|
|
12008
|
+
"propertyName": "type",
|
|
12009
|
+
"mapping": {
|
|
12010
|
+
"any_recipient": "#/components/schemas/SendPermissionAnyRecipient",
|
|
12011
|
+
"managed_zone": "#/components/schemas/SendPermissionManagedZone",
|
|
12012
|
+
"your_domain": "#/components/schemas/SendPermissionYourDomain",
|
|
12013
|
+
"address": "#/components/schemas/SendPermissionAddress"
|
|
12014
|
+
}
|
|
12015
|
+
},
|
|
12016
|
+
"oneOf": [
|
|
12017
|
+
{
|
|
12018
|
+
"type": "object",
|
|
12019
|
+
"description": "The caller can send to any recipient. When this rule is\npresent, every other rule in the response is redundant.\n",
|
|
12020
|
+
"properties": {
|
|
12021
|
+
"type": {
|
|
12022
|
+
"type": "string",
|
|
12023
|
+
"enum": ["any_recipient"]
|
|
12024
|
+
},
|
|
12025
|
+
"description": {
|
|
12026
|
+
"type": "string",
|
|
12027
|
+
"description": "Human-prose summary of the rule."
|
|
12028
|
+
}
|
|
12029
|
+
},
|
|
12030
|
+
"required": ["type", "description"]
|
|
12031
|
+
},
|
|
12032
|
+
{
|
|
12033
|
+
"type": "object",
|
|
12034
|
+
"description": "The caller can send to any address at the named\nPrimitive-managed zone. Always emitted (no entitlement\nrequired) because Primitive owns the zone and every mailbox\nbelongs to a Primitive customer by construction.\n",
|
|
12035
|
+
"properties": {
|
|
12036
|
+
"type": {
|
|
12037
|
+
"type": "string",
|
|
12038
|
+
"enum": ["managed_zone"]
|
|
12039
|
+
},
|
|
12040
|
+
"zone": {
|
|
12041
|
+
"type": "string",
|
|
12042
|
+
"description": "The managed apex domain. Sends are accepted to any\naddress at the apex itself or any subdomain (e.g.\n`alice@primitive.email` and `alice@acme.primitive.email`\nboth match the `primitive.email` zone rule).\n"
|
|
12043
|
+
},
|
|
12044
|
+
"description": {
|
|
12045
|
+
"type": "string",
|
|
12046
|
+
"description": "Human-prose summary of the rule."
|
|
12047
|
+
}
|
|
12048
|
+
},
|
|
12049
|
+
"required": [
|
|
12050
|
+
"type",
|
|
12051
|
+
"zone",
|
|
12052
|
+
"description"
|
|
12053
|
+
]
|
|
12054
|
+
},
|
|
12055
|
+
{
|
|
12056
|
+
"type": "object",
|
|
11525
12057
|
"description": "The caller can send to any address at one of their own\nverified outbound domains. Emitted once per active row in\nthe org's `domains` table.\n",
|
|
11526
12058
|
"properties": {
|
|
11527
12059
|
"type": {
|
|
@@ -12108,7 +12640,7 @@ const operationManifest = [
|
|
|
12108
12640
|
"binaryResponse": false,
|
|
12109
12641
|
"bodyRequired": true,
|
|
12110
12642
|
"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
|
|
12643
|
+
"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
12644
|
"hasJsonBody": true,
|
|
12113
12645
|
"method": "POST",
|
|
12114
12646
|
"operationId": "replyToEmail",
|
|
@@ -12143,6 +12675,36 @@ const operationManifest = [
|
|
|
12143
12675
|
"wait": {
|
|
12144
12676
|
"type": "boolean",
|
|
12145
12677
|
"description": "When true, wait for the first downstream SMTP delivery outcome before returning, mirroring the send-mail `wait` semantics."
|
|
12678
|
+
},
|
|
12679
|
+
"attachments": {
|
|
12680
|
+
"type": "array",
|
|
12681
|
+
"maxItems": 100,
|
|
12682
|
+
"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.",
|
|
12683
|
+
"items": {
|
|
12684
|
+
"type": "object",
|
|
12685
|
+
"additionalProperties": false,
|
|
12686
|
+
"properties": {
|
|
12687
|
+
"filename": {
|
|
12688
|
+
"type": "string",
|
|
12689
|
+
"minLength": 1,
|
|
12690
|
+
"maxLength": 255,
|
|
12691
|
+
"description": "Attachment filename. Control characters are rejected."
|
|
12692
|
+
},
|
|
12693
|
+
"content_type": {
|
|
12694
|
+
"type": "string",
|
|
12695
|
+
"minLength": 1,
|
|
12696
|
+
"maxLength": 255,
|
|
12697
|
+
"description": "Optional MIME content type. Control characters are rejected."
|
|
12698
|
+
},
|
|
12699
|
+
"content_base64": {
|
|
12700
|
+
"type": "string",
|
|
12701
|
+
"minLength": 1,
|
|
12702
|
+
"maxLength": 44040192,
|
|
12703
|
+
"description": "Base64-encoded attachment bytes."
|
|
12704
|
+
}
|
|
12705
|
+
},
|
|
12706
|
+
"required": ["filename", "content_base64"]
|
|
12707
|
+
}
|
|
12146
12708
|
}
|
|
12147
12709
|
}
|
|
12148
12710
|
},
|
|
@@ -13249,7 +13811,7 @@ async function runWithTiming(enabled, fn) {
|
|
|
13249
13811
|
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
13812
|
const API_BASE_URL_1_FLAG_DESCRIPTION = "Override the primary API base URL. Internal testing only; not documented to customers.";
|
|
13251
13813
|
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"]);
|
|
13814
|
+
const HOST_2_OPERATIONS = new Set(["sendEmail", "replyToEmail"]);
|
|
13253
13815
|
const RESERVED_FLAG_NAMES = new Set([
|
|
13254
13816
|
"api-key",
|
|
13255
13817
|
"api-base-url-1",
|
|
@@ -13480,6 +14042,39 @@ function canonicalizeCliReferences(description) {
|
|
|
13480
14042
|
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
14043
|
}
|
|
13482
14044
|
//#endregion
|
|
14045
|
+
//#region src/oclif/attachments.ts
|
|
14046
|
+
function readAttachmentBytes(path, readFile) {
|
|
14047
|
+
try {
|
|
14048
|
+
return Buffer.from(readFile(path));
|
|
14049
|
+
} catch (error) {
|
|
14050
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
14051
|
+
throw new Errors.CLIError(`Could not read --attachment ${path}: ${detail}`, { exit: 1 });
|
|
14052
|
+
}
|
|
14053
|
+
}
|
|
14054
|
+
function hasControlCharacter(value) {
|
|
14055
|
+
return Array.from(value).some((character) => {
|
|
14056
|
+
const code = character.charCodeAt(0);
|
|
14057
|
+
return code <= 31 || code >= 127 && code <= 159;
|
|
14058
|
+
});
|
|
14059
|
+
}
|
|
14060
|
+
function validateAttachmentFilename(path, filename) {
|
|
14061
|
+
if (!filename) throw new Errors.CLIError(`Could not derive an attachment filename from ${path}. Pass a file path.`, { exit: 1 });
|
|
14062
|
+
if (hasControlCharacter(filename)) throw new Errors.CLIError(`Attachment filename ${filename} contains control characters.`, { exit: 1 });
|
|
14063
|
+
}
|
|
14064
|
+
function readAttachmentFiles(paths, readFile = readFileSync) {
|
|
14065
|
+
if (!paths || paths.length === 0) return void 0;
|
|
14066
|
+
return paths.map((path) => {
|
|
14067
|
+
const filename = basename(path);
|
|
14068
|
+
validateAttachmentFilename(path, filename);
|
|
14069
|
+
const bytes = readAttachmentBytes(path, readFile);
|
|
14070
|
+
if (bytes.length === 0) throw new Errors.CLIError(`Attachment file ${path} is empty. Attachments must contain at least one byte.`, { exit: 1 });
|
|
14071
|
+
return {
|
|
14072
|
+
content_base64: bytes.toString("base64"),
|
|
14073
|
+
filename
|
|
14074
|
+
};
|
|
14075
|
+
});
|
|
14076
|
+
}
|
|
14077
|
+
//#endregion
|
|
13483
14078
|
//#region src/oclif/outbound-defaults.ts
|
|
13484
14079
|
const SUBJECT_MAX_LENGTH = 200;
|
|
13485
14080
|
function deriveSubject(body) {
|
|
@@ -13630,6 +14225,30 @@ async function readStdinToString(missingMessage = "No message provided. Pass the
|
|
|
13630
14225
|
for await (const chunk of process.stdin) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
13631
14226
|
return Buffer.concat(chunks).toString("utf8");
|
|
13632
14227
|
}
|
|
14228
|
+
function chatColor(color, text) {
|
|
14229
|
+
return ux.colorize(color, text);
|
|
14230
|
+
}
|
|
14231
|
+
function chatCommandText(command) {
|
|
14232
|
+
return chatColor("cyan", command);
|
|
14233
|
+
}
|
|
14234
|
+
function chatDetailLine(line) {
|
|
14235
|
+
return chatColor("dim", line);
|
|
14236
|
+
}
|
|
14237
|
+
function chatFailureText(message) {
|
|
14238
|
+
return chatColor("red", message);
|
|
14239
|
+
}
|
|
14240
|
+
function chatHeading(text) {
|
|
14241
|
+
return chatColor("bold", text);
|
|
14242
|
+
}
|
|
14243
|
+
function chatNoticeText(message) {
|
|
14244
|
+
return chatColor("yellow", message);
|
|
14245
|
+
}
|
|
14246
|
+
function chatProgressText(message) {
|
|
14247
|
+
return chatColor("cyan", message);
|
|
14248
|
+
}
|
|
14249
|
+
function chatSuccessText(message) {
|
|
14250
|
+
return chatColor("greenBright", message);
|
|
14251
|
+
}
|
|
13633
14252
|
var ChatProgressIndicator = class {
|
|
13634
14253
|
currentMessage = null;
|
|
13635
14254
|
frameIndex = 0;
|
|
@@ -13650,7 +14269,7 @@ var ChatProgressIndicator = class {
|
|
|
13650
14269
|
this.timer.unref?.();
|
|
13651
14270
|
return;
|
|
13652
14271
|
}
|
|
13653
|
-
this.stream.write(`${message}\n`);
|
|
14272
|
+
this.stream.write(`${chatProgressText(message)}\n`);
|
|
13654
14273
|
}
|
|
13655
14274
|
update(message, options = {}) {
|
|
13656
14275
|
this.currentMessage = message;
|
|
@@ -13663,10 +14282,10 @@ var ChatProgressIndicator = class {
|
|
|
13663
14282
|
return;
|
|
13664
14283
|
}
|
|
13665
14284
|
this.stopTimer();
|
|
13666
|
-
this.stream.write(`${message}\n`);
|
|
14285
|
+
this.stream.write(`${chatProgressText(message)}\n`);
|
|
13667
14286
|
if (options.heartbeatMs !== void 0) {
|
|
13668
14287
|
this.timer = setInterval(() => {
|
|
13669
|
-
this.stream.write(`${formatWaitingHeartbeat(message, this.now() - this.startedAt, options.timeoutSeconds)}\n`);
|
|
14288
|
+
this.stream.write(`${chatProgressText(formatWaitingHeartbeat(message, this.now() - this.startedAt, options.timeoutSeconds))}\n`);
|
|
13670
14289
|
}, options.heartbeatMs);
|
|
13671
14290
|
this.timer.unref?.();
|
|
13672
14291
|
}
|
|
@@ -13675,17 +14294,17 @@ var ChatProgressIndicator = class {
|
|
|
13675
14294
|
if (this.stream.isTTY) {
|
|
13676
14295
|
const currentMessage = this.currentMessage;
|
|
13677
14296
|
this.clearLine();
|
|
13678
|
-
this.stream.write(`${message}\n`);
|
|
14297
|
+
this.stream.write(`${chatNoticeText(message)}\n`);
|
|
13679
14298
|
if (currentMessage !== null && this.timer !== null) this.render(currentMessage);
|
|
13680
14299
|
return;
|
|
13681
14300
|
}
|
|
13682
|
-
this.stream.write(`${message}\n`);
|
|
14301
|
+
this.stream.write(`${chatNoticeText(message)}\n`);
|
|
13683
14302
|
}
|
|
13684
14303
|
succeed(message) {
|
|
13685
|
-
this.finish(`${message} after ${formatElapsed(this.now() - this.startedAt)}.`);
|
|
14304
|
+
this.finish(chatSuccessText(`${message} after ${formatElapsed(this.now() - this.startedAt)}.`));
|
|
13686
14305
|
}
|
|
13687
14306
|
fail(message) {
|
|
13688
|
-
this.finish(message);
|
|
14307
|
+
this.finish(chatFailureText(message));
|
|
13689
14308
|
}
|
|
13690
14309
|
finish(message) {
|
|
13691
14310
|
this.stopTimer();
|
|
@@ -13702,9 +14321,10 @@ var ChatProgressIndicator = class {
|
|
|
13702
14321
|
];
|
|
13703
14322
|
const frame = frames[this.frameIndex % frames.length];
|
|
13704
14323
|
this.frameIndex += 1;
|
|
13705
|
-
const
|
|
13706
|
-
|
|
13707
|
-
this.
|
|
14324
|
+
const elapsed = `(${formatElapsed(this.now() - this.startedAt)})`;
|
|
14325
|
+
const plainLine = `${frame} ${message} ${elapsed}`;
|
|
14326
|
+
this.lastLineLength = Math.max(this.lastLineLength, plainLine.length);
|
|
14327
|
+
this.stream.write(`\r${chatProgressText(`${frame} ${message}`)} ${chatColor("dim", elapsed)}`);
|
|
13708
14328
|
}
|
|
13709
14329
|
clearLine() {
|
|
13710
14330
|
if (this.lastLineLength > 0) {
|
|
@@ -13950,45 +14570,46 @@ function formatChatResponse(context) {
|
|
|
13950
14570
|
const accepted = context.sent.accepted.join(", ") || context.recipient;
|
|
13951
14571
|
const responseBody = resolveChatResponseBody(context.reply);
|
|
13952
14572
|
const lines = [
|
|
13953
|
-
"Reply received",
|
|
14573
|
+
chatSuccessText("Reply received"),
|
|
13954
14574
|
"",
|
|
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}
|
|
14575
|
+
chatHeading("Sent"),
|
|
14576
|
+
chatDetailLine(` To: ${accepted}`),
|
|
14577
|
+
chatDetailLine(` From: ${context.sent.from || context.from}`),
|
|
14578
|
+
chatDetailLine(` Subject: ${context.subject}`),
|
|
14579
|
+
chatDetailLine(` Sent email id: ${context.sent.id}`),
|
|
14580
|
+
chatColor("green", ` Delivery status: ${context.sent.delivery_status ?? context.sent.status}`),
|
|
13961
14581
|
"",
|
|
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)}`
|
|
14582
|
+
chatHeading("Reply"),
|
|
14583
|
+
chatDetailLine(` Email id: ${context.reply.id}`),
|
|
14584
|
+
chatDetailLine(` From: ${context.reply.from_email}`),
|
|
14585
|
+
chatDetailLine(` To: ${context.reply.to_email}`),
|
|
14586
|
+
chatDetailLine(` Subject: ${context.reply.subject ?? "(no subject)"}`),
|
|
14587
|
+
chatDetailLine(` Received: ${context.reply.received_at}`),
|
|
14588
|
+
chatDetailLine(` Match: ${matchDescription(context.matchStrategy)}`)
|
|
13969
14589
|
];
|
|
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}
|
|
13975
|
-
lines.push("", `Response body (${responseBody.format}; use --json for parsing)
|
|
14590
|
+
if (context.reply.reply_to_sent_email_id) lines.push(chatDetailLine(` Reply to sent email id: ${context.reply.reply_to_sent_email_id}`));
|
|
14591
|
+
if (context.reply.message_id) lines.push(chatDetailLine(` Message-Id: ${context.reply.message_id}`));
|
|
14592
|
+
if (context.localChatId !== void 0) lines.push(chatDetailLine(` Local chat id: ${context.localChatId}`));
|
|
14593
|
+
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."));
|
|
14594
|
+
for (const { description, command } of buildChatFollowUpCommands(context)) lines.push(chatHeading(` ${description}:`), ` ${chatCommandText(command)}`);
|
|
14595
|
+
lines.push("", chatHeading(`Response body (${responseBody.format}; use --json for parsing)`), "----- BEGIN RESPONSE -----", responseBody.body || "(empty response)", "----- END RESPONSE -----");
|
|
13976
14596
|
return lines.join("\n");
|
|
13977
14597
|
}
|
|
13978
14598
|
function formatChatRecoveryContext(context) {
|
|
14599
|
+
const accepted = context.sent.accepted.join(", ") || context.recipient;
|
|
13979
14600
|
const lines = [
|
|
13980
14601
|
"",
|
|
13981
|
-
"Sent message context",
|
|
13982
|
-
` To: ${
|
|
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}
|
|
14602
|
+
chatHeading("Sent message context"),
|
|
14603
|
+
chatDetailLine(` To: ${accepted}`),
|
|
14604
|
+
chatDetailLine(` From: ${context.sent.from || context.from}`),
|
|
14605
|
+
chatDetailLine(` Subject: ${context.subject}`),
|
|
14606
|
+
chatDetailLine(` Sent email id: ${context.sent.id}`),
|
|
14607
|
+
chatColor("green", ` Delivery status: ${context.sent.delivery_status ?? context.sent.status}`),
|
|
14608
|
+
chatDetailLine(` Poll since: ${context.sentAtIso}`),
|
|
13988
14609
|
"",
|
|
13989
|
-
"Helpful recovery commands"
|
|
14610
|
+
chatHeading("Helpful recovery commands")
|
|
13990
14611
|
];
|
|
13991
|
-
for (const { description, command } of buildChatRecoveryCommands(context)) lines.push(` ${description}
|
|
14612
|
+
for (const { description, command } of buildChatRecoveryCommands(context)) lines.push(chatHeading(` ${description}:`), ` ${chatCommandText(command)}`);
|
|
13992
14613
|
return lines.join("\n");
|
|
13993
14614
|
}
|
|
13994
14615
|
async function loadInboundEmailDetail(params) {
|
|
@@ -14081,8 +14702,10 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14081
14702
|
"<%= config.bin %> chat help@agent.acme.dev 'how do I rotate my API key?'",
|
|
14082
14703
|
"cat error.log | <%= config.bin %> chat help@agent.acme.dev",
|
|
14083
14704
|
"<%= config.bin %> chat reply 'one more thing'",
|
|
14705
|
+
"<%= config.bin %> chat reply 'see attached' --attachment ./report.pdf",
|
|
14084
14706
|
"<%= config.bin %> chat help@agent.acme.dev --reply 'one more thing'",
|
|
14085
14707
|
"<%= config.bin %> chat help@agent.acme.dev --reply 'one more thing' --reply-to-email-id <inbound-email-id>",
|
|
14708
|
+
"<%= config.bin %> chat help@agent.acme.dev 'can you review this?' --attachment ./report.pdf",
|
|
14086
14709
|
"<%= config.bin %> chat help@agent.acme.dev 'follow up question' --json",
|
|
14087
14710
|
"<%= config.bin %> chat help@agent.acme.dev 'one more thing' --timeout 300"
|
|
14088
14711
|
];
|
|
@@ -14116,6 +14739,11 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14116
14739
|
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
14740
|
"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
14741
|
"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." }),
|
|
14742
|
+
attachment: Flags.string({
|
|
14743
|
+
char: "a",
|
|
14744
|
+
description: "Attach a file to this chat message. Repeat --attachment to attach multiple files.",
|
|
14745
|
+
multiple: true
|
|
14746
|
+
}),
|
|
14119
14747
|
"chat-local-id": Flags.integer({
|
|
14120
14748
|
description: "Local chat id to update after this command succeeds. Internal plumbing for `primitive chat reply`.",
|
|
14121
14749
|
hidden: true,
|
|
@@ -14169,6 +14797,7 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14169
14797
|
configDir: this.config.configDir
|
|
14170
14798
|
};
|
|
14171
14799
|
const progress = flags.quiet ? null : new ChatProgressIndicator(process.stderr);
|
|
14800
|
+
const attachments = readAttachmentFiles(flags.attachment);
|
|
14172
14801
|
let from;
|
|
14173
14802
|
let parentReply;
|
|
14174
14803
|
let subject;
|
|
@@ -14227,9 +14856,10 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14227
14856
|
const sendResult = parentReply !== void 0 ? await replyToEmail({
|
|
14228
14857
|
body: {
|
|
14229
14858
|
body_text: message,
|
|
14230
|
-
from
|
|
14859
|
+
from,
|
|
14860
|
+
...attachments !== void 0 ? { attachments } : {}
|
|
14231
14861
|
},
|
|
14232
|
-
client: apiClient.
|
|
14862
|
+
client: apiClient._sendClient,
|
|
14233
14863
|
path: { id: parentReply.id },
|
|
14234
14864
|
responseStyle: "fields"
|
|
14235
14865
|
}) : await sendEmail({
|
|
@@ -14238,7 +14868,8 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
14238
14868
|
to: args.recipient,
|
|
14239
14869
|
subject,
|
|
14240
14870
|
body_text: message,
|
|
14241
|
-
...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {}
|
|
14871
|
+
...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {},
|
|
14872
|
+
...attachments !== void 0 ? { attachments } : {}
|
|
14242
14873
|
},
|
|
14243
14874
|
client: apiClient._sendClient,
|
|
14244
14875
|
responseStyle: "fields"
|
|
@@ -14353,6 +14984,7 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
|
|
|
14353
14984
|
"<%= config.bin %> chat reply 'one more thing'",
|
|
14354
14985
|
"<%= config.bin %> chat reply 0 'one more thing'",
|
|
14355
14986
|
"<%= config.bin %> chat reply --id 0 'one more thing'",
|
|
14987
|
+
"<%= config.bin %> chat reply 'see attached' --attachment ./report.pdf",
|
|
14356
14988
|
"cat follow-up.txt | <%= config.bin %> chat reply"
|
|
14357
14989
|
];
|
|
14358
14990
|
static args = {
|
|
@@ -14389,6 +15021,11 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
|
|
|
14389
15021
|
min: 1
|
|
14390
15022
|
}),
|
|
14391
15023
|
"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." }),
|
|
15024
|
+
attachment: Flags.string({
|
|
15025
|
+
char: "a",
|
|
15026
|
+
description: "Attach a file to the reply. Repeat --attachment to attach multiple files.",
|
|
15027
|
+
multiple: true
|
|
15028
|
+
}),
|
|
14392
15029
|
interval: Flags.integer({
|
|
14393
15030
|
description: "Seconds between polls while waiting for the reply.",
|
|
14394
15031
|
min: 1
|
|
@@ -14435,6 +15072,7 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
|
|
|
14435
15072
|
if (flags["api-base-url-2"] !== void 0) argv.push("--api-base-url-2", flags["api-base-url-2"]);
|
|
14436
15073
|
if (flags.json) argv.push("--json");
|
|
14437
15074
|
if (flags.quiet) argv.push("--quiet");
|
|
15075
|
+
for (const attachment of flags.attachment ?? []) argv.push("--attachment", attachment);
|
|
14438
15076
|
if (state.strict_only || flags["strict-only"]) argv.push("--strict-only");
|
|
14439
15077
|
if (flags.time) argv.push("--time");
|
|
14440
15078
|
await ChatCommand.run(argv, { root: this.config.root });
|
|
@@ -16444,8 +17082,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
16444
17082
|
name: "Primitive Team",
|
|
16445
17083
|
url: "https://primitive.dev"
|
|
16446
17084
|
};
|
|
16447
|
-
const SDK_VERSION_RANGE = "^0.
|
|
16448
|
-
const CLI_VERSION_RANGE = "^0.
|
|
17085
|
+
const SDK_VERSION_RANGE = "^0.35.0";
|
|
17086
|
+
const CLI_VERSION_RANGE = "^0.35.0";
|
|
16449
17087
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
16450
17088
|
function renderHandler() {
|
|
16451
17089
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -17818,6 +18456,7 @@ const DOMAIN_DISPLAY_WIDTH = 34;
|
|
|
17818
18456
|
const STATUS_DISPLAY_WIDTH = 12;
|
|
17819
18457
|
const BOOL_DISPLAY_WIDTH = 7;
|
|
17820
18458
|
const NUM_DISPLAY_WIDTH = 6;
|
|
18459
|
+
const DEFAULT_PRIMITIVE_LOCAL_PART = "agent";
|
|
17821
18460
|
function plural(count, singular, pluralValue = `${singular}s`) {
|
|
17822
18461
|
return `${count} ${count === 1 ? singular : pluralValue}`;
|
|
17823
18462
|
}
|
|
@@ -17853,6 +18492,14 @@ function domainSummary(domain) {
|
|
|
17853
18492
|
default: return `${domain.domain} has status ${String(domain.status)}.`;
|
|
17854
18493
|
}
|
|
17855
18494
|
}
|
|
18495
|
+
function findSuggestedPrimitiveAddress(domains) {
|
|
18496
|
+
const domain = domains.find((entry) => entry.managed && entry.active && entry.receiving_ready);
|
|
18497
|
+
if (!domain) return null;
|
|
18498
|
+
return {
|
|
18499
|
+
address: `${DEFAULT_PRIMITIVE_LOCAL_PART}@${domain.domain}`,
|
|
18500
|
+
domain: domain.domain
|
|
18501
|
+
};
|
|
18502
|
+
}
|
|
17856
18503
|
function focusInboxStatus(status, domainName) {
|
|
17857
18504
|
const normalized = domainName.toLowerCase();
|
|
17858
18505
|
const domain = status.domains.find((entry) => entry.domain.toLowerCase() === normalized);
|
|
@@ -17899,12 +18546,14 @@ function formatInboxStatus(status) {
|
|
|
17899
18546
|
"",
|
|
17900
18547
|
"Domains"
|
|
17901
18548
|
];
|
|
18549
|
+
const suggestedAddress = findSuggestedPrimitiveAddress(status.domains);
|
|
17902
18550
|
if (status.domains.length === 0) lines.push("No domains configured.");
|
|
17903
18551
|
else {
|
|
17904
18552
|
lines.push(formatDomainHeader());
|
|
17905
18553
|
for (const domain of status.domains) lines.push(formatDomainRow(domain));
|
|
17906
18554
|
}
|
|
17907
18555
|
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)}`);
|
|
18556
|
+
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
18557
|
if (status.next_actions.length > 0) {
|
|
17909
18558
|
lines.push("", "Next actions");
|
|
17910
18559
|
for (const action of status.next_actions) lines.push(formatNextAction(action));
|
|
@@ -17981,6 +18630,180 @@ var InboxStatusCommand = class InboxStatusCommand extends Command {
|
|
|
17981
18630
|
}
|
|
17982
18631
|
};
|
|
17983
18632
|
//#endregion
|
|
18633
|
+
//#region src/oclif/commands/inbox-setup.ts
|
|
18634
|
+
const DEFAULT_FUNCTION_NAME = "inbound-reply";
|
|
18635
|
+
const DEFAULT_LOCAL_PART = "inbox";
|
|
18636
|
+
const FUNCTION_ID_PLACEHOLDER = "<function-id>";
|
|
18637
|
+
function firstUsableManagedDomain(status) {
|
|
18638
|
+
return status.domains.find((domain) => domain.managed && domain.receiving_ready && domain.active) ?? status.domains.find((domain) => domain.managed && domain.receiving_ready) ?? null;
|
|
18639
|
+
}
|
|
18640
|
+
function buildInboxSetupCommands(functionName = DEFAULT_FUNCTION_NAME) {
|
|
18641
|
+
return {
|
|
18642
|
+
scaffold: [
|
|
18643
|
+
`primitive functions init ${functionName}`,
|
|
18644
|
+
`cd ${functionName}`,
|
|
18645
|
+
"npm install",
|
|
18646
|
+
"npm run build",
|
|
18647
|
+
`primitive functions deploy --name ${functionName} --file ./dist/handler.js --wait`,
|
|
18648
|
+
`primitive functions test --id ${FUNCTION_ID_PLACEHOLDER} --wait --show-sends`
|
|
18649
|
+
],
|
|
18650
|
+
logs: `primitive functions logs --id ${FUNCTION_ID_PLACEHOLDER}`,
|
|
18651
|
+
status: "primitive inbox status"
|
|
18652
|
+
};
|
|
18653
|
+
}
|
|
18654
|
+
function buildInboxSetupProof(commands) {
|
|
18655
|
+
return {
|
|
18656
|
+
after_test: [
|
|
18657
|
+
"inbound id for the generated test email",
|
|
18658
|
+
"function id matching the deployed Function",
|
|
18659
|
+
"invocation status completed, failed, or send_failed",
|
|
18660
|
+
"reply/send result emitted by the handler"
|
|
18661
|
+
],
|
|
18662
|
+
logs_command: commands.logs
|
|
18663
|
+
};
|
|
18664
|
+
}
|
|
18665
|
+
function buildInboxSetupGuide(status) {
|
|
18666
|
+
const domain = firstUsableManagedDomain(status);
|
|
18667
|
+
const commands = buildInboxSetupCommands();
|
|
18668
|
+
const mode = !status.receiving_ready ? "not_receiving" : status.processing_ready ? "actively_processed" : "stored_only";
|
|
18669
|
+
return {
|
|
18670
|
+
readiness: {
|
|
18671
|
+
ready: status.ready,
|
|
18672
|
+
receiving_ready: status.receiving_ready,
|
|
18673
|
+
processing_ready: status.processing_ready,
|
|
18674
|
+
mode,
|
|
18675
|
+
summary: status.summary
|
|
18676
|
+
},
|
|
18677
|
+
receive: {
|
|
18678
|
+
address: domain ? `${DEFAULT_LOCAL_PART}@${domain.domain}` : null,
|
|
18679
|
+
domain: domain?.domain ?? null,
|
|
18680
|
+
managed: domain?.managed ?? false,
|
|
18681
|
+
placeholder_local_part: domain ? DEFAULT_LOCAL_PART : null
|
|
18682
|
+
},
|
|
18683
|
+
processing: {
|
|
18684
|
+
stored_only: status.receiving_ready && !status.processing_ready,
|
|
18685
|
+
active: status.processing_ready,
|
|
18686
|
+
enabled_endpoints: status.endpoints.enabled,
|
|
18687
|
+
deployed_functions: status.functions.deployed
|
|
18688
|
+
},
|
|
18689
|
+
commands,
|
|
18690
|
+
proof: buildInboxSetupProof(commands),
|
|
18691
|
+
status
|
|
18692
|
+
};
|
|
18693
|
+
}
|
|
18694
|
+
function formatReadiness(guide) {
|
|
18695
|
+
const readiness = guide.readiness.ready ? "ready" : "not ready";
|
|
18696
|
+
const receiving = guide.readiness.receiving_ready ? "yes" : "no";
|
|
18697
|
+
const processing = guide.readiness.processing_ready ? "yes" : "no";
|
|
18698
|
+
const mode = guide.readiness.mode === "actively_processed" ? "actively processed" : guide.readiness.mode === "stored_only" ? "stored-only" : "not receiving";
|
|
18699
|
+
return [
|
|
18700
|
+
`Readiness: ${readiness}`,
|
|
18701
|
+
`Receiving: ${receiving}`,
|
|
18702
|
+
`Processing: ${processing}`,
|
|
18703
|
+
`Mode: ${mode}`
|
|
18704
|
+
].join("\n");
|
|
18705
|
+
}
|
|
18706
|
+
function formatReceiveAddress(guide) {
|
|
18707
|
+
if (!guide.receive.domain || !guide.receive.address) return "Receive address: none found on a receiving-ready Primitive-managed domain";
|
|
18708
|
+
return [`Receive address: ${guide.receive.address}`, `Receive domain: ${guide.receive.domain} (Primitive-managed)`].join("\n");
|
|
18709
|
+
}
|
|
18710
|
+
function formatDomainDetails(status) {
|
|
18711
|
+
if (status.domains.length === 0) return ["Domains: none configured"];
|
|
18712
|
+
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}`);
|
|
18713
|
+
}
|
|
18714
|
+
function formatScaffoldCommands(commands) {
|
|
18715
|
+
return commands.scaffold.map((command) => ` ${command}`);
|
|
18716
|
+
}
|
|
18717
|
+
function formatInboxSetupGuide(guide) {
|
|
18718
|
+
const lines = [
|
|
18719
|
+
"Inbound setup",
|
|
18720
|
+
"",
|
|
18721
|
+
guide.readiness.summary,
|
|
18722
|
+
"",
|
|
18723
|
+
formatReadiness(guide),
|
|
18724
|
+
"",
|
|
18725
|
+
formatReceiveAddress(guide),
|
|
18726
|
+
"",
|
|
18727
|
+
"Domains",
|
|
18728
|
+
...formatDomainDetails(guide.status),
|
|
18729
|
+
"",
|
|
18730
|
+
`Processing routes: ${guide.processing.enabled_endpoints} enabled endpoint(s), ${guide.processing.deployed_functions} deployed Function(s)`
|
|
18731
|
+
];
|
|
18732
|
+
if (guide.readiness.mode === "not_receiving") lines.push("", "Next actions", "Make a receiving-ready domain available, then re-run:", ` ${guide.commands.status}`);
|
|
18733
|
+
else if (!guide.processing.active) lines.push("", "Next actions", "No processing route is enabled. Scaffold, deploy, and test an email Function:", ...formatScaffoldCommands(guide.commands));
|
|
18734
|
+
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`);
|
|
18735
|
+
if (guide.status.next_actions.length > 0) {
|
|
18736
|
+
lines.push("", "API suggested actions");
|
|
18737
|
+
for (const action of guide.status.next_actions) lines.push(action.command ? `- ${action.message}\n ${action.command}` : `- ${action.message}`);
|
|
18738
|
+
}
|
|
18739
|
+
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}`);
|
|
18740
|
+
return lines.join("\n");
|
|
18741
|
+
}
|
|
18742
|
+
var InboxSetupCommand = class InboxSetupCommand extends Command {
|
|
18743
|
+
static description = `Guide inbound email setup from the server-owned inbox status API.
|
|
18744
|
+
|
|
18745
|
+
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.`;
|
|
18746
|
+
static summary = "Guide inbound email setup";
|
|
18747
|
+
static examples = ["<%= config.bin %> inbox setup", "<%= config.bin %> inbox setup --json"];
|
|
18748
|
+
static flags = {
|
|
18749
|
+
"api-key": Flags.string({
|
|
18750
|
+
description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
|
|
18751
|
+
env: "PRIMITIVE_API_KEY"
|
|
18752
|
+
}),
|
|
18753
|
+
"api-base-url-1": Flags.string({
|
|
18754
|
+
description: API_BASE_URL_1_FLAG_DESCRIPTION,
|
|
18755
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
18756
|
+
hidden: true
|
|
18757
|
+
}),
|
|
18758
|
+
"api-base-url-2": Flags.string({
|
|
18759
|
+
description: API_BASE_URL_2_FLAG_DESCRIPTION,
|
|
18760
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
18761
|
+
hidden: true
|
|
18762
|
+
}),
|
|
18763
|
+
json: Flags.boolean({ description: "Print structured readiness, receive address, commands, proof metadata, and raw status as JSON." }),
|
|
18764
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
18765
|
+
};
|
|
18766
|
+
async run() {
|
|
18767
|
+
const { flags } = await this.parse(InboxSetupCommand);
|
|
18768
|
+
await runWithTiming(flags.time, async () => {
|
|
18769
|
+
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
18770
|
+
apiKey: flags["api-key"],
|
|
18771
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
18772
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
18773
|
+
configDir: this.config.configDir
|
|
18774
|
+
});
|
|
18775
|
+
const result = await getInboxStatus({
|
|
18776
|
+
client: apiClient.client,
|
|
18777
|
+
responseStyle: "fields"
|
|
18778
|
+
});
|
|
18779
|
+
if (result.error) {
|
|
18780
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
18781
|
+
writeErrorWithHints(errorPayload);
|
|
18782
|
+
surfaceUnauthorizedHint({
|
|
18783
|
+
auth,
|
|
18784
|
+
baseUrlOverridden,
|
|
18785
|
+
configDir: this.config.configDir,
|
|
18786
|
+
payload: errorPayload
|
|
18787
|
+
});
|
|
18788
|
+
process.exitCode = 1;
|
|
18789
|
+
return;
|
|
18790
|
+
}
|
|
18791
|
+
const envelope = result.data ?? {};
|
|
18792
|
+
const status = envelope.data;
|
|
18793
|
+
if (!status) throw new Errors.CLIError("Primitive API returned no inbox status.", { exit: 1 });
|
|
18794
|
+
const guide = buildInboxSetupGuide(status);
|
|
18795
|
+
if (flags.json) {
|
|
18796
|
+
this.log(JSON.stringify({
|
|
18797
|
+
...envelope,
|
|
18798
|
+
data: guide
|
|
18799
|
+
}, null, 2));
|
|
18800
|
+
return;
|
|
18801
|
+
}
|
|
18802
|
+
this.log(formatInboxSetupGuide(guide));
|
|
18803
|
+
});
|
|
18804
|
+
}
|
|
18805
|
+
};
|
|
18806
|
+
//#endregion
|
|
17984
18807
|
//#region src/oclif/commands/login.ts
|
|
17985
18808
|
const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
|
|
17986
18809
|
function cliError$3(message) {
|
|
@@ -18303,11 +19126,100 @@ function loadPendingAgentSignup(configDir, apiBaseUrl1) {
|
|
|
18303
19126
|
expires_in: Math.max(0, Math.ceil((new Date(pending.expires_at).getTime() - Date.now()) / 1e3))
|
|
18304
19127
|
};
|
|
18305
19128
|
}
|
|
19129
|
+
function readPendingAgentSignupState(configDir, apiBaseUrl1) {
|
|
19130
|
+
const path = pendingSignupPath(configDir);
|
|
19131
|
+
let contents;
|
|
19132
|
+
try {
|
|
19133
|
+
contents = readFileSync(path, "utf8");
|
|
19134
|
+
} catch (error) {
|
|
19135
|
+
if (error && typeof error === "object" && error.code === "ENOENT") return null;
|
|
19136
|
+
throw error;
|
|
19137
|
+
}
|
|
19138
|
+
let pending;
|
|
19139
|
+
try {
|
|
19140
|
+
pending = pendingSignupFromJson(JSON.parse(contents));
|
|
19141
|
+
} catch {
|
|
19142
|
+
pending = null;
|
|
19143
|
+
}
|
|
19144
|
+
if (!pending) {
|
|
19145
|
+
deletePendingAgentSignup(configDir);
|
|
19146
|
+
return null;
|
|
19147
|
+
}
|
|
19148
|
+
if (pending.api_base_url_1 !== apiBaseUrl1) return null;
|
|
19149
|
+
return pending;
|
|
19150
|
+
}
|
|
19151
|
+
function pendingSignupStartCommand(email) {
|
|
19152
|
+
return `primitive signup ${email ?? "<email>"} --signup-code <invite-code> --accept-terms`;
|
|
19153
|
+
}
|
|
19154
|
+
function buildSignupStatus(params) {
|
|
19155
|
+
const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
|
|
19156
|
+
const pending = readPendingAgentSignupState(params.configDir, params.apiBaseUrl1);
|
|
19157
|
+
if (!pending) return {
|
|
19158
|
+
code_length: null,
|
|
19159
|
+
confirm_command: null,
|
|
19160
|
+
email: null,
|
|
19161
|
+
expired: false,
|
|
19162
|
+
expires_at: null,
|
|
19163
|
+
expires_in: null,
|
|
19164
|
+
pending: false,
|
|
19165
|
+
resend_after: null,
|
|
19166
|
+
resend_command: null,
|
|
19167
|
+
signup_command: pendingSignupStartCommand(params.email)
|
|
19168
|
+
};
|
|
19169
|
+
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.`);
|
|
19170
|
+
const expiresAtMs = new Date(pending.expires_at).getTime();
|
|
19171
|
+
const expiresIn = Number.isFinite(expiresAtMs) ? Math.ceil((expiresAtMs - Date.now()) / 1e3) : null;
|
|
19172
|
+
return {
|
|
19173
|
+
code_length: pending.verification_code_length,
|
|
19174
|
+
confirm_command: `primitive ${copy.confirmCommand(pending.email)}`,
|
|
19175
|
+
email: pending.email,
|
|
19176
|
+
expired: expiresIn !== null && expiresIn <= 0,
|
|
19177
|
+
expires_at: pending.expires_at,
|
|
19178
|
+
expires_in: expiresIn === null ? null : Math.max(0, expiresIn),
|
|
19179
|
+
pending: true,
|
|
19180
|
+
resend_after: pending.resend_after,
|
|
19181
|
+
resend_command: `primitive ${copy.resendCommand(pending.email)}`
|
|
19182
|
+
};
|
|
19183
|
+
}
|
|
19184
|
+
function writeSignupStatus(status) {
|
|
19185
|
+
if (!status.pending) {
|
|
19186
|
+
process$1.stdout.write("No pending Primitive signup found.\n");
|
|
19187
|
+
process$1.stdout.write(`Start one with \`${status.signup_command ?? pendingSignupStartCommand()}\`.\n`);
|
|
19188
|
+
return;
|
|
19189
|
+
}
|
|
19190
|
+
process$1.stdout.write(`Pending Primitive signup for ${status.email}.\n`);
|
|
19191
|
+
if (status.code_length !== null) process$1.stdout.write(`Verification code length: ${status.code_length}\n`);
|
|
19192
|
+
if (status.expires_at) if (status.expired) process$1.stdout.write(`Expired at: ${status.expires_at}\n`);
|
|
19193
|
+
else {
|
|
19194
|
+
process$1.stdout.write(`Expires at: ${status.expires_at}\n`);
|
|
19195
|
+
process$1.stdout.write(`Expires in: ${formatSignupSeconds(status.expires_in)}\n`);
|
|
19196
|
+
}
|
|
19197
|
+
if (status.resend_after !== null) process$1.stdout.write(`Resend after: ${formatSignupSeconds(status.resend_after)}\n`);
|
|
19198
|
+
if (status.confirm_command) process$1.stdout.write(`Confirm: ${status.confirm_command}\n`);
|
|
19199
|
+
if (status.resend_command) process$1.stdout.write(`Resend: ${status.resend_command}\n`);
|
|
19200
|
+
}
|
|
19201
|
+
function runSignupStatus(params) {
|
|
19202
|
+
const { requestConfig } = createCliApiClient({
|
|
19203
|
+
apiBaseUrl1: params.flags["api-base-url-1"],
|
|
19204
|
+
configDir: params.configDir
|
|
19205
|
+
});
|
|
19206
|
+
const status = buildSignupStatus({
|
|
19207
|
+
apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
|
|
19208
|
+
configDir: params.configDir,
|
|
19209
|
+
copy: params.copy,
|
|
19210
|
+
email: params.email
|
|
19211
|
+
});
|
|
19212
|
+
if (params.flags.json) {
|
|
19213
|
+
process$1.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
19214
|
+
return;
|
|
19215
|
+
}
|
|
19216
|
+
writeSignupStatus(status);
|
|
19217
|
+
}
|
|
18306
19218
|
function requirePendingSignupForEmail(params) {
|
|
18307
19219
|
const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
|
|
18308
19220
|
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.`);
|
|
19221
|
+
if (!pending) throw cliError$2(`No pending ${copy.actionNoun} for ${params.email}. Run \`primitive signup status ${params.email}\` to inspect pending state, or \`primitive ${copy.startCommand(params.email)}\` first.`);
|
|
19222
|
+
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
19223
|
return pending;
|
|
18312
19224
|
}
|
|
18313
19225
|
function retryAfterSeconds(result) {
|
|
@@ -18410,13 +19322,13 @@ async function startSignup(params) {
|
|
|
18410
19322
|
if (existingPending && !params.flags.force) {
|
|
18411
19323
|
if (normalizeEmail(existingPending.email) === normalizeEmail(params.email)) {
|
|
18412
19324
|
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,
|
|
19325
|
+
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
19326
|
return {
|
|
18415
19327
|
pending: existingPending,
|
|
18416
19328
|
started: false
|
|
18417
19329
|
};
|
|
18418
19330
|
}
|
|
18419
|
-
throw cliError$2(`Pending ${copy.actionNoun} is for ${existingPending.email}. Run \`primitive ${copy.startCommand(params.email)} --force\` to replace it.`);
|
|
19331
|
+
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
19332
|
}
|
|
18421
19333
|
if (params.flags.force) deletePendingAgentSignup(params.configDir);
|
|
18422
19334
|
const promptRequiredFn = params.deps.promptRequired ?? promptRequired;
|
|
@@ -18552,23 +19464,25 @@ async function runSignupConfirmWithCredentialLock(params) {
|
|
|
18552
19464
|
}
|
|
18553
19465
|
const payload = extractErrorPayload(verified.error);
|
|
18554
19466
|
const code = extractErrorCode(payload);
|
|
18555
|
-
if (code === INVALID_VERIFICATION_CODE) throw cliError$2(`Invalid verification code. Try again
|
|
19467
|
+
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
19468
|
if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(configDir);
|
|
18557
19469
|
writeErrorWithHints(payload);
|
|
18558
19470
|
throw cliError$2("Primitive agent signup failed while verifying the account.");
|
|
18559
19471
|
}
|
|
18560
19472
|
async function runSignupResendWithCredentialLock(params) {
|
|
18561
19473
|
const deps = params.deps ?? {};
|
|
19474
|
+
const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
|
|
18562
19475
|
const { apiClient, requestConfig } = createCliApiClient({
|
|
18563
19476
|
apiBaseUrl1: params.flags["api-base-url-1"],
|
|
18564
19477
|
configDir: params.configDir
|
|
18565
19478
|
});
|
|
18566
|
-
const pending = requirePendingSignupForEmail({
|
|
19479
|
+
const pending = params.email ? requirePendingSignupForEmail({
|
|
18567
19480
|
apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
|
|
18568
|
-
copy
|
|
19481
|
+
copy,
|
|
18569
19482
|
configDir: params.configDir,
|
|
18570
19483
|
email: params.email
|
|
18571
|
-
});
|
|
19484
|
+
}) : loadPendingAgentSignup(params.configDir, requestConfig.resolvedApiBaseUrl1);
|
|
19485
|
+
if (!pending) throw cliError$2(`No pending ${copy.actionNoun} found. Run \`primitive signup status\` to inspect pending state, or start one with \`${pendingSignupStartCommand()}\`.`);
|
|
18572
19486
|
const resend = await resendVerificationCode({
|
|
18573
19487
|
apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
|
|
18574
19488
|
apiClient,
|
|
@@ -18741,12 +19655,12 @@ var SignupConfirmCommand = class SignupConfirmCommand extends Command {
|
|
|
18741
19655
|
};
|
|
18742
19656
|
var SignupResendCommand = class SignupResendCommand extends Command {
|
|
18743
19657
|
static args = { email: Args.string({
|
|
18744
|
-
description: "Email address used to start signup",
|
|
18745
|
-
required:
|
|
19658
|
+
description: "Email address used to start signup. Defaults to the saved pending signup.",
|
|
19659
|
+
required: false
|
|
18746
19660
|
}) };
|
|
18747
19661
|
static description = "Resend the verification code for a pending signup.";
|
|
18748
19662
|
static summary = "Resend signup verification code";
|
|
18749
|
-
static examples = ["<%= config.bin %> signup resend user@example.com"];
|
|
19663
|
+
static examples = ["<%= config.bin %> signup resend", "<%= config.bin %> signup resend user@example.com"];
|
|
18750
19664
|
static flags = { "api-base-url-1": Flags.string({
|
|
18751
19665
|
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
18752
19666
|
env: "PRIMITIVE_API_BASE_URL_1",
|
|
@@ -18771,6 +19685,35 @@ var SignupResendCommand = class SignupResendCommand extends Command {
|
|
|
18771
19685
|
}
|
|
18772
19686
|
}
|
|
18773
19687
|
};
|
|
19688
|
+
var SignupStatusCommand = class SignupStatusCommand extends Command {
|
|
19689
|
+
static args = { email: Args.string({
|
|
19690
|
+
description: "Email address expected in the pending signup",
|
|
19691
|
+
required: false
|
|
19692
|
+
}) };
|
|
19693
|
+
static description = "Inspect the locally saved pending Primitive signup state.";
|
|
19694
|
+
static summary = "Show pending signup status";
|
|
19695
|
+
static examples = [
|
|
19696
|
+
"<%= config.bin %> signup status",
|
|
19697
|
+
"<%= config.bin %> signup status user@example.com",
|
|
19698
|
+
"<%= config.bin %> signup status --json"
|
|
19699
|
+
];
|
|
19700
|
+
static flags = {
|
|
19701
|
+
"api-base-url-1": Flags.string({
|
|
19702
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
19703
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
19704
|
+
hidden: true
|
|
19705
|
+
}),
|
|
19706
|
+
json: Flags.boolean({ description: "Print pending signup status as JSON" })
|
|
19707
|
+
};
|
|
19708
|
+
async run() {
|
|
19709
|
+
const { args, flags } = await this.parse(SignupStatusCommand);
|
|
19710
|
+
runSignupStatus({
|
|
19711
|
+
configDir: this.config.configDir,
|
|
19712
|
+
email: args.email,
|
|
19713
|
+
flags
|
|
19714
|
+
});
|
|
19715
|
+
}
|
|
19716
|
+
};
|
|
18774
19717
|
var SignupInteractiveCommand = class SignupInteractiveCommand extends Command {
|
|
18775
19718
|
static description = "Run the full signup flow in one interactive terminal session.";
|
|
18776
19719
|
static summary = "Run interactive account signup";
|
|
@@ -19031,6 +19974,7 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
19031
19974
|
static examples = [
|
|
19032
19975
|
"<%= config.bin %> reply --id <inbound-email-id> --body 'Thanks, got it.'",
|
|
19033
19976
|
"<%= config.bin %> reply --id <inbound-email-id> --body-file ./reply.txt",
|
|
19977
|
+
"<%= config.bin %> reply --id <inbound-email-id> --body 'See attached.' --attachment ./report.pdf",
|
|
19034
19978
|
"<%= config.bin %> reply --id <inbound-email-id> --html '<p>Thanks, got it.</p>' --wait",
|
|
19035
19979
|
"<%= config.bin %> reply --id <inbound-email-id> --from 'Support <support@example.com>' --body 'Thanks!'"
|
|
19036
19980
|
];
|
|
@@ -19054,12 +19998,17 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
19054
19998
|
required: true
|
|
19055
19999
|
}),
|
|
19056
20000
|
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." }),
|
|
20001
|
+
"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
20002
|
"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
20003
|
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." }),
|
|
20004
|
+
"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
20005
|
"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
20006
|
from: Flags.string({ description: "Optional From header override. Defaults to the inbound recipient." }),
|
|
20007
|
+
attachment: Flags.string({
|
|
20008
|
+
char: "a",
|
|
20009
|
+
description: "Attach a file to the reply. Repeat --attachment to attach multiple files.",
|
|
20010
|
+
multiple: true
|
|
20011
|
+
}),
|
|
19063
20012
|
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
20013
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
19065
20014
|
};
|
|
@@ -19081,14 +20030,16 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
19081
20030
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
19082
20031
|
configDir: this.config.configDir
|
|
19083
20032
|
});
|
|
20033
|
+
const attachments = readAttachmentFiles(flags.attachment);
|
|
19084
20034
|
const result = await replyToEmail({
|
|
19085
20035
|
body: {
|
|
19086
20036
|
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
19087
20037
|
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
19088
20038
|
...flags.from !== void 0 ? { from: flags.from } : {},
|
|
20039
|
+
...attachments !== void 0 ? { attachments } : {},
|
|
19089
20040
|
...flags.wait !== void 0 ? { wait: flags.wait } : {}
|
|
19090
20041
|
},
|
|
19091
|
-
client: apiClient.
|
|
20042
|
+
client: apiClient._sendClient,
|
|
19092
20043
|
path: { id: flags.id },
|
|
19093
20044
|
responseStyle: "fields"
|
|
19094
20045
|
});
|
|
@@ -19113,39 +20064,6 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
19113
20064
|
}
|
|
19114
20065
|
};
|
|
19115
20066
|
//#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
|
-
}
|
|
19124
|
-
}
|
|
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
|
-
});
|
|
19130
|
-
}
|
|
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 });
|
|
19134
|
-
}
|
|
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
|
-
});
|
|
19147
|
-
}
|
|
19148
|
-
//#endregion
|
|
19149
20067
|
//#region src/oclif/commands/send.ts
|
|
19150
20068
|
var SendCommand = class SendCommand extends Command {
|
|
19151
20069
|
static description = `Send an outbound email. Agent-grade shortcut for \`sending send\` with sensible defaults.
|
|
@@ -20002,6 +20920,7 @@ const COMMANDS = {
|
|
|
20002
20920
|
"signup:confirm": SignupConfirmCommand,
|
|
20003
20921
|
"signup:interactive": SignupInteractiveCommand,
|
|
20004
20922
|
"signup:resend": SignupResendCommand,
|
|
20923
|
+
"signup:status": SignupStatusCommand,
|
|
20005
20924
|
logout: LogoutCommand,
|
|
20006
20925
|
whoami: WhoamiCommand,
|
|
20007
20926
|
doctor: DoctorCommand,
|
|
@@ -20010,6 +20929,7 @@ const COMMANDS = {
|
|
|
20010
20929
|
"emails:wait": EmailsWaitCommand,
|
|
20011
20930
|
"domains:zone-file": DomainsZoneFileCommand,
|
|
20012
20931
|
"domains:download-domain-zone-file": DomainsZoneFileCommand,
|
|
20932
|
+
"inbox:setup": InboxSetupCommand,
|
|
20013
20933
|
"inbox:status": InboxStatusCommand,
|
|
20014
20934
|
"inbox:get-inbox-status": InboxStatusCommand,
|
|
20015
20935
|
"functions:init": FunctionsInitCommand,
|