@primitivedotdev/cli 0.31.4 → 0.31.5

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,
@@ -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.",
@@ -14108,7 +14515,7 @@ const ADDRESS_DISPLAY_WIDTH = 32;
14108
14515
  const ID_DISPLAY_WIDTH_SHORT = 8;
14109
14516
  const ID_DISPLAY_WIDTH_FULL = 36;
14110
14517
  const RECEIVED_DISPLAY_WIDTH = 19;
14111
- function truncate(value, width) {
14518
+ function truncate$1(value, width) {
14112
14519
  if (value.length <= width) return value.padEnd(width);
14113
14520
  return `${value.slice(0, width - 3)}...`;
14114
14521
  }
@@ -14123,7 +14530,7 @@ function pickIdWidth(isTty) {
14123
14530
  return isTty ? ID_DISPLAY_WIDTH_SHORT : ID_DISPLAY_WIDTH_FULL;
14124
14531
  }
14125
14532
  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)}`;
14533
+ 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
14534
  }
14128
14535
  function formatHeader(idWidth) {
14129
14536
  return `${"ID".padEnd(idWidth)} ${"RECEIVED (UTC)".padEnd(RECEIVED_DISPLAY_WIDTH)} ${"FROM".padEnd(ADDRESS_DISPLAY_WIDTH)} ${"TO".padEnd(ADDRESS_DISPLAY_WIDTH)} SUBJECT`;
@@ -16509,6 +16916,174 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
16509
16916
  }
16510
16917
  };
16511
16918
  //#endregion
16919
+ //#region src/oclif/commands/inbox-status.ts
16920
+ const DOMAIN_DISPLAY_WIDTH = 34;
16921
+ const STATUS_DISPLAY_WIDTH = 12;
16922
+ const BOOL_DISPLAY_WIDTH = 7;
16923
+ const NUM_DISPLAY_WIDTH = 6;
16924
+ function plural(count, singular, pluralValue = `${singular}s`) {
16925
+ return `${count} ${count === 1 ? singular : pluralValue}`;
16926
+ }
16927
+ function statusText(status) {
16928
+ switch (status) {
16929
+ case "ready": return "ready";
16930
+ case "stored_only": return "stored-only";
16931
+ case "pending_dns": return "pending-dns";
16932
+ case "inactive": return "inactive";
16933
+ default: return String(status);
16934
+ }
16935
+ }
16936
+ function yesNo(value) {
16937
+ return value ? "yes" : "no";
16938
+ }
16939
+ function formatInboxDate(value) {
16940
+ if (!value) return "never";
16941
+ const d = new Date(value);
16942
+ if (Number.isNaN(d.getTime())) return value;
16943
+ const pad = (n) => String(n).padStart(2, "0");
16944
+ return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())} UTC`;
16945
+ }
16946
+ function truncate(value, width) {
16947
+ if (value.length <= width) return value.padEnd(width);
16948
+ return `${value.slice(0, width - 3)}...`;
16949
+ }
16950
+ function domainSummary(domain) {
16951
+ switch (domain.status) {
16952
+ case "ready": return `${domain.domain} can receive mail and has ${plural(domain.processing_route_count, "processing route")}.`;
16953
+ case "stored_only": return `${domain.domain} can receive and store mail, but has no enabled processing route.`;
16954
+ case "pending_dns": return `${domain.domain} is waiting on DNS verification before it can receive mail.`;
16955
+ case "inactive": return `${domain.domain} is verified but inactive.`;
16956
+ default: return `${domain.domain} has status ${String(domain.status)}.`;
16957
+ }
16958
+ }
16959
+ function focusInboxStatus(status, domainName) {
16960
+ const normalized = domainName.toLowerCase();
16961
+ const domain = status.domains.find((entry) => entry.domain.toLowerCase() === normalized);
16962
+ if (!domain) throw new Errors.CLIError(`Domain ${domainName} was not found.`, { exit: 1 });
16963
+ return {
16964
+ ...status,
16965
+ domains: [domain],
16966
+ ready: domain.receiving_ready && domain.processing_ready,
16967
+ receiving_ready: domain.receiving_ready,
16968
+ processing_ready: domain.processing_ready,
16969
+ summary: domainSummary(domain),
16970
+ recent_emails: {
16971
+ total: domain.email_count,
16972
+ latest_received_at: domain.latest_email_received_at
16973
+ }
16974
+ };
16975
+ }
16976
+ function formatDomainHeader() {
16977
+ return [
16978
+ "DOMAIN".padEnd(DOMAIN_DISPLAY_WIDTH),
16979
+ "STATUS".padEnd(STATUS_DISPLAY_WIDTH),
16980
+ "RECEIVE".padEnd(BOOL_DISPLAY_WIDTH),
16981
+ "PROCESS".padEnd(BOOL_DISPLAY_WIDTH),
16982
+ "EMAILS".padStart(NUM_DISPLAY_WIDTH),
16983
+ "ROUTES".padStart(NUM_DISPLAY_WIDTH)
16984
+ ].join(" ");
16985
+ }
16986
+ function formatDomainRow(domain) {
16987
+ return [
16988
+ truncate(domain.domain, DOMAIN_DISPLAY_WIDTH),
16989
+ statusText(domain.status).padEnd(STATUS_DISPLAY_WIDTH),
16990
+ yesNo(domain.receiving_ready).padEnd(BOOL_DISPLAY_WIDTH),
16991
+ yesNo(domain.processing_ready).padEnd(BOOL_DISPLAY_WIDTH),
16992
+ String(domain.email_count).padStart(NUM_DISPLAY_WIDTH),
16993
+ String(domain.processing_route_count).padStart(NUM_DISPLAY_WIDTH)
16994
+ ].join(" ");
16995
+ }
16996
+ function formatNextAction(action) {
16997
+ return action.command ? `- ${action.message}\n ${action.command}` : `- ${action.message}`;
16998
+ }
16999
+ function formatInboxStatus(status) {
17000
+ const lines = [
17001
+ status.summary,
17002
+ "",
17003
+ "Domains"
17004
+ ];
17005
+ if (status.domains.length === 0) lines.push("No domains configured.");
17006
+ else {
17007
+ lines.push(formatDomainHeader());
17008
+ for (const domain of status.domains) lines.push(formatDomainRow(domain));
17009
+ }
17010
+ 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)}`);
17011
+ if (status.next_actions.length > 0) {
17012
+ lines.push("", "Next actions");
17013
+ for (const action of status.next_actions) lines.push(formatNextAction(action));
17014
+ }
17015
+ return lines.join("\n");
17016
+ }
17017
+ var InboxStatusCommand = class InboxStatusCommand extends Command {
17018
+ static description = `Show consolidated inbound email readiness.
17019
+
17020
+ 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.`;
17021
+ static summary = "Show inbound email readiness";
17022
+ static examples = [
17023
+ "<%= config.bin %> inbox status",
17024
+ "<%= config.bin %> inbox status --domain example.com",
17025
+ "<%= config.bin %> inbox status --json | jq '.data.next_actions'"
17026
+ ];
17027
+ static flags = {
17028
+ "api-key": Flags.string({
17029
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
17030
+ env: "PRIMITIVE_API_KEY"
17031
+ }),
17032
+ "api-base-url-1": Flags.string({
17033
+ description: API_BASE_URL_1_FLAG_DESCRIPTION,
17034
+ env: "PRIMITIVE_API_BASE_URL_1",
17035
+ hidden: true
17036
+ }),
17037
+ "api-base-url-2": Flags.string({
17038
+ description: API_BASE_URL_2_FLAG_DESCRIPTION,
17039
+ env: "PRIMITIVE_API_BASE_URL_2",
17040
+ hidden: true
17041
+ }),
17042
+ domain: Flags.string({ description: "Focus domain readiness and recent email fields on one domain returned by the inbox status API." }),
17043
+ 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." }),
17044
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
17045
+ };
17046
+ async run() {
17047
+ const { flags } = await this.parse(InboxStatusCommand);
17048
+ await runWithTiming(flags.time, async () => {
17049
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
17050
+ apiKey: flags["api-key"],
17051
+ apiBaseUrl1: flags["api-base-url-1"],
17052
+ apiBaseUrl2: flags["api-base-url-2"],
17053
+ configDir: this.config.configDir
17054
+ });
17055
+ const result = await getInboxStatus({
17056
+ client: apiClient.client,
17057
+ responseStyle: "fields"
17058
+ });
17059
+ if (result.error) {
17060
+ const errorPayload = extractErrorPayload(result.error);
17061
+ writeErrorWithHints(errorPayload);
17062
+ surfaceUnauthorizedHint({
17063
+ auth,
17064
+ baseUrlOverridden,
17065
+ configDir: this.config.configDir,
17066
+ payload: errorPayload
17067
+ });
17068
+ process.exitCode = 1;
17069
+ return;
17070
+ }
17071
+ const envelope = result.data ?? {};
17072
+ const status = envelope.data;
17073
+ if (!status) throw new Errors.CLIError("Primitive API returned no inbox status.", { exit: 1 });
17074
+ const outputStatus = flags.domain ? focusInboxStatus(status, flags.domain) : status;
17075
+ if (flags.json) {
17076
+ this.log(JSON.stringify({
17077
+ ...envelope,
17078
+ data: outputStatus
17079
+ }, null, 2));
17080
+ return;
17081
+ }
17082
+ this.log(formatInboxStatus(outputStatus));
17083
+ });
17084
+ }
17085
+ };
17086
+ //#endregion
16512
17087
  //#region src/oclif/commands/login.ts
16513
17088
  const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
16514
17089
  function cliError$3(message) {
@@ -17915,25 +18490,39 @@ var SigninOtpResendCommand = class SigninOtpResendCommand extends Command {
17915
18490
  };
17916
18491
  //#endregion
17917
18492
  //#region src/oclif/commands/whoami.ts
18493
+ function formatWhoamiSummary(account) {
18494
+ return [
18495
+ `Authenticated as ${account.email}`,
18496
+ `Account id: ${account.id}`,
18497
+ `Plan: ${account.plan}`
18498
+ ].join("\n");
18499
+ }
17918
18500
  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.`;
18501
+ 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.
18502
+
18503
+ The default output is a concise human summary. Pass --json only when a script intentionally needs the full /account response.`;
17920
18504
  static summary = "Print the authenticated account (credentials smoke test)";
17921
- static examples = ["<%= config.bin %> whoami", "<%= config.bin %> whoami --api-key prim_..."];
18505
+ static examples = [
18506
+ "<%= config.bin %> whoami",
18507
+ "<%= config.bin %> whoami --api-key prim_...",
18508
+ "<%= config.bin %> whoami --json | jq .id"
18509
+ ];
17922
18510
  static flags = {
17923
18511
  "api-key": Flags.string({
17924
18512
  description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
17925
18513
  env: "PRIMITIVE_API_KEY"
17926
18514
  }),
17927
18515
  "api-base-url-1": Flags.string({
17928
- description: "Override the primary API base URL. Internal testing only; not documented to customers.",
18516
+ description: API_BASE_URL_1_FLAG_DESCRIPTION,
17929
18517
  env: "PRIMITIVE_API_BASE_URL_1",
17930
18518
  hidden: true
17931
18519
  }),
17932
18520
  "api-base-url-2": Flags.string({
17933
- description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
18521
+ description: API_BASE_URL_2_FLAG_DESCRIPTION,
17934
18522
  env: "PRIMITIVE_API_BASE_URL_2",
17935
18523
  hidden: true
17936
18524
  }),
18525
+ json: Flags.boolean({ description: "Print the full account JSON response. Default output hides setup and billing internals." }),
17937
18526
  time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
17938
18527
  };
17939
18528
  async run() {
@@ -17966,12 +18555,11 @@ var WhoamiCommand = class WhoamiCommand extends Command {
17966
18555
  process.stderr.write("Server returned an empty account body; this should not happen for a valid key.\n");
17967
18556
  throw new Errors.CLIError("unexpected empty response");
17968
18557
  }
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));
18558
+ if (flags.json) {
18559
+ this.log(JSON.stringify(account, null, 2));
18560
+ return;
18561
+ }
18562
+ this.log(formatWhoamiSummary(account));
17975
18563
  });
17976
18564
  }
17977
18565
  };
@@ -18225,7 +18813,11 @@ const DESCRIBE_OPERATION_ALIASES = {
18225
18813
  function resolveOperationAlias(id) {
18226
18814
  return DESCRIBE_OPERATION_ALIASES[id] ?? id;
18227
18815
  }
18228
- const OVERRIDDEN_OPERATION_IDS = new Set(["domains:download-domain-zone-file", "functions:test-function"]);
18816
+ const OVERRIDDEN_OPERATION_IDS = new Set([
18817
+ "domains:download-domain-zone-file",
18818
+ "functions:test-function",
18819
+ "inbox:get-inbox-status"
18820
+ ]);
18229
18821
  const generatedCommands = Object.fromEntries(operationManifest.filter((operation) => !OVERRIDDEN_OPERATION_IDS.has(operationId(operation))).map((operation) => [operationId(operation), createOperationCommand(operation)]));
18230
18822
  const COMMANDS = {
18231
18823
  completion: CompletionCommand,
@@ -18257,6 +18849,8 @@ const COMMANDS = {
18257
18849
  "emails:wait": EmailsWaitCommand,
18258
18850
  "domains:zone-file": DomainsZoneFileCommand,
18259
18851
  "domains:download-domain-zone-file": DomainsZoneFileCommand,
18852
+ "inbox:status": InboxStatusCommand,
18853
+ "inbox:get-inbox-status": InboxStatusCommand,
18260
18854
  "functions:init": FunctionsInitCommand,
18261
18855
  "functions:templates": FunctionsTemplatesCommand,
18262
18856
  "functions:deploy": FunctionsDeployCommand,
@@ -0,0 +1,94 @@
1
+ .TH PRIMITIVE 1
2
+ .SH NAME
3
+ primitive \- command line interface for Primitive email and Functions
4
+ .SH SYNOPSIS
5
+ .B primitive
6
+ .I command
7
+ [\fIoptions\fR]
8
+ .br
9
+ .B primitive
10
+ .B --help
11
+ .br
12
+ .B primitive
13
+ .I command
14
+ .B --help
15
+ .SH DESCRIPTION
16
+ .B primitive
17
+ is the command line interface for Primitive. It sends mail, inspects inbound and outbound email, manages domains and webhook endpoints, deploys Primitive Functions, and exposes generated API operations for scripting.
18
+ .PP
19
+ Most commands print human-readable output by default. Commands that expose JSON usually provide a
20
+ .B --json
21
+ flag or use generated operation output that can be piped into tools such as
22
+ .BR jq (1).
23
+ .SH AUTHENTICATION
24
+ Commands use saved OAuth credentials from
25
+ .B primitive signin
26
+ or an API key from
27
+ .B --api-key
28
+ or
29
+ .BR PRIMITIVE_API_KEY .
30
+ .PP
31
+ Run
32
+ .B primitive whoami
33
+ to verify which account the CLI is authenticated as.
34
+ .SH COMMON COMMANDS
35
+ .TP
36
+ .B primitive send --to ADDRESS --subject SUBJECT --body TEXT
37
+ Send an outbound email.
38
+ .TP
39
+ .B primitive send --to ADDRESS --body TEXT --attachment PATH
40
+ Send an outbound email with a file attachment.
41
+ .TP
42
+ .B primitive domains add DOMAIN
43
+ Start a custom-domain claim.
44
+ .TP
45
+ .B primitive domains verify --id DOMAIN_ID
46
+ Verify DNS records for a pending domain claim.
47
+ .TP
48
+ .B primitive domains zone-file --id DOMAIN_ID
49
+ Download DNS records as a BIND-format zone file.
50
+ .TP
51
+ .B primitive emails latest
52
+ Show recent inbound emails.
53
+ .TP
54
+ .B primitive functions init NAME
55
+ Scaffold a Primitive Function.
56
+ .TP
57
+ .B primitive functions deploy --name NAME --file PATH
58
+ Deploy a Primitive Function bundle.
59
+ .TP
60
+ .B primitive list-operations
61
+ Print the generated API operation manifest.
62
+ .TP
63
+ .B primitive describe COMMAND_OR_OPERATION
64
+ Describe a generated API operation, including request and response schemas.
65
+ .SH GLOBAL OPTIONS
66
+ .TP
67
+ .B --api-key KEY
68
+ Use an explicit Primitive API key for this command.
69
+ .TP
70
+ .B --time
71
+ Print command duration to standard error after completion.
72
+ .TP
73
+ .B --help
74
+ Show command help.
75
+ .SH ENVIRONMENT
76
+ .TP
77
+ .B PRIMITIVE_API_KEY
78
+ API key used when
79
+ .B --api-key
80
+ is not supplied and no saved OAuth credential should be used.
81
+ .TP
82
+ .B PRIMITIVE_API_BASE_URL_1
83
+ Override the primary API base URL. Intended for development and testing.
84
+ .TP
85
+ .B PRIMITIVE_API_BASE_URL_2
86
+ Override the secondary API base URL used for selected send flows. Intended for development and testing.
87
+ .SH FILES
88
+ .TP
89
+ .I ~/.config/primitive
90
+ Default CLI configuration directory used by oclif on most systems.
91
+ .SH SEE ALSO
92
+ .B primitive --help
93
+ .br
94
+ Primitive documentation: https://primitive.dev
package/package.json CHANGED
@@ -1,16 +1,20 @@
1
1
  {
2
2
  "name": "@primitivedotdev/cli",
3
- "version": "0.31.4",
3
+ "version": "0.31.5",
4
4
  "description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "files": [
8
8
  "bin",
9
- "dist"
9
+ "dist",
10
+ "man"
10
11
  ],
11
12
  "bin": {
12
13
  "primitive": "./bin/run.js"
13
14
  },
15
+ "man": [
16
+ "./man/primitive.1"
17
+ ],
14
18
  "oclif": {
15
19
  "bin": "primitive",
16
20
  "commands": {
@@ -40,6 +44,9 @@
40
44
  "domains": {
41
45
  "description": "Claim, verify, manage email domains, and download DNS zone files"
42
46
  },
47
+ "inbox": {
48
+ "description": "Check inbound email setup and processing readiness"
49
+ },
43
50
  "emails": {
44
51
  "description": "List, inspect, and wait for received emails. Prefer task aliases like `primitive emails list`, `primitive emails get`, `primitive emails latest`, `primitive emails wait`, and `primitive emails watch`; generated API names remain available for compatibility."
45
52
  },