@primitivedotdev/cli 0.31.4 → 0.31.6

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.
@@ -651,6 +651,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
651
651
  getEmail: () => getEmail,
652
652
  getFunction: () => getFunction,
653
653
  getFunctionTestRunTrace: () => getFunctionTestRunTrace,
654
+ getInboxStatus: () => getInboxStatus,
654
655
  getSendPermissions: () => getSendPermissions,
655
656
  getSentEmail: () => getSentEmail,
656
657
  getStorageStats: () => getStorageStats,
@@ -1033,6 +1034,28 @@ const downloadDomainZoneFile = (options) => (options.client ?? client).get({
1033
1034
  ...options
1034
1035
  });
1035
1036
  /**
1037
+ * Get inbound inbox readiness
1038
+ *
1039
+ * Returns one consolidated view of inbound domain readiness,
1040
+ * webhook/function processing routes, deployed Functions, and
1041
+ * recent inbound email activity.
1042
+ *
1043
+ * Agents should call this before guiding a user through inbound
1044
+ * setup. It answers the practical questions "can I receive mail",
1045
+ * "will anything process that mail", and "what should I do next"
1046
+ * without forcing clients to stitch together domains, endpoints,
1047
+ * functions, and emails manually.
1048
+ *
1049
+ */
1050
+ const getInboxStatus = (options) => (options?.client ?? client).get({
1051
+ security: [{
1052
+ scheme: "bearer",
1053
+ type: "http"
1054
+ }],
1055
+ url: "/inbox/status",
1056
+ ...options
1057
+ });
1058
+ /**
1036
1059
  * List inbound emails
1037
1060
  *
1038
1061
  * Returns a paginated list of INBOUND emails received at your
@@ -1894,6 +1917,10 @@ const openapiDocument = {
1894
1917
  "name": "Domains",
1895
1918
  "description": "Claim, verify, and manage email domains"
1896
1919
  },
1920
+ {
1921
+ "name": "Inbox",
1922
+ "description": "Check inbound email setup and processing readiness"
1923
+ },
1897
1924
  {
1898
1925
  "name": "Emails",
1899
1926
  "description": "List, inspect, and manage received emails"
@@ -2500,6 +2527,23 @@ const openapiDocument = {
2500
2527
  }
2501
2528
  }
2502
2529
  },
2530
+ "/inbox/status": { "get": {
2531
+ "operationId": "getInboxStatus",
2532
+ "summary": "Get inbound inbox readiness",
2533
+ "description": "Returns one consolidated view of inbound domain readiness,\nwebhook/function processing routes, deployed Functions, and\nrecent inbound email activity.\n\nAgents should call this before guiding a user through inbound\nsetup. It answers the practical questions \"can I receive mail\",\n\"will anything process that mail\", and \"what should I do next\"\nwithout forcing clients to stitch together domains, endpoints,\nfunctions, and emails manually.\n",
2534
+ "tags": ["Inbox"],
2535
+ "responses": {
2536
+ "200": {
2537
+ "description": "Consolidated inbox readiness status",
2538
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
2539
+ "type": "object",
2540
+ "properties": { "data": { "$ref": "#/components/schemas/InboxStatus" } }
2541
+ }] } } }
2542
+ },
2543
+ "401": { "$ref": "#/components/responses/Unauthorized" },
2544
+ "429": { "$ref": "#/components/responses/RateLimited" }
2545
+ }
2546
+ } },
2503
2547
  "/emails": { "get": {
2504
2548
  "operationId": "listEmails",
2505
2549
  "summary": "List inbound emails",
@@ -4788,6 +4832,181 @@ const openapiDocument = {
4788
4832
  } },
4789
4833
  "required": ["secret"]
4790
4834
  },
4835
+ "InboxStatus": {
4836
+ "type": "object",
4837
+ "additionalProperties": false,
4838
+ "properties": {
4839
+ "ready": {
4840
+ "type": "boolean",
4841
+ "description": "True when at least one active inbound domain has an enabled processing route."
4842
+ },
4843
+ "receiving_ready": {
4844
+ "type": "boolean",
4845
+ "description": "True when at least one active verified or managed domain can receive mail."
4846
+ },
4847
+ "processing_ready": {
4848
+ "type": "boolean",
4849
+ "description": "True when at least one receiving-ready domain has an enabled webhook or function route."
4850
+ },
4851
+ "summary": {
4852
+ "type": "string",
4853
+ "description": "Short human-readable status summary."
4854
+ },
4855
+ "next_actions": {
4856
+ "type": "array",
4857
+ "items": { "$ref": "#/components/schemas/InboxStatusNextAction" }
4858
+ },
4859
+ "domains": {
4860
+ "type": "array",
4861
+ "items": { "$ref": "#/components/schemas/InboxStatusDomain" }
4862
+ },
4863
+ "endpoints": { "$ref": "#/components/schemas/InboxStatusEndpointSummary" },
4864
+ "functions": { "$ref": "#/components/schemas/InboxStatusFunctionSummary" },
4865
+ "recent_emails": { "$ref": "#/components/schemas/InboxStatusRecentEmailSummary" }
4866
+ },
4867
+ "required": [
4868
+ "ready",
4869
+ "receiving_ready",
4870
+ "processing_ready",
4871
+ "summary",
4872
+ "next_actions",
4873
+ "domains",
4874
+ "endpoints",
4875
+ "functions",
4876
+ "recent_emails"
4877
+ ]
4878
+ },
4879
+ "InboxStatusNextAction": {
4880
+ "type": "object",
4881
+ "additionalProperties": false,
4882
+ "properties": {
4883
+ "kind": {
4884
+ "type": "string",
4885
+ "enum": [
4886
+ "add_domain",
4887
+ "verify_domain",
4888
+ "configure_processing",
4889
+ "send_test_email",
4890
+ "fix_failed_functions"
4891
+ ]
4892
+ },
4893
+ "message": {
4894
+ "type": "string",
4895
+ "description": "Human-readable next step."
4896
+ },
4897
+ "command": {
4898
+ "type": "string",
4899
+ "description": "Suggested Primitive CLI command when there is an obvious next step."
4900
+ }
4901
+ },
4902
+ "required": ["kind", "message"]
4903
+ },
4904
+ "InboxStatusDomain": {
4905
+ "type": "object",
4906
+ "additionalProperties": false,
4907
+ "properties": {
4908
+ "id": { "type": "string" },
4909
+ "domain": { "type": "string" },
4910
+ "verified": { "type": "boolean" },
4911
+ "active": { "type": "boolean" },
4912
+ "managed": { "type": "boolean" },
4913
+ "receiving_ready": { "type": "boolean" },
4914
+ "processing_ready": { "type": "boolean" },
4915
+ "processing_route_count": { "type": "integer" },
4916
+ "endpoint_count": { "type": "integer" },
4917
+ "enabled_endpoint_count": { "type": "integer" },
4918
+ "function_endpoint_count": { "type": "integer" },
4919
+ "email_count": {
4920
+ "type": "integer",
4921
+ "description": "Number of inbound emails received for this domain in the last 30 days."
4922
+ },
4923
+ "latest_email_received_at": {
4924
+ "type": ["string", "null"],
4925
+ "format": "date-time",
4926
+ "description": "Most recent inbound email received for this domain in the last 30 days."
4927
+ },
4928
+ "status": {
4929
+ "type": "string",
4930
+ "enum": [
4931
+ "ready",
4932
+ "stored_only",
4933
+ "pending_dns",
4934
+ "inactive"
4935
+ ]
4936
+ }
4937
+ },
4938
+ "required": [
4939
+ "id",
4940
+ "domain",
4941
+ "verified",
4942
+ "active",
4943
+ "managed",
4944
+ "receiving_ready",
4945
+ "processing_ready",
4946
+ "processing_route_count",
4947
+ "endpoint_count",
4948
+ "enabled_endpoint_count",
4949
+ "function_endpoint_count",
4950
+ "email_count",
4951
+ "latest_email_received_at",
4952
+ "status"
4953
+ ]
4954
+ },
4955
+ "InboxStatusEndpointSummary": {
4956
+ "type": "object",
4957
+ "additionalProperties": false,
4958
+ "properties": {
4959
+ "total": { "type": "integer" },
4960
+ "enabled": { "type": "integer" },
4961
+ "disabled": { "type": "integer" },
4962
+ "fallback_enabled": { "type": "integer" },
4963
+ "domain_scoped_enabled": { "type": "integer" },
4964
+ "http_enabled": { "type": "integer" },
4965
+ "function_enabled": { "type": "integer" }
4966
+ },
4967
+ "required": [
4968
+ "total",
4969
+ "enabled",
4970
+ "disabled",
4971
+ "fallback_enabled",
4972
+ "domain_scoped_enabled",
4973
+ "http_enabled",
4974
+ "function_enabled"
4975
+ ]
4976
+ },
4977
+ "InboxStatusFunctionSummary": {
4978
+ "type": "object",
4979
+ "additionalProperties": false,
4980
+ "properties": {
4981
+ "total": { "type": "integer" },
4982
+ "deployed": { "type": "integer" },
4983
+ "pending": { "type": "integer" },
4984
+ "failed": { "type": "integer" }
4985
+ },
4986
+ "required": [
4987
+ "total",
4988
+ "deployed",
4989
+ "pending",
4990
+ "failed"
4991
+ ]
4992
+ },
4993
+ "InboxStatusRecentEmailSummary": {
4994
+ "type": "object",
4995
+ "description": "Inbound email activity from the last 30 days.",
4996
+ "additionalProperties": false,
4997
+ "properties": {
4998
+ "total": {
4999
+ "type": "integer",
5000
+ "description": "Number of inbound emails received in the last 30 days."
5001
+ },
5002
+ "latest_received_at": {
5003
+ "type": ["string", "null"],
5004
+ "format": "date-time",
5005
+ "description": "Most recent inbound email received in the last 30 days."
5006
+ }
5007
+ },
5008
+ "required": ["total", "latest_received_at"]
5009
+ },
4791
5010
  "Domain": {
4792
5011
  "description": "A domain can be either verified or unverified. Verified domains have\n`is_active` and `spam_threshold` fields. Unverified domains have a\n`verification_token` and `dns_records` for DNS setup.\n",
4793
5012
  "oneOf": [{ "$ref": "#/components/schemas/VerifiedDomain" }, { "$ref": "#/components/schemas/UnverifiedDomain" }]
@@ -10881,6 +11100,193 @@ const operationManifest = [
10881
11100
  "tag": "Functions",
10882
11101
  "tagCommand": "functions"
10883
11102
  },
11103
+ {
11104
+ "binaryResponse": false,
11105
+ "bodyRequired": false,
11106
+ "command": "get-inbox-status",
11107
+ "description": "Returns one consolidated view of inbound domain readiness,\nwebhook/function processing routes, deployed Functions, and\nrecent inbound email activity.\n\nAgents should call this before guiding a user through inbound\nsetup. It answers the practical questions \"can I receive mail\",\n\"will anything process that mail\", and \"what should I do next\"\nwithout forcing clients to stitch together domains, endpoints,\nfunctions, and emails manually.\n",
11108
+ "hasJsonBody": false,
11109
+ "method": "GET",
11110
+ "operationId": "getInboxStatus",
11111
+ "path": "/inbox/status",
11112
+ "pathParams": [],
11113
+ "queryParams": [],
11114
+ "requestSchema": null,
11115
+ "responseSchema": {
11116
+ "type": "object",
11117
+ "additionalProperties": false,
11118
+ "properties": {
11119
+ "ready": {
11120
+ "type": "boolean",
11121
+ "description": "True when at least one active inbound domain has an enabled processing route."
11122
+ },
11123
+ "receiving_ready": {
11124
+ "type": "boolean",
11125
+ "description": "True when at least one active verified or managed domain can receive mail."
11126
+ },
11127
+ "processing_ready": {
11128
+ "type": "boolean",
11129
+ "description": "True when at least one receiving-ready domain has an enabled webhook or function route."
11130
+ },
11131
+ "summary": {
11132
+ "type": "string",
11133
+ "description": "Short human-readable status summary."
11134
+ },
11135
+ "next_actions": {
11136
+ "type": "array",
11137
+ "items": {
11138
+ "type": "object",
11139
+ "additionalProperties": false,
11140
+ "properties": {
11141
+ "kind": {
11142
+ "type": "string",
11143
+ "enum": [
11144
+ "add_domain",
11145
+ "verify_domain",
11146
+ "configure_processing",
11147
+ "send_test_email",
11148
+ "fix_failed_functions"
11149
+ ]
11150
+ },
11151
+ "message": {
11152
+ "type": "string",
11153
+ "description": "Human-readable next step."
11154
+ },
11155
+ "command": {
11156
+ "type": "string",
11157
+ "description": "Suggested Primitive CLI command when there is an obvious next step."
11158
+ }
11159
+ },
11160
+ "required": ["kind", "message"]
11161
+ }
11162
+ },
11163
+ "domains": {
11164
+ "type": "array",
11165
+ "items": {
11166
+ "type": "object",
11167
+ "additionalProperties": false,
11168
+ "properties": {
11169
+ "id": { "type": "string" },
11170
+ "domain": { "type": "string" },
11171
+ "verified": { "type": "boolean" },
11172
+ "active": { "type": "boolean" },
11173
+ "managed": { "type": "boolean" },
11174
+ "receiving_ready": { "type": "boolean" },
11175
+ "processing_ready": { "type": "boolean" },
11176
+ "processing_route_count": { "type": "integer" },
11177
+ "endpoint_count": { "type": "integer" },
11178
+ "enabled_endpoint_count": { "type": "integer" },
11179
+ "function_endpoint_count": { "type": "integer" },
11180
+ "email_count": {
11181
+ "type": "integer",
11182
+ "description": "Number of inbound emails received for this domain in the last 30 days."
11183
+ },
11184
+ "latest_email_received_at": {
11185
+ "type": ["string", "null"],
11186
+ "format": "date-time",
11187
+ "description": "Most recent inbound email received for this domain in the last 30 days."
11188
+ },
11189
+ "status": {
11190
+ "type": "string",
11191
+ "enum": [
11192
+ "ready",
11193
+ "stored_only",
11194
+ "pending_dns",
11195
+ "inactive"
11196
+ ]
11197
+ }
11198
+ },
11199
+ "required": [
11200
+ "id",
11201
+ "domain",
11202
+ "verified",
11203
+ "active",
11204
+ "managed",
11205
+ "receiving_ready",
11206
+ "processing_ready",
11207
+ "processing_route_count",
11208
+ "endpoint_count",
11209
+ "enabled_endpoint_count",
11210
+ "function_endpoint_count",
11211
+ "email_count",
11212
+ "latest_email_received_at",
11213
+ "status"
11214
+ ]
11215
+ }
11216
+ },
11217
+ "endpoints": {
11218
+ "type": "object",
11219
+ "additionalProperties": false,
11220
+ "properties": {
11221
+ "total": { "type": "integer" },
11222
+ "enabled": { "type": "integer" },
11223
+ "disabled": { "type": "integer" },
11224
+ "fallback_enabled": { "type": "integer" },
11225
+ "domain_scoped_enabled": { "type": "integer" },
11226
+ "http_enabled": { "type": "integer" },
11227
+ "function_enabled": { "type": "integer" }
11228
+ },
11229
+ "required": [
11230
+ "total",
11231
+ "enabled",
11232
+ "disabled",
11233
+ "fallback_enabled",
11234
+ "domain_scoped_enabled",
11235
+ "http_enabled",
11236
+ "function_enabled"
11237
+ ]
11238
+ },
11239
+ "functions": {
11240
+ "type": "object",
11241
+ "additionalProperties": false,
11242
+ "properties": {
11243
+ "total": { "type": "integer" },
11244
+ "deployed": { "type": "integer" },
11245
+ "pending": { "type": "integer" },
11246
+ "failed": { "type": "integer" }
11247
+ },
11248
+ "required": [
11249
+ "total",
11250
+ "deployed",
11251
+ "pending",
11252
+ "failed"
11253
+ ]
11254
+ },
11255
+ "recent_emails": {
11256
+ "type": "object",
11257
+ "description": "Inbound email activity from the last 30 days.",
11258
+ "additionalProperties": false,
11259
+ "properties": {
11260
+ "total": {
11261
+ "type": "integer",
11262
+ "description": "Number of inbound emails received in the last 30 days."
11263
+ },
11264
+ "latest_received_at": {
11265
+ "type": ["string", "null"],
11266
+ "format": "date-time",
11267
+ "description": "Most recent inbound email received in the last 30 days."
11268
+ }
11269
+ },
11270
+ "required": ["total", "latest_received_at"]
11271
+ }
11272
+ },
11273
+ "required": [
11274
+ "ready",
11275
+ "receiving_ready",
11276
+ "processing_ready",
11277
+ "summary",
11278
+ "next_actions",
11279
+ "domains",
11280
+ "endpoints",
11281
+ "functions",
11282
+ "recent_emails"
11283
+ ]
11284
+ },
11285
+ "sdkName": "getInboxStatus",
11286
+ "summary": "Get inbound inbox readiness",
11287
+ "tag": "Inbox",
11288
+ "tagCommand": "inbox"
11289
+ },
10884
11290
  {
10885
11291
  "binaryResponse": false,
10886
11292
  "bodyRequired": false,
@@ -12973,7 +13379,7 @@ function surfaceUnauthorizedHint(params) {
12973
13379
  }
12974
13380
  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");
12975
13381
  }
12976
- function formatElapsed(ms) {
13382
+ function formatElapsed$1(ms) {
12977
13383
  const seconds = ms / 1e3;
12978
13384
  if (seconds < 60) return `${seconds.toFixed(2)}s`;
12979
13385
  const minutes = Math.floor(seconds / 60);
@@ -12985,7 +13391,7 @@ async function runWithTiming(enabled, fn) {
12985
13391
  try {
12986
13392
  return await fn();
12987
13393
  } finally {
12988
- process.stderr.write(`[time: ${formatElapsed(Date.now() - start)}]\n`);
13394
+ process.stderr.write(`[time: ${formatElapsed$1(Date.now() - start)}]\n`);
12989
13395
  }
12990
13396
  }
12991
13397
  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.";
@@ -13082,6 +13488,7 @@ const OPERATION_HINTS = {
13082
13488
  addDomain: "Tip: after this returns a domain id, run `primitive domains zone-file --id <domain-id> --output <domain>.zone` when the user wants an importable DNS zone file.",
13083
13489
  verifyDomain: "Tip: if DNS is still missing, run `primitive domains zone-file --id <domain-id> --output <domain>.zone` to give the user an importable DNS zone file.",
13084
13490
  downloadDomainZoneFile: "Tip: prefer `primitive domains zone-file --id <domain-id> --output <domain>.zone` for CLI-friendly file output.",
13491
+ getInboxStatus: "Tip: prefer `primitive inbox status` for a compact readiness summary and next-step commands.",
13085
13492
  sendEmail: "Tip: prefer `primitive send --to <address> --body <text> --attachment <file>` for file attachments. This raw command exists for callers passing JSON.",
13086
13493
  createFunction: "Tip: prefer `primitive functions deploy --name <name> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
13087
13494
  updateFunction: "Tip: prefer `primitive functions redeploy --id <id> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
@@ -13348,6 +13755,300 @@ async function readStdinToString() {
13348
13755
  for await (const chunk of process.stdin) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
13349
13756
  return Buffer.concat(chunks).toString("utf8");
13350
13757
  }
13758
+ var ChatProgressIndicator = class {
13759
+ currentMessage = null;
13760
+ frameIndex = 0;
13761
+ lastLineLength = 0;
13762
+ startedAt;
13763
+ timer = null;
13764
+ constructor(stream = process.stderr, now = Date.now) {
13765
+ this.stream = stream;
13766
+ this.now = now;
13767
+ this.startedAt = this.now();
13768
+ }
13769
+ start(message) {
13770
+ this.stopTimer();
13771
+ this.currentMessage = message;
13772
+ if (this.stream.isTTY) {
13773
+ this.render(message);
13774
+ this.timer = setInterval(() => this.render(message), 120);
13775
+ this.timer.unref?.();
13776
+ return;
13777
+ }
13778
+ this.stream.write(`${message}\n`);
13779
+ }
13780
+ update(message, options = {}) {
13781
+ this.currentMessage = message;
13782
+ if (this.stream.isTTY) {
13783
+ this.stopTimer();
13784
+ this.clearLine();
13785
+ this.render(message);
13786
+ this.timer = setInterval(() => this.render(message), 120);
13787
+ this.timer.unref?.();
13788
+ return;
13789
+ }
13790
+ this.stopTimer();
13791
+ this.stream.write(`${message}\n`);
13792
+ if (options.heartbeatMs !== void 0) {
13793
+ this.timer = setInterval(() => {
13794
+ this.stream.write(`${formatWaitingHeartbeat(message, this.now() - this.startedAt, options.timeoutSeconds)}\n`);
13795
+ }, options.heartbeatMs);
13796
+ this.timer.unref?.();
13797
+ }
13798
+ }
13799
+ notice(message) {
13800
+ if (this.stream.isTTY) {
13801
+ const currentMessage = this.currentMessage;
13802
+ this.clearLine();
13803
+ this.stream.write(`${message}\n`);
13804
+ if (currentMessage !== null && this.timer !== null) this.render(currentMessage);
13805
+ return;
13806
+ }
13807
+ this.stream.write(`${message}\n`);
13808
+ }
13809
+ succeed(message) {
13810
+ this.finish(`${message} after ${formatElapsed(this.now() - this.startedAt)}.`);
13811
+ }
13812
+ fail(message) {
13813
+ this.finish(message);
13814
+ }
13815
+ finish(message) {
13816
+ this.stopTimer();
13817
+ this.currentMessage = null;
13818
+ if (this.stream.isTTY) this.clearLine();
13819
+ this.stream.write(`${message}\n`);
13820
+ }
13821
+ render(message) {
13822
+ const frames = [
13823
+ "-",
13824
+ "\\",
13825
+ "|",
13826
+ "/"
13827
+ ];
13828
+ const frame = frames[this.frameIndex % frames.length];
13829
+ this.frameIndex += 1;
13830
+ const line = `${frame} ${message} (${formatElapsed(this.now() - this.startedAt)})`;
13831
+ this.lastLineLength = Math.max(this.lastLineLength, line.length);
13832
+ this.stream.write(`\r${line}`);
13833
+ }
13834
+ clearLine() {
13835
+ if (this.lastLineLength > 0) {
13836
+ this.stream.write(`\r${" ".repeat(this.lastLineLength)}\r`);
13837
+ this.lastLineLength = 0;
13838
+ }
13839
+ }
13840
+ stopTimer() {
13841
+ if (this.timer !== null) {
13842
+ clearInterval(this.timer);
13843
+ this.timer = null;
13844
+ }
13845
+ }
13846
+ };
13847
+ function formatElapsed(ms) {
13848
+ const seconds = Math.max(0, Math.round(ms / 1e3));
13849
+ if (seconds < 60) return `${seconds}s`;
13850
+ const minutes = Math.floor(seconds / 60);
13851
+ const remainder = seconds % 60;
13852
+ return remainder === 0 ? `${minutes}m` : `${minutes}m ${remainder}s`;
13853
+ }
13854
+ function formatWaitingHeartbeat(message, elapsedMs, timeoutSeconds) {
13855
+ const timeout = timeoutSeconds === void 0 ? "" : timeoutSeconds === 0 ? ", no timeout" : `, timeout ${formatElapsed(timeoutSeconds * 1e3)}`;
13856
+ return `${message} (${formatElapsed(elapsedMs)} elapsed${timeout})`;
13857
+ }
13858
+ function shellQuote(value) {
13859
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value;
13860
+ return `'${value.replaceAll("'", "'\\''")}'`;
13861
+ }
13862
+ function commandFromArgv(argv) {
13863
+ return argv.map(shellQuote).join(" ");
13864
+ }
13865
+ function resolveChatResponseBody(reply) {
13866
+ if (reply.body_text && reply.body_text.length > 0) return {
13867
+ body: reply.body_text,
13868
+ format: "text"
13869
+ };
13870
+ if (reply.body_html && reply.body_html.length > 0) return {
13871
+ body: reply.body_html,
13872
+ format: "html"
13873
+ };
13874
+ if (reply.body_text !== null && reply.body_text !== void 0) return {
13875
+ body: reply.body_text,
13876
+ format: "text"
13877
+ };
13878
+ if (reply.body_html !== null && reply.body_html !== void 0) return {
13879
+ body: reply.body_html,
13880
+ format: "html"
13881
+ };
13882
+ return {
13883
+ body: "",
13884
+ format: "empty"
13885
+ };
13886
+ }
13887
+ function matchDescription(strategy) {
13888
+ return strategy === "strict" ? "strict, matched by reply_to_sent_email_id" : "fallback, matched by sender/time window";
13889
+ }
13890
+ function buildCommand(kind, description, argv, options = {}) {
13891
+ const requiresMessage = options.requiresMessage ?? false;
13892
+ return {
13893
+ argv,
13894
+ description,
13895
+ command: commandFromArgv(argv),
13896
+ kind,
13897
+ placeholders: requiresMessage ? [{
13898
+ description: "Replace with the message body before running.",
13899
+ token: "<message>"
13900
+ }] : [],
13901
+ requires_message: requiresMessage
13902
+ };
13903
+ }
13904
+ function buildChatFollowUpCommands(context) {
13905
+ const commands = [];
13906
+ const continueParts = [
13907
+ "primitive",
13908
+ "chat",
13909
+ context.recipient,
13910
+ "<message>",
13911
+ "--from",
13912
+ context.from,
13913
+ "--subject",
13914
+ context.subject,
13915
+ "--timeout",
13916
+ String(context.timeoutSeconds)
13917
+ ];
13918
+ if (context.reply.message_id) continueParts.push("--in-reply-to", context.reply.message_id);
13919
+ if (context.json) continueParts.push("--json");
13920
+ if (context.quiet) continueParts.push("--quiet");
13921
+ if (context.strictOnly) continueParts.push("--strict-only");
13922
+ else if (context.strictPhaseSeconds !== DEFAULT_STRICT_PHASE_SECONDS) continueParts.push("--strict-phase-seconds", String(context.strictPhaseSeconds));
13923
+ commands.push(buildCommand("continue_chat", "Continue this chat", continueParts, { requiresMessage: true }));
13924
+ commands.push(buildCommand("reply_direct", "Reply directly to the inbound email", [
13925
+ "primitive",
13926
+ "reply",
13927
+ "--id",
13928
+ context.reply.id,
13929
+ "--from",
13930
+ context.from,
13931
+ "--body",
13932
+ "<message>"
13933
+ ], { requiresMessage: true }));
13934
+ commands.push(buildCommand("inspect_reply", "Inspect the full inbound email", [
13935
+ "primitive",
13936
+ "emails",
13937
+ "get",
13938
+ "--id",
13939
+ context.reply.id
13940
+ ]));
13941
+ commands.push(buildCommand("wait_for_more", "Wait for future replies to this send", [
13942
+ "primitive",
13943
+ "emails",
13944
+ "wait",
13945
+ "--reply-to-sent-email-id",
13946
+ context.sent.id,
13947
+ "--to",
13948
+ context.from,
13949
+ "--since",
13950
+ context.reply.received_at,
13951
+ "--timeout",
13952
+ String(context.timeoutSeconds)
13953
+ ]));
13954
+ return commands;
13955
+ }
13956
+ function buildChatRecoveryCommands(context) {
13957
+ return [
13958
+ buildCommand("wait_threaded_reply", "Wait for the threaded reply again", [
13959
+ "primitive",
13960
+ "emails",
13961
+ "wait",
13962
+ "--reply-to-sent-email-id",
13963
+ context.sent.id,
13964
+ "--to",
13965
+ context.from,
13966
+ "--since",
13967
+ context.sentAtIso,
13968
+ "--timeout",
13969
+ String(context.timeoutSeconds)
13970
+ ]),
13971
+ buildCommand("wait_fallback_reply", "Fallback wait by sender/time window", [
13972
+ "primitive",
13973
+ "emails",
13974
+ "wait",
13975
+ "--from",
13976
+ context.recipient,
13977
+ "--to",
13978
+ context.from,
13979
+ "--since",
13980
+ context.sentAtIso,
13981
+ "--timeout",
13982
+ String(context.timeoutSeconds)
13983
+ ]),
13984
+ buildCommand("inspect_sent_email", "Inspect the outbound send", [
13985
+ "primitive",
13986
+ "sent",
13987
+ "get",
13988
+ "--id",
13989
+ context.sent.id
13990
+ ])
13991
+ ];
13992
+ }
13993
+ function buildChatJsonEnvelope(context) {
13994
+ const responseBody = resolveChatResponseBody(context.reply);
13995
+ return {
13996
+ sent: context.sent,
13997
+ reply: context.reply,
13998
+ response_body: responseBody.body,
13999
+ response_body_format: responseBody.format,
14000
+ match: {
14001
+ description: matchDescription(context.matchStrategy),
14002
+ reply_to_sent_email_id: context.reply.reply_to_sent_email_id ?? null,
14003
+ strategy: context.matchStrategy
14004
+ },
14005
+ follow_up_commands: buildChatFollowUpCommands(context)
14006
+ };
14007
+ }
14008
+ function formatChatResponse(context) {
14009
+ const accepted = context.sent.accepted.join(", ") || context.recipient;
14010
+ const responseBody = resolveChatResponseBody(context.reply);
14011
+ const lines = [
14012
+ "Reply received",
14013
+ "",
14014
+ "Sent",
14015
+ ` To: ${accepted}`,
14016
+ ` From: ${context.sent.from || context.from}`,
14017
+ ` Subject: ${context.subject}`,
14018
+ ` Sent email id: ${context.sent.id}`,
14019
+ ` Delivery status: ${context.sent.delivery_status ?? context.sent.status}`,
14020
+ "",
14021
+ "Reply",
14022
+ ` Email id: ${context.reply.id}`,
14023
+ ` From: ${context.reply.from_email}`,
14024
+ ` To: ${context.reply.to_email}`,
14025
+ ` Subject: ${context.reply.subject ?? "(no subject)"}`,
14026
+ ` Received: ${context.reply.received_at}`,
14027
+ ` Match: ${matchDescription(context.matchStrategy)}`
14028
+ ];
14029
+ if (context.reply.reply_to_sent_email_id) lines.push(` Reply to sent email id: ${context.reply.reply_to_sent_email_id}`);
14030
+ if (context.reply.message_id) lines.push(` Message-Id: ${context.reply.message_id}`);
14031
+ lines.push("", "Helpful follow-up commands", " Replace <message> before running commands that include it.", " Commands are templates; use --json for parse-safe output.");
14032
+ for (const { description, command } of buildChatFollowUpCommands(context)) lines.push(` ${description}:`, ` ${command}`);
14033
+ lines.push("", `Response body (${responseBody.format}; use --json for parsing)`, "----- BEGIN RESPONSE -----", responseBody.body || "(empty response)", "----- END RESPONSE -----");
14034
+ return lines.join("\n");
14035
+ }
14036
+ function formatChatRecoveryContext(context) {
14037
+ const lines = [
14038
+ "",
14039
+ "Sent message context",
14040
+ ` To: ${context.sent.accepted.join(", ") || context.recipient}`,
14041
+ ` From: ${context.sent.from || context.from}`,
14042
+ ` Subject: ${context.subject}`,
14043
+ ` Sent email id: ${context.sent.id}`,
14044
+ ` Delivery status: ${context.sent.delivery_status ?? context.sent.status}`,
14045
+ ` Poll since: ${context.sentAtIso}`,
14046
+ "",
14047
+ "Helpful recovery commands"
14048
+ ];
14049
+ for (const { description, command } of buildChatRecoveryCommands(context)) lines.push(` ${description}:`, ` ${command}`);
14050
+ return lines.join("\n");
14051
+ }
13351
14052
  var ChatCommand = class ChatCommand extends Command {
13352
14053
  static description = `Send a message to an address and wait for the reply.
13353
14054
 
@@ -13356,13 +14057,24 @@ var ChatCommand = class ChatCommand extends Command {
13356
14057
  \`primitive chat\` is semantic (send + wait for the threaded reply).
13357
14058
 
13358
14059
  The message body can be given as the second positional argument or
13359
- piped via stdin. The reply body is written to stdout; --json emits a
13360
- structured envelope with both sides of the exchange.
14060
+ piped via stdin. The default output confirms the reply was received,
14061
+ prints exchange metadata, shows the response body, and lists helpful
14062
+ follow-up commands as templates. The default transcript is for humans;
14063
+ agents and scripts should pass --json for parse-safe output.
14064
+
14065
+ --json emits a structured envelope with both sides of the exchange,
14066
+ a direct response_body field, match details, and follow-up command
14067
+ metadata such as kind, argv, placeholders, and requires_message.
13361
14068
 
13362
- Matching the reply: the wait phase polls inbound mail filtered by
13363
- the recipient as sender and the send time as a lower bound. The
13364
- first match is taken; the full inbound row is then fetched for the
13365
- body. Exits non-zero on timeout.`;
14069
+ Matching the reply: chat first waits in strict threading mode by
14070
+ filtering inbound mail with reply_to_sent_email_id=<sent id>. If
14071
+ no strict match arrives before the strict phase ends, and
14072
+ --strict-only is not set, it falls back to a weaker sender/time
14073
+ window match: from=<recipient>, to=<sender>, and since=<send time>.
14074
+ The fallback can catch clients that strip threading headers, but it
14075
+ is less exact than strict matching. Progress is written to stderr
14076
+ while the CLI waits. Exits non-zero on timeout and prints recovery
14077
+ commands when the send succeeded but no reply was returned.`;
13366
14078
  static summary = "Chat with an agent over email (send and wait for the reply)";
13367
14079
  static examples = [
13368
14080
  "<%= config.bin %> chat help@agent.acme.dev 'how do I rotate my API key?'",
@@ -13395,7 +14107,8 @@ var ChatCommand = class ChatCommand extends Command {
13395
14107
  from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
13396
14108
  subject: Flags.string({ description: "Subject line. Defaults to the first line of the message when omitted." }),
13397
14109
  "in-reply-to": Flags.string({ description: "Message-Id of the parent email to thread this against. Use when continuing a prior conversation from outside the CLI; for an inbound you received via Primitive, prefer `primitive reply --id <inbound-id>`." }),
13398
- json: Flags.boolean({ description: "Emit a structured JSON envelope { sent, reply } on stdout instead of just the reply body." }),
14110
+ json: Flags.boolean({ description: "Emit a structured JSON envelope { sent, reply, response_body, response_body_format, match, follow_up_commands } on stdout instead of the human-readable transcript." }),
14111
+ quiet: Flags.boolean({ description: "Suppress stderr progress updates while sending and waiting. Errors and recovery commands are still written to stderr." }),
13399
14112
  timeout: Flags.integer({
13400
14113
  default: DEFAULT_CHAT_TIMEOUT_SECONDS,
13401
14114
  description: "Seconds to wait for a reply before exiting non-zero; 0 waits forever.",
@@ -13440,6 +14153,8 @@ var ChatCommand = class ChatCommand extends Command {
13440
14153
  const from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
13441
14154
  const subject = flags.subject ?? deriveSubject(message);
13442
14155
  const sentAtIso = (/* @__PURE__ */ new Date()).toISOString();
14156
+ const progress = flags.quiet ? null : new ChatProgressIndicator(process.stderr);
14157
+ progress?.start(`Sending message to ${args.recipient}`);
13443
14158
  const sendResult = await sendEmail({
13444
14159
  body: {
13445
14160
  from,
@@ -13452,6 +14167,7 @@ var ChatCommand = class ChatCommand extends Command {
13452
14167
  responseStyle: "fields"
13453
14168
  });
13454
14169
  if (sendResult.error) {
14170
+ progress?.fail("Message send failed.");
13455
14171
  const errorPayload = extractErrorPayload(sendResult.error);
13456
14172
  writeErrorWithHints(errorPayload);
13457
14173
  surfaceUnauthorizedHint({
@@ -13462,39 +14178,76 @@ var ChatCommand = class ChatCommand extends Command {
13462
14178
  return;
13463
14179
  }
13464
14180
  const sent = sendResult.data?.data;
13465
- if (!sent) throw cliError$6("Send succeeded but the API returned no data.");
13466
- const reply = await waitForReply({
13467
- apiClient,
13468
- authFailureContext,
14181
+ if (!sent) {
14182
+ progress?.fail("Send succeeded but the API returned no data.");
14183
+ throw cliError$6("Send succeeded but the API returned no data.");
14184
+ }
14185
+ progress?.update(`Message sent; waiting for reply from ${args.recipient}`, {
14186
+ heartbeatMs: 15e3,
14187
+ timeoutSeconds: flags.timeout
14188
+ });
14189
+ const baseContext = {
13469
14190
  from,
13470
- interval: flags.interval,
13471
- pageSize: flags["page-size"],
14191
+ json: flags.json,
14192
+ quiet: flags.quiet,
13472
14193
  recipient: args.recipient,
14194
+ sent,
13473
14195
  sentAtIso,
13474
- sentId: sent.id,
13475
14196
  strictOnly: flags["strict-only"],
13476
14197
  strictPhaseSeconds: flags["strict-phase-seconds"],
14198
+ subject,
13477
14199
  timeoutSeconds: flags.timeout
13478
- });
13479
- if (reply === null) {
13480
- process.stderr.write(`Timed out after ${flags.timeout}s waiting for a reply from ${args.recipient}.\n`);
14200
+ };
14201
+ let replyResult;
14202
+ try {
14203
+ replyResult = await waitForReply({
14204
+ apiClient,
14205
+ authFailureContext,
14206
+ from,
14207
+ interval: flags.interval,
14208
+ notice: (message) => {
14209
+ if (progress) {
14210
+ progress.notice(message);
14211
+ return;
14212
+ }
14213
+ process.stderr.write(`${message}\n`);
14214
+ },
14215
+ pageSize: flags["page-size"],
14216
+ recipient: args.recipient,
14217
+ sentAtIso,
14218
+ sentId: sent.id,
14219
+ strictOnly: flags["strict-only"],
14220
+ strictPhaseSeconds: flags["strict-phase-seconds"],
14221
+ timeoutSeconds: flags.timeout
14222
+ });
14223
+ } catch (error) {
14224
+ progress?.fail("Reply polling failed.");
14225
+ process.stderr.write(`${formatChatRecoveryContext(baseContext)}\n`);
14226
+ throw error;
14227
+ }
14228
+ if (replyResult === null) {
14229
+ const timeoutMessage = `Timed out after ${flags.timeout}s waiting for a reply from ${args.recipient}.`;
14230
+ progress?.fail(timeoutMessage);
14231
+ if (progress === null) process.stderr.write(`${timeoutMessage}\n`);
14232
+ process.stderr.write(`${formatChatRecoveryContext(baseContext)}\n`);
13481
14233
  process.exitCode = 1;
13482
14234
  return;
13483
14235
  }
13484
- if (flags.json) {
13485
- const envelope = {
13486
- sent,
13487
- reply
13488
- };
13489
- this.log(JSON.stringify(envelope, null, 2));
13490
- } else {
13491
- const body = reply.body_text ?? reply.body_html ?? "";
13492
- this.log(body);
13493
- }
14236
+ progress?.succeed(`Reply received from ${replyResult.reply.from_email}`);
14237
+ const outputContext = {
14238
+ ...baseContext,
14239
+ matchStrategy: replyResult.matchStrategy,
14240
+ reply: replyResult.reply
14241
+ };
14242
+ if (flags.json) this.log(JSON.stringify(buildChatJsonEnvelope(outputContext), null, 2));
14243
+ else this.log(formatChatResponse(outputContext));
13494
14244
  });
13495
14245
  }
13496
14246
  };
13497
14247
  async function waitForReply(params) {
14248
+ const notice = params.notice ?? ((message) => {
14249
+ process.stderr.write(`${message}\n`);
14250
+ });
13498
14251
  const totalDeadline = params.timeoutSeconds === 0 ? null : Date.now() + params.timeoutSeconds * 1e3;
13499
14252
  const strictDeadlineFromBudget = Date.now() + params.strictPhaseSeconds * 1e3;
13500
14253
  const strictDeadline = params.strictOnly ? totalDeadline : totalDeadline === null ? strictDeadlineFromBudget : Math.min(strictDeadlineFromBudget, totalDeadline);
@@ -13563,11 +14316,14 @@ async function waitForReply(params) {
13563
14316
  const detail = envelope?.data ?? envelope ?? null;
13564
14317
  if (!detail) throw new Errors.CLIError(`Reply landed but the email body could not be loaded (id=${match.id}).`, { exit: 1 });
13565
14318
  if (phase.label === "strict" && detail.reply_to_sent_email_id !== params.sentId) {
13566
- if (!strictFilterUnsupported) process.stderr.write(params.strictOnly ? "Strict-phase reply matching is not supported by this Primitive API host; --strict-only requires server support so the command will exit without a match.\n" : "Strict-phase reply matching is not supported by this Primitive API host; falling back to time-window matching.\n");
14319
+ if (!strictFilterUnsupported) notice(params.strictOnly ? "Strict-phase reply matching is not supported by this Primitive API host; --strict-only requires server support so the command will exit without a match." : "Strict-phase reply matching is not supported by this Primitive API host; falling back to time-window matching.");
13567
14320
  strictFilterUnsupported = true;
13568
14321
  continue;
13569
14322
  }
13570
- return detail;
14323
+ return {
14324
+ reply: detail,
14325
+ matchStrategy: phase.label
14326
+ };
13571
14327
  }
13572
14328
  if (strictFilterUnsupported && phase.label === "strict") break;
13573
14329
  if (lastAccepted !== void 0) continue;
@@ -13589,6 +14345,33 @@ function redactConfig(config) {
13589
14345
  environments: Object.fromEntries(Object.entries(config.environments).map(([name, environment]) => [name, redactCliEnvironment(environment)]))
13590
14346
  };
13591
14347
  }
14348
+ function switchCliEnvironment(configDir, environmentName) {
14349
+ const environment = normalizeCliEnvironmentName(environmentName);
14350
+ const config = loadOrCreateConfig(configDir);
14351
+ if (!config.environments[environment]) throw new Errors.CLIError(`Primitive CLI environment ${environment} is not configured.`, { exit: 1 });
14352
+ const previousEnvironment = resolveConfigEnvironment(config)?.name ?? null;
14353
+ const nextConfig = {
14354
+ ...config,
14355
+ current_environment: environment
14356
+ };
14357
+ const shouldClearCredentials = previousEnvironment !== environment;
14358
+ let removedCredentials = false;
14359
+ if (shouldClearCredentials) {
14360
+ const releaseLock = acquireCliCredentialsLock(configDir);
14361
+ try {
14362
+ saveCliConfig(configDir, nextConfig);
14363
+ removedCredentials = existsSync(credentialsPath(configDir));
14364
+ deleteCliCredentials(configDir);
14365
+ } finally {
14366
+ releaseLock();
14367
+ }
14368
+ } else saveCliConfig(configDir, nextConfig);
14369
+ return {
14370
+ environment,
14371
+ previousEnvironment,
14372
+ removedCredentials
14373
+ };
14374
+ }
13592
14375
  var ConfigSetCommand = class ConfigSetCommand extends Command {
13593
14376
  static summary = "Set a Primitive CLI request environment";
13594
14377
  static flags = {
@@ -13625,20 +14408,16 @@ var ConfigSetCommand = class ConfigSetCommand extends Command {
13625
14408
  };
13626
14409
  var ConfigUseCommand = class ConfigUseCommand extends Command {
13627
14410
  static summary = "Switch the active Primitive CLI request environment";
14411
+ static description = "Switch the active Primitive CLI request environment. When this switches to a different environment, the CLI removes saved OAuth credentials so the next authenticated command signs in against the newly active API host.";
13628
14412
  static args = { environment: Args.string({
13629
14413
  description: "Environment name to use",
13630
14414
  required: true
13631
14415
  }) };
13632
14416
  async run() {
13633
14417
  const { args } = await this.parse(ConfigUseCommand);
13634
- const environment = normalizeCliEnvironmentName(args.environment);
13635
- const config = loadOrCreateConfig(this.config.configDir);
13636
- if (!config.environments[environment]) throw new Errors.CLIError(`Primitive CLI environment ${environment} is not configured.`, { exit: 1 });
13637
- saveCliConfig(this.config.configDir, {
13638
- ...config,
13639
- current_environment: environment
13640
- });
14418
+ const { environment, removedCredentials } = switchCliEnvironment(this.config.configDir, args.environment);
13641
14419
  process.stderr.write(`Primitive CLI environment ${environment} is active.\n`);
14420
+ if (removedCredentials) process.stderr.write("Removed saved Primitive CLI credentials. Run `primitive signin` to authenticate in the active environment.\n");
13642
14421
  }
13643
14422
  };
13644
14423
  var ConfigListCommand = class ConfigListCommand extends Command {
@@ -14108,7 +14887,7 @@ const ADDRESS_DISPLAY_WIDTH = 32;
14108
14887
  const ID_DISPLAY_WIDTH_SHORT = 8;
14109
14888
  const ID_DISPLAY_WIDTH_FULL = 36;
14110
14889
  const RECEIVED_DISPLAY_WIDTH = 19;
14111
- function truncate(value, width) {
14890
+ function truncate$1(value, width) {
14112
14891
  if (value.length <= width) return value.padEnd(width);
14113
14892
  return `${value.slice(0, width - 3)}...`;
14114
14893
  }
@@ -14123,7 +14902,7 @@ function pickIdWidth(isTty) {
14123
14902
  return isTty ? ID_DISPLAY_WIDTH_SHORT : ID_DISPLAY_WIDTH_FULL;
14124
14903
  }
14125
14904
  function formatRow(email, idWidth) {
14126
- return `${truncate(email.id.slice(0, idWidth), idWidth)} ${formatReceivedAt(email.received_at)} ${truncate(email.sender ?? "", ADDRESS_DISPLAY_WIDTH)} ${truncate(email.recipient ?? "", ADDRESS_DISPLAY_WIDTH)} ${truncate((email.subject ?? "").replace(/\s+/g, " "), SUBJECT_DISPLAY_WIDTH)}`;
14905
+ 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)}`;
14127
14906
  }
14128
14907
  function formatHeader(idWidth) {
14129
14908
  return `${"ID".padEnd(idWidth)} ${"RECEIVED (UTC)".padEnd(RECEIVED_DISPLAY_WIDTH)} ${"FROM".padEnd(ADDRESS_DISPLAY_WIDTH)} ${"TO".padEnd(ADDRESS_DISPLAY_WIDTH)} SUBJECT`;
@@ -16509,6 +17288,174 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
16509
17288
  }
16510
17289
  };
16511
17290
  //#endregion
17291
+ //#region src/oclif/commands/inbox-status.ts
17292
+ const DOMAIN_DISPLAY_WIDTH = 34;
17293
+ const STATUS_DISPLAY_WIDTH = 12;
17294
+ const BOOL_DISPLAY_WIDTH = 7;
17295
+ const NUM_DISPLAY_WIDTH = 6;
17296
+ function plural(count, singular, pluralValue = `${singular}s`) {
17297
+ return `${count} ${count === 1 ? singular : pluralValue}`;
17298
+ }
17299
+ function statusText(status) {
17300
+ switch (status) {
17301
+ case "ready": return "ready";
17302
+ case "stored_only": return "stored-only";
17303
+ case "pending_dns": return "pending-dns";
17304
+ case "inactive": return "inactive";
17305
+ default: return String(status);
17306
+ }
17307
+ }
17308
+ function yesNo(value) {
17309
+ return value ? "yes" : "no";
17310
+ }
17311
+ function formatInboxDate(value) {
17312
+ if (!value) return "never";
17313
+ const d = new Date(value);
17314
+ if (Number.isNaN(d.getTime())) return value;
17315
+ const pad = (n) => String(n).padStart(2, "0");
17316
+ return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())} UTC`;
17317
+ }
17318
+ function truncate(value, width) {
17319
+ if (value.length <= width) return value.padEnd(width);
17320
+ return `${value.slice(0, width - 3)}...`;
17321
+ }
17322
+ function domainSummary(domain) {
17323
+ switch (domain.status) {
17324
+ case "ready": return `${domain.domain} can receive mail and has ${plural(domain.processing_route_count, "processing route")}.`;
17325
+ case "stored_only": return `${domain.domain} can receive and store mail, but has no enabled processing route.`;
17326
+ case "pending_dns": return `${domain.domain} is waiting on DNS verification before it can receive mail.`;
17327
+ case "inactive": return `${domain.domain} is verified but inactive.`;
17328
+ default: return `${domain.domain} has status ${String(domain.status)}.`;
17329
+ }
17330
+ }
17331
+ function focusInboxStatus(status, domainName) {
17332
+ const normalized = domainName.toLowerCase();
17333
+ const domain = status.domains.find((entry) => entry.domain.toLowerCase() === normalized);
17334
+ if (!domain) throw new Errors.CLIError(`Domain ${domainName} was not found.`, { exit: 1 });
17335
+ return {
17336
+ ...status,
17337
+ domains: [domain],
17338
+ ready: domain.receiving_ready && domain.processing_ready,
17339
+ receiving_ready: domain.receiving_ready,
17340
+ processing_ready: domain.processing_ready,
17341
+ summary: domainSummary(domain),
17342
+ recent_emails: {
17343
+ total: domain.email_count,
17344
+ latest_received_at: domain.latest_email_received_at
17345
+ }
17346
+ };
17347
+ }
17348
+ function formatDomainHeader() {
17349
+ return [
17350
+ "DOMAIN".padEnd(DOMAIN_DISPLAY_WIDTH),
17351
+ "STATUS".padEnd(STATUS_DISPLAY_WIDTH),
17352
+ "RECEIVE".padEnd(BOOL_DISPLAY_WIDTH),
17353
+ "PROCESS".padEnd(BOOL_DISPLAY_WIDTH),
17354
+ "EMAILS".padStart(NUM_DISPLAY_WIDTH),
17355
+ "ROUTES".padStart(NUM_DISPLAY_WIDTH)
17356
+ ].join(" ");
17357
+ }
17358
+ function formatDomainRow(domain) {
17359
+ return [
17360
+ truncate(domain.domain, DOMAIN_DISPLAY_WIDTH),
17361
+ statusText(domain.status).padEnd(STATUS_DISPLAY_WIDTH),
17362
+ yesNo(domain.receiving_ready).padEnd(BOOL_DISPLAY_WIDTH),
17363
+ yesNo(domain.processing_ready).padEnd(BOOL_DISPLAY_WIDTH),
17364
+ String(domain.email_count).padStart(NUM_DISPLAY_WIDTH),
17365
+ String(domain.processing_route_count).padStart(NUM_DISPLAY_WIDTH)
17366
+ ].join(" ");
17367
+ }
17368
+ function formatNextAction(action) {
17369
+ return action.command ? `- ${action.message}\n ${action.command}` : `- ${action.message}`;
17370
+ }
17371
+ function formatInboxStatus(status) {
17372
+ const lines = [
17373
+ status.summary,
17374
+ "",
17375
+ "Domains"
17376
+ ];
17377
+ if (status.domains.length === 0) lines.push("No domains configured.");
17378
+ else {
17379
+ lines.push(formatDomainHeader());
17380
+ for (const domain of status.domains) lines.push(formatDomainRow(domain));
17381
+ }
17382
+ 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)}`);
17383
+ if (status.next_actions.length > 0) {
17384
+ lines.push("", "Next actions");
17385
+ for (const action of status.next_actions) lines.push(formatNextAction(action));
17386
+ }
17387
+ return lines.join("\n");
17388
+ }
17389
+ var InboxStatusCommand = class InboxStatusCommand extends Command {
17390
+ static description = `Show consolidated inbound email readiness.
17391
+
17392
+ This checks the server-owned inbox status API instead of reconstructing readiness locally from separate domain, endpoint, function, and email lists. Use it before testing inbound email setup: it tells you whether mail can be received, whether anything will process it, and which next command is most useful.`;
17393
+ static summary = "Show inbound email readiness";
17394
+ static examples = [
17395
+ "<%= config.bin %> inbox status",
17396
+ "<%= config.bin %> inbox status --domain example.com",
17397
+ "<%= config.bin %> inbox status --json | jq '.data.next_actions'"
17398
+ ];
17399
+ static flags = {
17400
+ "api-key": Flags.string({
17401
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
17402
+ env: "PRIMITIVE_API_KEY"
17403
+ }),
17404
+ "api-base-url-1": Flags.string({
17405
+ description: API_BASE_URL_1_FLAG_DESCRIPTION,
17406
+ env: "PRIMITIVE_API_BASE_URL_1",
17407
+ hidden: true
17408
+ }),
17409
+ "api-base-url-2": Flags.string({
17410
+ description: API_BASE_URL_2_FLAG_DESCRIPTION,
17411
+ env: "PRIMITIVE_API_BASE_URL_2",
17412
+ hidden: true
17413
+ }),
17414
+ domain: Flags.string({ description: "Focus domain readiness and recent email fields on one domain returned by the inbox status API." }),
17415
+ json: Flags.boolean({ description: "Print the raw response envelope as JSON. With --domain, domain readiness and recent email fields are focused while endpoint, function, and next-action summaries remain account-level." }),
17416
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
17417
+ };
17418
+ async run() {
17419
+ const { flags } = await this.parse(InboxStatusCommand);
17420
+ await runWithTiming(flags.time, async () => {
17421
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
17422
+ apiKey: flags["api-key"],
17423
+ apiBaseUrl1: flags["api-base-url-1"],
17424
+ apiBaseUrl2: flags["api-base-url-2"],
17425
+ configDir: this.config.configDir
17426
+ });
17427
+ const result = await getInboxStatus({
17428
+ client: apiClient.client,
17429
+ responseStyle: "fields"
17430
+ });
17431
+ if (result.error) {
17432
+ const errorPayload = extractErrorPayload(result.error);
17433
+ writeErrorWithHints(errorPayload);
17434
+ surfaceUnauthorizedHint({
17435
+ auth,
17436
+ baseUrlOverridden,
17437
+ configDir: this.config.configDir,
17438
+ payload: errorPayload
17439
+ });
17440
+ process.exitCode = 1;
17441
+ return;
17442
+ }
17443
+ const envelope = result.data ?? {};
17444
+ const status = envelope.data;
17445
+ if (!status) throw new Errors.CLIError("Primitive API returned no inbox status.", { exit: 1 });
17446
+ const outputStatus = flags.domain ? focusInboxStatus(status, flags.domain) : status;
17447
+ if (flags.json) {
17448
+ this.log(JSON.stringify({
17449
+ ...envelope,
17450
+ data: outputStatus
17451
+ }, null, 2));
17452
+ return;
17453
+ }
17454
+ this.log(formatInboxStatus(outputStatus));
17455
+ });
17456
+ }
17457
+ };
17458
+ //#endregion
16512
17459
  //#region src/oclif/commands/login.ts
16513
17460
  const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
16514
17461
  function cliError$3(message) {
@@ -17915,25 +18862,39 @@ var SigninOtpResendCommand = class SigninOtpResendCommand extends Command {
17915
18862
  };
17916
18863
  //#endregion
17917
18864
  //#region src/oclif/commands/whoami.ts
18865
+ function formatWhoamiSummary(account) {
18866
+ return [
18867
+ `Authenticated as ${account.email}`,
18868
+ `Account id: ${account.id}`,
18869
+ `Plan: ${account.plan}`
18870
+ ].join("\n");
18871
+ }
17918
18872
  var WhoamiCommand = class WhoamiCommand extends Command {
17919
- static description = `Print the account currently authenticated by saved OAuth credentials or an explicit API key. Useful as a credentials smoke test: confirms auth is live and shows which account it belongs to.`;
18873
+ static description = `Print the account currently authenticated by saved OAuth credentials or an explicit API key. Useful as a credentials smoke test: confirms auth is live and shows which account it belongs to.
18874
+
18875
+ The default output is a concise human summary. Pass --json only when a script intentionally needs the full /account response.`;
17920
18876
  static summary = "Print the authenticated account (credentials smoke test)";
17921
- static examples = ["<%= config.bin %> whoami", "<%= config.bin %> whoami --api-key prim_..."];
18877
+ static examples = [
18878
+ "<%= config.bin %> whoami",
18879
+ "<%= config.bin %> whoami --api-key prim_...",
18880
+ "<%= config.bin %> whoami --json | jq .id"
18881
+ ];
17922
18882
  static flags = {
17923
18883
  "api-key": Flags.string({
17924
18884
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
17925
18885
  env: "PRIMITIVE_API_KEY"
17926
18886
  }),
17927
18887
  "api-base-url-1": Flags.string({
17928
- description: "Override the primary API base URL. Internal testing only; not documented to customers.",
18888
+ description: API_BASE_URL_1_FLAG_DESCRIPTION,
17929
18889
  env: "PRIMITIVE_API_BASE_URL_1",
17930
18890
  hidden: true
17931
18891
  }),
17932
18892
  "api-base-url-2": Flags.string({
17933
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
18893
+ description: API_BASE_URL_2_FLAG_DESCRIPTION,
17934
18894
  env: "PRIMITIVE_API_BASE_URL_2",
17935
18895
  hidden: true
17936
18896
  }),
18897
+ json: Flags.boolean({ description: "Print the full account JSON response. Default output hides setup and billing internals." }),
17937
18898
  time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
17938
18899
  };
17939
18900
  async run() {
@@ -17966,12 +18927,11 @@ var WhoamiCommand = class WhoamiCommand extends Command {
17966
18927
  process.stderr.write("Server returned an empty account body; this should not happen for a valid key.\n");
17967
18928
  throw new Errors.CLIError("unexpected empty response");
17968
18929
  }
17969
- const onboarding = account.onboarding_completed === true ? "complete" : account.onboarding_step ? `in progress (step: ${account.onboarding_step})` : "incomplete";
17970
- process.stderr.write(`Authenticated as ${account.email}\n`);
17971
- process.stderr.write(` Account id: ${account.id}\n`);
17972
- process.stderr.write(` Plan: ${account.plan}\n`);
17973
- process.stderr.write(` Onboarding: ${onboarding}\n`);
17974
- this.log(JSON.stringify(account, null, 2));
18930
+ if (flags.json) {
18931
+ this.log(JSON.stringify(account, null, 2));
18932
+ return;
18933
+ }
18934
+ this.log(formatWhoamiSummary(account));
17975
18935
  });
17976
18936
  }
17977
18937
  };
@@ -18225,7 +19185,11 @@ const DESCRIBE_OPERATION_ALIASES = {
18225
19185
  function resolveOperationAlias(id) {
18226
19186
  return DESCRIBE_OPERATION_ALIASES[id] ?? id;
18227
19187
  }
18228
- const OVERRIDDEN_OPERATION_IDS = new Set(["domains:download-domain-zone-file", "functions:test-function"]);
19188
+ const OVERRIDDEN_OPERATION_IDS = new Set([
19189
+ "domains:download-domain-zone-file",
19190
+ "functions:test-function",
19191
+ "inbox:get-inbox-status"
19192
+ ]);
18229
19193
  const generatedCommands = Object.fromEntries(operationManifest.filter((operation) => !OVERRIDDEN_OPERATION_IDS.has(operationId(operation))).map((operation) => [operationId(operation), createOperationCommand(operation)]));
18230
19194
  const COMMANDS = {
18231
19195
  completion: CompletionCommand,
@@ -18257,6 +19221,8 @@ const COMMANDS = {
18257
19221
  "emails:wait": EmailsWaitCommand,
18258
19222
  "domains:zone-file": DomainsZoneFileCommand,
18259
19223
  "domains:download-domain-zone-file": DomainsZoneFileCommand,
19224
+ "inbox:status": InboxStatusCommand,
19225
+ "inbox:get-inbox-status": InboxStatusCommand,
18260
19226
  "functions:init": FunctionsInitCommand,
18261
19227
  "functions:templates": FunctionsTemplatesCommand,
18262
19228
  "functions:deploy": FunctionsDeployCommand,