@primitivedotdev/cli 0.31.2 → 0.31.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { Args, Command, Errors, Flags } from "@oclif/core";
2
2
  import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import { randomUUID } from "node:crypto";
4
- import { dirname, join, resolve } from "node:path";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
5
  import { spawn } from "node:child_process";
6
6
  import { hostname } from "node:os";
7
7
  import process$1 from "node:process";
@@ -645,6 +645,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
645
645
  deleteFunctionSecret: () => deleteFunctionSecret,
646
646
  discardEmailContent: () => discardEmailContent,
647
647
  downloadAttachments: () => downloadAttachments,
648
+ downloadDomainZoneFile: () => downloadDomainZoneFile,
648
649
  downloadRawEmail: () => downloadRawEmail,
649
650
  getAccount: () => getAccount,
650
651
  getEmail: () => getEmail,
@@ -941,9 +942,11 @@ const listDomains = (options) => (options?.client ?? client).get({
941
942
  /**
942
943
  * Claim a new domain
943
944
  *
944
- * Creates an unverified domain claim. You will receive a
945
- * `verification_token` to add as a DNS TXT record before
946
- * calling the verify endpoint.
945
+ * Creates an unverified domain claim and returns the exact
946
+ * DNS records to publish in `dns_records`. Publish those
947
+ * records before calling the verify endpoint. To give users
948
+ * an importable DNS file, call `downloadDomainZoneFile` or run
949
+ * `primitive domains zone-file --id <domain-id>`.
947
950
  *
948
951
  */
949
952
  const addDomain = (options) => (options.client ?? client).post({
@@ -993,9 +996,15 @@ const updateDomain = (options) => (options.client ?? client).patch({
993
996
  /**
994
997
  * Verify domain ownership
995
998
  *
996
- * Checks DNS records (MX and TXT) to verify domain ownership.
999
+ * Checks DNS records required for inbound routing, ownership,
1000
+ * and outbound authentication: MX, ownership TXT, SPF, DKIM,
1001
+ * DMARC, and TLS-RPT.
997
1002
  * On success, the domain is promoted from unverified to verified.
998
- * On failure, returns which checks passed and which failed.
1003
+ * On failure, returns which checks passed and which failed,
1004
+ * plus the exact DNS records still expected. To give users
1005
+ * an importable DNS file for missing records, call
1006
+ * `downloadDomainZoneFile` or run
1007
+ * `primitive domains zone-file --id <domain-id>`.
999
1008
  *
1000
1009
  */
1001
1010
  const verifyDomain = (options) => (options.client ?? client).post({
@@ -1007,6 +1016,23 @@ const verifyDomain = (options) => (options.client ?? client).post({
1007
1016
  ...options
1008
1017
  });
1009
1018
  /**
1019
+ * Download domain DNS zone file
1020
+ *
1021
+ * Downloads a BIND-format DNS zone file containing the DNS records
1022
+ * required for a domain claim. Agents should offer this after
1023
+ * `addDomain` when users want to import DNS records instead of
1024
+ * copying each record manually.
1025
+ *
1026
+ */
1027
+ const downloadDomainZoneFile = (options) => (options.client ?? client).get({
1028
+ security: [{
1029
+ scheme: "bearer",
1030
+ type: "http"
1031
+ }],
1032
+ url: "/domains/{id}/zone-file",
1033
+ ...options
1034
+ });
1035
+ /**
1010
1036
  * List inbound emails
1011
1037
  *
1012
1038
  * Returns a paginated list of INBOUND emails received at your
@@ -2333,7 +2359,7 @@ const openapiDocument = {
2333
2359
  "post": {
2334
2360
  "operationId": "addDomain",
2335
2361
  "summary": "Claim a new domain",
2336
- "description": "Creates an unverified domain claim. You will receive a\n`verification_token` to add as a DNS TXT record before\ncalling the verify endpoint.\n",
2362
+ "description": "Creates an unverified domain claim and returns the exact\nDNS records to publish in `dns_records`. Publish those\nrecords before calling the verify endpoint. To give users\nan importable DNS file, call `downloadDomainZoneFile` or run\n`primitive domains zone-file --id <domain-id>`.\n",
2337
2363
  "tags": ["Domains"],
2338
2364
  "requestBody": {
2339
2365
  "required": true,
@@ -2426,7 +2452,7 @@ const openapiDocument = {
2426
2452
  "post": {
2427
2453
  "operationId": "verifyDomain",
2428
2454
  "summary": "Verify domain ownership",
2429
- "description": "Checks DNS records (MX and TXT) to verify domain ownership.\nOn success, the domain is promoted from unverified to verified.\nOn failure, returns which checks passed and which failed.\n",
2455
+ "description": "Checks DNS records required for inbound routing, ownership,\nand outbound authentication: MX, ownership TXT, SPF, DKIM,\nDMARC, and TLS-RPT.\nOn success, the domain is promoted from unverified to verified.\nOn failure, returns which checks passed and which failed,\nplus the exact DNS records still expected. To give users\nan importable DNS file for missing records, call\n`downloadDomainZoneFile` or run\n`primitive domains zone-file --id <domain-id>`.\n",
2430
2456
  "tags": ["Domains"],
2431
2457
  "responses": {
2432
2458
  "200": {
@@ -2442,6 +2468,38 @@ const openapiDocument = {
2442
2468
  }
2443
2469
  }
2444
2470
  },
2471
+ "/domains/{id}/zone-file": {
2472
+ "parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
2473
+ "get": {
2474
+ "operationId": "downloadDomainZoneFile",
2475
+ "summary": "Download domain DNS zone file",
2476
+ "description": "Downloads a BIND-format DNS zone file containing the DNS records\nrequired for a domain claim. Agents should offer this after\n`addDomain` when users want to import DNS records instead of\ncopying each record manually.\n",
2477
+ "tags": ["Domains"],
2478
+ "parameters": [{
2479
+ "name": "outbound_only",
2480
+ "in": "query",
2481
+ "schema": { "type": "boolean" },
2482
+ "description": "When true, include only outbound DNS records. Verified domains\ndefault to outbound-only; pending claims default to all required\nrecords.\n"
2483
+ }],
2484
+ "responses": {
2485
+ "200": {
2486
+ "description": "BIND-format zone file",
2487
+ "content": { "text/plain": { "schema": {
2488
+ "type": "string",
2489
+ "format": "binary"
2490
+ } } },
2491
+ "headers": { "Content-Disposition": { "schema": {
2492
+ "type": "string",
2493
+ "example": "attachment; filename=\"example.com.zone\""
2494
+ } } }
2495
+ },
2496
+ "400": { "$ref": "#/components/responses/ValidationError" },
2497
+ "401": { "$ref": "#/components/responses/Unauthorized" },
2498
+ "404": { "$ref": "#/components/responses/NotFound" },
2499
+ "429": { "$ref": "#/components/responses/RateLimited" }
2500
+ }
2501
+ }
2502
+ },
2445
2503
  "/emails": { "get": {
2446
2504
  "operationId": "listEmails",
2447
2505
  "summary": "List inbound emails",
@@ -4731,7 +4789,7 @@ const openapiDocument = {
4731
4789
  "required": ["secret"]
4732
4790
  },
4733
4791
  "Domain": {
4734
- "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` for DNS verification.\n",
4792
+ "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",
4735
4793
  "oneOf": [{ "$ref": "#/components/schemas/VerifiedDomain" }, { "$ref": "#/components/schemas/UnverifiedDomain" }]
4736
4794
  },
4737
4795
  "VerifiedDomain": {
@@ -4771,6 +4829,74 @@ const openapiDocument = {
4771
4829
  "created_at"
4772
4830
  ]
4773
4831
  },
4832
+ "DomainDnsRecord": {
4833
+ "type": "object",
4834
+ "additionalProperties": false,
4835
+ "properties": {
4836
+ "type": {
4837
+ "type": "string",
4838
+ "enum": ["MX", "TXT"],
4839
+ "description": "DNS record type."
4840
+ },
4841
+ "name": {
4842
+ "type": "string",
4843
+ "description": "DNS-provider host/name value relative to the managed root zone."
4844
+ },
4845
+ "fqdn": {
4846
+ "type": "string",
4847
+ "description": "Fully-qualified DNS record name."
4848
+ },
4849
+ "value": {
4850
+ "type": "string",
4851
+ "description": "Exact value to publish."
4852
+ },
4853
+ "priority": {
4854
+ "type": "integer",
4855
+ "description": "MX priority. Present only for MX records."
4856
+ },
4857
+ "ttl": {
4858
+ "type": "integer",
4859
+ "description": "Suggested TTL in seconds when the API can provide one."
4860
+ },
4861
+ "required": {
4862
+ "type": "boolean",
4863
+ "const": true
4864
+ },
4865
+ "purpose": {
4866
+ "type": "string",
4867
+ "enum": [
4868
+ "inbound_mx",
4869
+ "ownership_verification",
4870
+ "spf",
4871
+ "dkim",
4872
+ "dmarc",
4873
+ "tls_reporting"
4874
+ ]
4875
+ },
4876
+ "status": {
4877
+ "type": "string",
4878
+ "enum": [
4879
+ "pending",
4880
+ "found",
4881
+ "missing",
4882
+ "incorrect"
4883
+ ]
4884
+ },
4885
+ "message": {
4886
+ "type": "string",
4887
+ "description": "Short explanation of why this record is needed."
4888
+ }
4889
+ },
4890
+ "required": [
4891
+ "type",
4892
+ "name",
4893
+ "fqdn",
4894
+ "value",
4895
+ "required",
4896
+ "purpose",
4897
+ "status"
4898
+ ]
4899
+ },
4774
4900
  "UnverifiedDomain": {
4775
4901
  "type": "object",
4776
4902
  "properties": {
@@ -4791,6 +4917,11 @@ const openapiDocument = {
4791
4917
  "type": "string",
4792
4918
  "description": "Add this value as a TXT record to verify ownership"
4793
4919
  },
4920
+ "dns_records": {
4921
+ "type": "array",
4922
+ "description": "Exact DNS records to publish for this pending domain claim.",
4923
+ "items": { "$ref": "#/components/schemas/DomainDnsRecord" }
4924
+ },
4794
4925
  "created_at": {
4795
4926
  "type": "string",
4796
4927
  "format": "date-time"
@@ -4808,12 +4939,23 @@ const openapiDocument = {
4808
4939
  "AddDomainInput": {
4809
4940
  "type": "object",
4810
4941
  "additionalProperties": false,
4811
- "properties": { "domain": {
4812
- "type": "string",
4813
- "minLength": 1,
4814
- "maxLength": 253,
4815
- "description": "The domain name to claim (e.g. \"example.com\")"
4816
- } },
4942
+ "properties": {
4943
+ "domain": {
4944
+ "type": "string",
4945
+ "minLength": 1,
4946
+ "maxLength": 253,
4947
+ "description": "The domain name to claim (e.g. \"example.com\")"
4948
+ },
4949
+ "confirmed": {
4950
+ "type": "boolean",
4951
+ "description": "Set to true to confirm replacing an existing mailbox provider after an mx_conflict response."
4952
+ },
4953
+ "outbound": {
4954
+ "type": "boolean",
4955
+ "deprecated": true,
4956
+ "description": "Deprecated and ignored. Outbound DNS is provisioned for every new domain claim."
4957
+ }
4958
+ },
4817
4959
  "required": ["domain"]
4818
4960
  },
4819
4961
  "UpdateDomainInput": {
@@ -4835,10 +4977,17 @@ const openapiDocument = {
4835
4977
  },
4836
4978
  "DomainVerifyResult": { "oneOf": [{
4837
4979
  "type": "object",
4838
- "properties": { "verified": {
4839
- "type": "boolean",
4840
- "const": true
4841
- } },
4980
+ "properties": {
4981
+ "verified": {
4982
+ "type": "boolean",
4983
+ "const": true
4984
+ },
4985
+ "dns_records": {
4986
+ "type": "array",
4987
+ "description": "Exact DNS records checked for this verification attempt.",
4988
+ "items": { "$ref": "#/components/schemas/DomainDnsRecord" }
4989
+ }
4990
+ },
4842
4991
  "required": ["verified"]
4843
4992
  }, {
4844
4993
  "type": "object",
@@ -4855,6 +5004,27 @@ const openapiDocument = {
4855
5004
  "type": "boolean",
4856
5005
  "description": "Whether the TXT verification record was found"
4857
5006
  },
5007
+ "spfFound": {
5008
+ "type": "boolean",
5009
+ "description": "Whether the SPF record includes Primitive."
5010
+ },
5011
+ "dkimFound": {
5012
+ "type": "boolean",
5013
+ "description": "Whether the DKIM public key record was found."
5014
+ },
5015
+ "dmarcFound": {
5016
+ "type": "boolean",
5017
+ "description": "Whether the DMARC record was found."
5018
+ },
5019
+ "tlsRptFound": {
5020
+ "type": "boolean",
5021
+ "description": "Whether the TLS-RPT record was found."
5022
+ },
5023
+ "dns_records": {
5024
+ "type": "array",
5025
+ "description": "Exact DNS records checked for this verification attempt.",
5026
+ "items": { "$ref": "#/components/schemas/DomainDnsRecord" }
5027
+ },
4858
5028
  "error": {
4859
5029
  "type": "string",
4860
5030
  "description": "Human-readable verification failure reason"
@@ -5167,6 +5337,31 @@ const openapiDocument = {
5167
5337
  "created_at"
5168
5338
  ]
5169
5339
  },
5340
+ "SendMailAttachment": {
5341
+ "type": "object",
5342
+ "additionalProperties": false,
5343
+ "properties": {
5344
+ "filename": {
5345
+ "type": "string",
5346
+ "minLength": 1,
5347
+ "maxLength": 255,
5348
+ "description": "Attachment filename. Control characters are rejected."
5349
+ },
5350
+ "content_type": {
5351
+ "type": "string",
5352
+ "minLength": 1,
5353
+ "maxLength": 255,
5354
+ "description": "Optional MIME content type. Control characters are rejected."
5355
+ },
5356
+ "content_base64": {
5357
+ "type": "string",
5358
+ "minLength": 1,
5359
+ "maxLength": 44040192,
5360
+ "description": "Base64-encoded attachment bytes."
5361
+ }
5362
+ },
5363
+ "required": ["filename", "content_base64"]
5364
+ },
5170
5365
  "SendMailInput": {
5171
5366
  "type": "object",
5172
5367
  "additionalProperties": false,
@@ -5215,6 +5410,12 @@ const openapiDocument = {
5215
5410
  "pattern": "^[^\\x00-\\x1F\\x7F]+$"
5216
5411
  }
5217
5412
  },
5413
+ "attachments": {
5414
+ "type": "array",
5415
+ "maxItems": 100,
5416
+ "description": "Inline attachments. Send requests with attachments to https://api.primitive.dev/v1/send-mail. Combined raw decoded attachment bytes must be at most 31457280.",
5417
+ "items": { "$ref": "#/components/schemas/SendMailAttachment" }
5418
+ },
5218
5419
  "wait": {
5219
5420
  "type": "boolean",
5220
5421
  "description": "When true, wait for the first downstream SMTP delivery outcome before returning."
@@ -7502,7 +7703,7 @@ const operationManifest = [
7502
7703
  "binaryResponse": false,
7503
7704
  "bodyRequired": true,
7504
7705
  "command": "add-domain",
7505
- "description": "Creates an unverified domain claim. You will receive a\n`verification_token` to add as a DNS TXT record before\ncalling the verify endpoint.\n",
7706
+ "description": "Creates an unverified domain claim and returns the exact\nDNS records to publish in `dns_records`. Publish those\nrecords before calling the verify endpoint. To give users\nan importable DNS file, call `downloadDomainZoneFile` or run\n`primitive domains zone-file --id <domain-id>`.\n",
7506
7707
  "hasJsonBody": true,
7507
7708
  "method": "POST",
7508
7709
  "operationId": "addDomain",
@@ -7512,12 +7713,23 @@ const operationManifest = [
7512
7713
  "requestSchema": {
7513
7714
  "type": "object",
7514
7715
  "additionalProperties": false,
7515
- "properties": { "domain": {
7516
- "type": "string",
7517
- "minLength": 1,
7518
- "maxLength": 253,
7519
- "description": "The domain name to claim (e.g. \"example.com\")"
7520
- } },
7716
+ "properties": {
7717
+ "domain": {
7718
+ "type": "string",
7719
+ "minLength": 1,
7720
+ "maxLength": 253,
7721
+ "description": "The domain name to claim (e.g. \"example.com\")"
7722
+ },
7723
+ "confirmed": {
7724
+ "type": "boolean",
7725
+ "description": "Set to true to confirm replacing an existing mailbox provider after an mx_conflict response."
7726
+ },
7727
+ "outbound": {
7728
+ "type": "boolean",
7729
+ "deprecated": true,
7730
+ "description": "Deprecated and ignored. Outbound DNS is provisioned for every new domain claim."
7731
+ }
7732
+ },
7521
7733
  "required": ["domain"]
7522
7734
  },
7523
7735
  "responseSchema": {
@@ -7540,6 +7752,78 @@ const operationManifest = [
7540
7752
  "type": "string",
7541
7753
  "description": "Add this value as a TXT record to verify ownership"
7542
7754
  },
7755
+ "dns_records": {
7756
+ "type": "array",
7757
+ "description": "Exact DNS records to publish for this pending domain claim.",
7758
+ "items": {
7759
+ "type": "object",
7760
+ "additionalProperties": false,
7761
+ "properties": {
7762
+ "type": {
7763
+ "type": "string",
7764
+ "enum": ["MX", "TXT"],
7765
+ "description": "DNS record type."
7766
+ },
7767
+ "name": {
7768
+ "type": "string",
7769
+ "description": "DNS-provider host/name value relative to the managed root zone."
7770
+ },
7771
+ "fqdn": {
7772
+ "type": "string",
7773
+ "description": "Fully-qualified DNS record name."
7774
+ },
7775
+ "value": {
7776
+ "type": "string",
7777
+ "description": "Exact value to publish."
7778
+ },
7779
+ "priority": {
7780
+ "type": "integer",
7781
+ "description": "MX priority. Present only for MX records."
7782
+ },
7783
+ "ttl": {
7784
+ "type": "integer",
7785
+ "description": "Suggested TTL in seconds when the API can provide one."
7786
+ },
7787
+ "required": {
7788
+ "type": "boolean",
7789
+ "const": true
7790
+ },
7791
+ "purpose": {
7792
+ "type": "string",
7793
+ "enum": [
7794
+ "inbound_mx",
7795
+ "ownership_verification",
7796
+ "spf",
7797
+ "dkim",
7798
+ "dmarc",
7799
+ "tls_reporting"
7800
+ ]
7801
+ },
7802
+ "status": {
7803
+ "type": "string",
7804
+ "enum": [
7805
+ "pending",
7806
+ "found",
7807
+ "missing",
7808
+ "incorrect"
7809
+ ]
7810
+ },
7811
+ "message": {
7812
+ "type": "string",
7813
+ "description": "Short explanation of why this record is needed."
7814
+ }
7815
+ },
7816
+ "required": [
7817
+ "type",
7818
+ "name",
7819
+ "fqdn",
7820
+ "value",
7821
+ "required",
7822
+ "purpose",
7823
+ "status"
7824
+ ]
7825
+ }
7826
+ },
7543
7827
  "created_at": {
7544
7828
  "type": "string",
7545
7829
  "format": "date-time"
@@ -7583,6 +7867,36 @@ const operationManifest = [
7583
7867
  "tag": "Domains",
7584
7868
  "tagCommand": "domains"
7585
7869
  },
7870
+ {
7871
+ "binaryResponse": true,
7872
+ "bodyRequired": false,
7873
+ "command": "download-domain-zone-file",
7874
+ "description": "Downloads a BIND-format DNS zone file containing the DNS records\nrequired for a domain claim. Agents should offer this after\n`addDomain` when users want to import DNS records instead of\ncopying each record manually.\n",
7875
+ "hasJsonBody": false,
7876
+ "method": "GET",
7877
+ "operationId": "downloadDomainZoneFile",
7878
+ "path": "/domains/{id}/zone-file",
7879
+ "pathParams": [{
7880
+ "description": "Resource UUID",
7881
+ "enum": null,
7882
+ "name": "id",
7883
+ "required": true,
7884
+ "type": "string"
7885
+ }],
7886
+ "queryParams": [{
7887
+ "description": "When true, include only outbound DNS records. Verified domains\ndefault to outbound-only; pending claims default to all required\nrecords.\n",
7888
+ "enum": null,
7889
+ "name": "outbound_only",
7890
+ "required": false,
7891
+ "type": "boolean"
7892
+ }],
7893
+ "requestSchema": null,
7894
+ "responseSchema": null,
7895
+ "sdkName": "downloadDomainZoneFile",
7896
+ "summary": "Download domain DNS zone file",
7897
+ "tag": "Domains",
7898
+ "tagCommand": "domains"
7899
+ },
7586
7900
  {
7587
7901
  "binaryResponse": false,
7588
7902
  "bodyRequired": false,
@@ -7598,7 +7912,7 @@ const operationManifest = [
7598
7912
  "responseSchema": {
7599
7913
  "type": "array",
7600
7914
  "items": {
7601
- "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` for DNS verification.\n",
7915
+ "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",
7602
7916
  "oneOf": [{
7603
7917
  "type": "object",
7604
7918
  "properties": {
@@ -7655,6 +7969,78 @@ const operationManifest = [
7655
7969
  "type": "string",
7656
7970
  "description": "Add this value as a TXT record to verify ownership"
7657
7971
  },
7972
+ "dns_records": {
7973
+ "type": "array",
7974
+ "description": "Exact DNS records to publish for this pending domain claim.",
7975
+ "items": {
7976
+ "type": "object",
7977
+ "additionalProperties": false,
7978
+ "properties": {
7979
+ "type": {
7980
+ "type": "string",
7981
+ "enum": ["MX", "TXT"],
7982
+ "description": "DNS record type."
7983
+ },
7984
+ "name": {
7985
+ "type": "string",
7986
+ "description": "DNS-provider host/name value relative to the managed root zone."
7987
+ },
7988
+ "fqdn": {
7989
+ "type": "string",
7990
+ "description": "Fully-qualified DNS record name."
7991
+ },
7992
+ "value": {
7993
+ "type": "string",
7994
+ "description": "Exact value to publish."
7995
+ },
7996
+ "priority": {
7997
+ "type": "integer",
7998
+ "description": "MX priority. Present only for MX records."
7999
+ },
8000
+ "ttl": {
8001
+ "type": "integer",
8002
+ "description": "Suggested TTL in seconds when the API can provide one."
8003
+ },
8004
+ "required": {
8005
+ "type": "boolean",
8006
+ "const": true
8007
+ },
8008
+ "purpose": {
8009
+ "type": "string",
8010
+ "enum": [
8011
+ "inbound_mx",
8012
+ "ownership_verification",
8013
+ "spf",
8014
+ "dkim",
8015
+ "dmarc",
8016
+ "tls_reporting"
8017
+ ]
8018
+ },
8019
+ "status": {
8020
+ "type": "string",
8021
+ "enum": [
8022
+ "pending",
8023
+ "found",
8024
+ "missing",
8025
+ "incorrect"
8026
+ ]
8027
+ },
8028
+ "message": {
8029
+ "type": "string",
8030
+ "description": "Short explanation of why this record is needed."
8031
+ }
8032
+ },
8033
+ "required": [
8034
+ "type",
8035
+ "name",
8036
+ "fqdn",
8037
+ "value",
8038
+ "required",
8039
+ "purpose",
8040
+ "status"
8041
+ ]
8042
+ }
8043
+ },
7658
8044
  "created_at": {
7659
8045
  "type": "string",
7660
8046
  "format": "date-time"
@@ -7756,7 +8142,7 @@ const operationManifest = [
7756
8142
  "binaryResponse": false,
7757
8143
  "bodyRequired": false,
7758
8144
  "command": "verify-domain",
7759
- "description": "Checks DNS records (MX and TXT) to verify domain ownership.\nOn success, the domain is promoted from unverified to verified.\nOn failure, returns which checks passed and which failed.\n",
8145
+ "description": "Checks DNS records required for inbound routing, ownership,\nand outbound authentication: MX, ownership TXT, SPF, DKIM,\nDMARC, and TLS-RPT.\nOn success, the domain is promoted from unverified to verified.\nOn failure, returns which checks passed and which failed,\nplus the exact DNS records still expected. To give users\nan importable DNS file for missing records, call\n`downloadDomainZoneFile` or run\n`primitive domains zone-file --id <domain-id>`.\n",
7760
8146
  "hasJsonBody": false,
7761
8147
  "method": "POST",
7762
8148
  "operationId": "verifyDomain",
@@ -7772,10 +8158,84 @@ const operationManifest = [
7772
8158
  "requestSchema": null,
7773
8159
  "responseSchema": { "oneOf": [{
7774
8160
  "type": "object",
7775
- "properties": { "verified": {
7776
- "type": "boolean",
7777
- "const": true
7778
- } },
8161
+ "properties": {
8162
+ "verified": {
8163
+ "type": "boolean",
8164
+ "const": true
8165
+ },
8166
+ "dns_records": {
8167
+ "type": "array",
8168
+ "description": "Exact DNS records checked for this verification attempt.",
8169
+ "items": {
8170
+ "type": "object",
8171
+ "additionalProperties": false,
8172
+ "properties": {
8173
+ "type": {
8174
+ "type": "string",
8175
+ "enum": ["MX", "TXT"],
8176
+ "description": "DNS record type."
8177
+ },
8178
+ "name": {
8179
+ "type": "string",
8180
+ "description": "DNS-provider host/name value relative to the managed root zone."
8181
+ },
8182
+ "fqdn": {
8183
+ "type": "string",
8184
+ "description": "Fully-qualified DNS record name."
8185
+ },
8186
+ "value": {
8187
+ "type": "string",
8188
+ "description": "Exact value to publish."
8189
+ },
8190
+ "priority": {
8191
+ "type": "integer",
8192
+ "description": "MX priority. Present only for MX records."
8193
+ },
8194
+ "ttl": {
8195
+ "type": "integer",
8196
+ "description": "Suggested TTL in seconds when the API can provide one."
8197
+ },
8198
+ "required": {
8199
+ "type": "boolean",
8200
+ "const": true
8201
+ },
8202
+ "purpose": {
8203
+ "type": "string",
8204
+ "enum": [
8205
+ "inbound_mx",
8206
+ "ownership_verification",
8207
+ "spf",
8208
+ "dkim",
8209
+ "dmarc",
8210
+ "tls_reporting"
8211
+ ]
8212
+ },
8213
+ "status": {
8214
+ "type": "string",
8215
+ "enum": [
8216
+ "pending",
8217
+ "found",
8218
+ "missing",
8219
+ "incorrect"
8220
+ ]
8221
+ },
8222
+ "message": {
8223
+ "type": "string",
8224
+ "description": "Short explanation of why this record is needed."
8225
+ }
8226
+ },
8227
+ "required": [
8228
+ "type",
8229
+ "name",
8230
+ "fqdn",
8231
+ "value",
8232
+ "required",
8233
+ "purpose",
8234
+ "status"
8235
+ ]
8236
+ }
8237
+ }
8238
+ },
7779
8239
  "required": ["verified"]
7780
8240
  }, {
7781
8241
  "type": "object",
@@ -7792,6 +8252,94 @@ const operationManifest = [
7792
8252
  "type": "boolean",
7793
8253
  "description": "Whether the TXT verification record was found"
7794
8254
  },
8255
+ "spfFound": {
8256
+ "type": "boolean",
8257
+ "description": "Whether the SPF record includes Primitive."
8258
+ },
8259
+ "dkimFound": {
8260
+ "type": "boolean",
8261
+ "description": "Whether the DKIM public key record was found."
8262
+ },
8263
+ "dmarcFound": {
8264
+ "type": "boolean",
8265
+ "description": "Whether the DMARC record was found."
8266
+ },
8267
+ "tlsRptFound": {
8268
+ "type": "boolean",
8269
+ "description": "Whether the TLS-RPT record was found."
8270
+ },
8271
+ "dns_records": {
8272
+ "type": "array",
8273
+ "description": "Exact DNS records checked for this verification attempt.",
8274
+ "items": {
8275
+ "type": "object",
8276
+ "additionalProperties": false,
8277
+ "properties": {
8278
+ "type": {
8279
+ "type": "string",
8280
+ "enum": ["MX", "TXT"],
8281
+ "description": "DNS record type."
8282
+ },
8283
+ "name": {
8284
+ "type": "string",
8285
+ "description": "DNS-provider host/name value relative to the managed root zone."
8286
+ },
8287
+ "fqdn": {
8288
+ "type": "string",
8289
+ "description": "Fully-qualified DNS record name."
8290
+ },
8291
+ "value": {
8292
+ "type": "string",
8293
+ "description": "Exact value to publish."
8294
+ },
8295
+ "priority": {
8296
+ "type": "integer",
8297
+ "description": "MX priority. Present only for MX records."
8298
+ },
8299
+ "ttl": {
8300
+ "type": "integer",
8301
+ "description": "Suggested TTL in seconds when the API can provide one."
8302
+ },
8303
+ "required": {
8304
+ "type": "boolean",
8305
+ "const": true
8306
+ },
8307
+ "purpose": {
8308
+ "type": "string",
8309
+ "enum": [
8310
+ "inbound_mx",
8311
+ "ownership_verification",
8312
+ "spf",
8313
+ "dkim",
8314
+ "dmarc",
8315
+ "tls_reporting"
8316
+ ]
8317
+ },
8318
+ "status": {
8319
+ "type": "string",
8320
+ "enum": [
8321
+ "pending",
8322
+ "found",
8323
+ "missing",
8324
+ "incorrect"
8325
+ ]
8326
+ },
8327
+ "message": {
8328
+ "type": "string",
8329
+ "description": "Short explanation of why this record is needed."
8330
+ }
8331
+ },
8332
+ "required": [
8333
+ "type",
8334
+ "name",
8335
+ "fqdn",
8336
+ "value",
8337
+ "required",
8338
+ "purpose",
8339
+ "status"
8340
+ ]
8341
+ }
8342
+ },
7795
8343
  "error": {
7796
8344
  "type": "string",
7797
8345
  "description": "Human-readable verification failure reason"
@@ -11165,6 +11713,36 @@ const operationManifest = [
11165
11713
  "pattern": "^[^\\x00-\\x1F\\x7F]+$"
11166
11714
  }
11167
11715
  },
11716
+ "attachments": {
11717
+ "type": "array",
11718
+ "maxItems": 100,
11719
+ "description": "Inline attachments. Send requests with attachments to https://api.primitive.dev/v1/send-mail. Combined raw decoded attachment bytes must be at most 31457280.",
11720
+ "items": {
11721
+ "type": "object",
11722
+ "additionalProperties": false,
11723
+ "properties": {
11724
+ "filename": {
11725
+ "type": "string",
11726
+ "minLength": 1,
11727
+ "maxLength": 255,
11728
+ "description": "Attachment filename. Control characters are rejected."
11729
+ },
11730
+ "content_type": {
11731
+ "type": "string",
11732
+ "minLength": 1,
11733
+ "maxLength": 255,
11734
+ "description": "Optional MIME content type. Control characters are rejected."
11735
+ },
11736
+ "content_base64": {
11737
+ "type": "string",
11738
+ "minLength": 1,
11739
+ "maxLength": 44040192,
11740
+ "description": "Base64-encoded attachment bytes."
11741
+ }
11742
+ },
11743
+ "required": ["filename", "content_base64"]
11744
+ }
11745
+ },
11168
11746
  "wait": {
11169
11747
  "type": "boolean",
11170
11748
  "description": "When true, wait for the first downstream SMTP delivery outcome before returning."
@@ -11508,7 +12086,7 @@ var PrimitiveApiClient = class {
11508
12086
  const CREDENTIALS_FILE = "credentials.json";
11509
12087
  const CREDENTIALS_LOCK_DIR = "credentials.lock";
11510
12088
  const CREDENTIALS_LOCK_STALE_MS = 1800 * 1e3;
11511
- const MALFORMED_CREDENTIALS_HINT = "Run `primitive logout` and then `primitive login`.";
12089
+ const MALFORMED_CREDENTIALS_HINT = "Run `primitive logout` and then `primitive signin`.";
11512
12090
  function isRecord$2(value) {
11513
12091
  return value !== null && typeof value === "object" && !Array.isArray(value);
11514
12092
  }
@@ -11586,10 +12164,10 @@ function loadCliCredentials(configDir) {
11586
12164
  try {
11587
12165
  rmSync(path, { force: true });
11588
12166
  } catch {}
11589
- process.stderr.write("Removed local Primitive CLI API-key login state. API keys are still valid when passed explicitly, but `primitive login` now uses OAuth. Run `primitive login` to create an OAuth session. No API key was revoked.\n");
12167
+ process.stderr.write("Removed local Primitive CLI API-key login state. API keys are still valid when passed explicitly, but saved CLI auth now uses OAuth. Run `primitive signin` to create an OAuth session. No API key was revoked.\n");
11590
12168
  return null;
11591
12169
  }
11592
- if (error instanceof SyntaxError) throw new Error("Stored Primitive CLI credentials are not valid JSON. Run `primitive logout` and then `primitive login`.");
12170
+ if (error instanceof SyntaxError) throw new Error("Stored Primitive CLI credentials are not valid JSON. Run `primitive logout` and then `primitive signin`.");
11593
12171
  throw error;
11594
12172
  }
11595
12173
  }
@@ -11872,7 +12450,7 @@ function redactCliEnvironment(environment) {
11872
12450
  //#region src/oclif/api-client.ts
11873
12451
  const API_HEADERS_ENV = "PRIMITIVE_API_HEADERS";
11874
12452
  const OAUTH_REFRESH_SKEW_MS = 60 * 1e3;
11875
- const SAVED_CLI_OAUTH_SESSION_EXPIRED_MESSAGE = "Saved Primitive CLI OAuth session expired or was revoked. Run `primitive login` to authenticate again.";
12453
+ const SAVED_CLI_OAUTH_SESSION_EXPIRED_MESSAGE = "Saved Primitive CLI OAuth session expired or was revoked. Run `primitive signin` to authenticate again.";
11876
12454
  function mergeHeaders(...headers) {
11877
12455
  const merged = {};
11878
12456
  for (const headerSet of headers) {
@@ -11970,7 +12548,7 @@ async function refreshStoredCliCredentials(params) {
11970
12548
  throw new Errors.CLIError(detail, { exit: 1 });
11971
12549
  }
11972
12550
  const current = loadCliCredentials(params.configDir);
11973
- if (!current) throw new Errors.CLIError("Saved Primitive CLI OAuth session is no longer available. Run `primitive login` to authenticate again.", { exit: 1 });
12551
+ if (!current) throw new Errors.CLIError("Saved Primitive CLI OAuth session is no longer available. Run `primitive signin` to authenticate again.", { exit: 1 });
11974
12552
  if (!shouldRefresh(current, now)) return current;
11975
12553
  const fetchImpl = params.fetch ?? fetch;
11976
12554
  const body = new URLSearchParams({
@@ -12279,26 +12857,26 @@ function coerceParameterValue(parameter, value) {
12279
12857
  if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") return value;
12280
12858
  throw new Errors.CLIError(`Unsupported flag value for --${parameter.name}`);
12281
12859
  }
12282
- function cliError$6(message) {
12860
+ function cliError$7(message) {
12283
12861
  return new Errors.CLIError(message, { exit: 1 });
12284
12862
  }
12285
12863
  function parseJson(source, flagLabel) {
12286
12864
  try {
12287
12865
  return JSON.parse(source);
12288
12866
  } catch (error) {
12289
- throw cliError$6(`${flagLabel} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
12867
+ throw cliError$7(`${flagLabel} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
12290
12868
  }
12291
12869
  }
12292
12870
  function readJsonBody(flags) {
12293
12871
  const bodyFile = flags["body-file"];
12294
12872
  const rawBody = flags["raw-body"];
12295
- if (bodyFile && rawBody) throw cliError$6("Use either --raw-body or --body-file, not both");
12873
+ if (bodyFile && rawBody) throw cliError$7("Use either --raw-body or --body-file, not both");
12296
12874
  if (typeof bodyFile === "string") {
12297
12875
  let contents;
12298
12876
  try {
12299
12877
  contents = readFileSync(bodyFile, "utf8");
12300
12878
  } catch (error) {
12301
- throw cliError$6(`Could not read --body-file ${bodyFile}: ${error instanceof Error ? error.message : String(error)}`);
12879
+ throw cliError$7(`Could not read --body-file ${bodyFile}: ${error instanceof Error ? error.message : String(error)}`);
12302
12880
  }
12303
12881
  return parseJson(contents, `--body-file ${bodyFile}`);
12304
12882
  }
@@ -12308,7 +12886,7 @@ function readTextFileFlag(path, flagLabel) {
12308
12886
  try {
12309
12887
  return readFileSync(path, "utf8");
12310
12888
  } catch (error) {
12311
- throw cliError$6(`Could not read ${flagLabel} ${path}: ${error instanceof Error ? error.message : String(error)}`);
12889
+ throw cliError$7(`Could not read ${flagLabel} ${path}: ${error instanceof Error ? error.message : String(error)}`);
12312
12890
  }
12313
12891
  }
12314
12892
  function extractErrorPayload(raw) {
@@ -12355,7 +12933,7 @@ function extractErrorCode(payload) {
12355
12933
  if (typeof direct === "string") return direct;
12356
12934
  }
12357
12935
  }
12358
- const ERROR_CODE_HINTS = { [API_ERROR_CODES.unauthorized]: "Hint: run `primitive login`, pass --api-key explicitly, or set PRIMITIVE_API_KEY in your environment. `primitive whoami` is the fastest way to verify auth is live." };
12936
+ const ERROR_CODE_HINTS = { [API_ERROR_CODES.unauthorized]: "Hint: run `primitive signin`, pass --api-key explicitly, or set PRIMITIVE_API_KEY in your environment. `primitive whoami` is the fastest way to verify auth is live." };
12359
12937
  const NETWORK_ERROR_HINTS = {
12360
12938
  ENETUNREACH: "Hint: the network is unreachable. If you're behind a proxy and set HTTP(S)_PROXY, re-run with NODE_USE_ENV_PROXY=1 (Node 22+ ignores those env vars by default). `primitive doctor` reports the local environment in one shot.",
12361
12939
  ECONNREFUSED: "Hint: the server refused the connection. Check that your firewall allows egress to *.primitive.dev, that your PRIMITIVE_API_BASE_URL_* overrides (if any) point at a reachable host, and re-run with NODE_USE_ENV_PROXY=1 if you're behind a proxy. `primitive doctor` reports the local environment in one shot.",
@@ -12393,7 +12971,7 @@ function surfaceUnauthorizedHint(params) {
12393
12971
  process.stderr.write("Saved Primitive CLI credentials were rejected by the overridden API base URL. The saved credential is preserved; unset PRIMITIVE_API_BASE_URL_1, run `primitive config reset` to clear configured URL overrides, or run `primitive logout` to remove the stored credential.\n");
12394
12972
  return;
12395
12973
  }
12396
- 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 login` to mint a fresh session.\n");
12974
+ 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");
12397
12975
  }
12398
12976
  function formatElapsed(ms) {
12399
12977
  const seconds = ms / 1e3;
@@ -12501,6 +13079,10 @@ function operationOutputPayload(envelope, includeEnvelope) {
12501
13079
  return includeEnvelope ? envelope ?? null : envelope?.data ?? null;
12502
13080
  }
12503
13081
  const OPERATION_HINTS = {
13082
+ 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
+ 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
+ downloadDomainZoneFile: "Tip: prefer `primitive domains zone-file --id <domain-id> --output <domain>.zone` for CLI-friendly file output.",
13085
+ sendEmail: "Tip: prefer `primitive send --to <address> --body <text> --attachment <file>` for file attachments. This raw command exists for callers passing JSON.",
12504
13086
  createFunction: "Tip: prefer `primitive functions deploy --name <name> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
12505
13087
  updateFunction: "Tip: prefer `primitive functions redeploy --id <id> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
12506
13088
  createFunctionSecret: "Tip: prefer `primitive functions set-secret --id <id> --key <KEY> --value <value> [--redeploy]` for secret writes that also push the binding live. This raw command exists for callers passing JSON.",
@@ -12612,7 +13194,8 @@ const EMPTY_RESULT_HINTS = {
12612
13194
  listEndpoints: "(no results) No webhook endpoints configured. Add one with `primitive endpoints create --url <your-url>`.",
12613
13195
  listEmails: "(no results) No inbound emails received yet on this account. Send one to a verified domain to populate this list. For a compact view, prefer `primitive emails latest`.",
12614
13196
  listDomains: "(no results) No domains on this account. Add one with `primitive domains add --domain <yourdomain.example>`.",
12615
- listFilters: "(no results) No filter rules configured."
13197
+ listFilters: "(no results) No filter rules configured.",
13198
+ listFunctions: "(no results) No Functions configured yet. Start with `primitive functions templates`, then `primitive functions init --template <template>` and `primitive functions deploy --name <name> --file <bundle>`."
12616
13199
  };
12617
13200
  function canonicalizeCliReferences(description) {
12618
13201
  return description.replaceAll("`primitive emails:latest`", "`primitive emails latest`").replaceAll("`primitive describe emails:get-email | jq '.responseSchema.properties'`", "`primitive describe emails:get | jq '.responseSchema.properties'`");
@@ -12756,11 +13339,11 @@ function sleep$1(ms) {
12756
13339
  //#region src/oclif/commands/chat.ts
12757
13340
  const DEFAULT_CHAT_TIMEOUT_SECONDS = 120;
12758
13341
  const DEFAULT_STRICT_PHASE_SECONDS = 60;
12759
- function cliError$5(message) {
13342
+ function cliError$6(message) {
12760
13343
  return new Errors.CLIError(message, { exit: 1 });
12761
13344
  }
12762
13345
  async function readStdinToString() {
12763
- if (process.stdin.isTTY) throw cliError$5("No message provided. Pass the message as the second positional argument or pipe it via stdin.");
13346
+ if (process.stdin.isTTY) throw cliError$6("No message provided. Pass the message as the second positional argument or pipe it via stdin.");
12764
13347
  const chunks = [];
12765
13348
  for await (const chunk of process.stdin) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
12766
13349
  return Buffer.concat(chunks).toString("utf8");
@@ -12796,7 +13379,7 @@ var ChatCommand = class ChatCommand extends Command {
12796
13379
  };
12797
13380
  static flags = {
12798
13381
  "api-key": Flags.string({
12799
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
13382
+ description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive signin` credentials)",
12800
13383
  env: "PRIMITIVE_API_KEY"
12801
13384
  }),
12802
13385
  "api-base-url-1": Flags.string({
@@ -12841,7 +13424,7 @@ var ChatCommand = class ChatCommand extends Command {
12841
13424
  async run() {
12842
13425
  const { args, flags } = await this.parse(ChatCommand);
12843
13426
  const message = args.message !== void 0 && args.message !== "" ? args.message : await readStdinToString();
12844
- if (!message.trim()) throw cliError$5("Message body is empty.");
13427
+ if (!message.trim()) throw cliError$6("Message body is empty.");
12845
13428
  await runWithTiming(flags.time, async () => {
12846
13429
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
12847
13430
  apiKey: flags["api-key"],
@@ -12879,7 +13462,7 @@ var ChatCommand = class ChatCommand extends Command {
12879
13462
  return;
12880
13463
  }
12881
13464
  const sent = sendResult.data?.data;
12882
- if (!sent) throw cliError$5("Send succeeded but the API returned no data.");
13465
+ if (!sent) throw cliError$6("Send succeeded but the API returned no data.");
12883
13466
  const reply = await waitForReply({
12884
13467
  apiClient,
12885
13468
  authFailureContext,
@@ -13203,23 +13786,23 @@ function checkApiKey(opts) {
13203
13786
  if (typeof parsed?.api_key === "string" && parsed.api_key.length > 0) return {
13204
13787
  status: "fail",
13205
13788
  message: `${credsPath} contains legacy API-key login state`,
13206
- hint: "Run `primitive login` to create saved OAuth credentials. Existing API keys still work with --api-key or PRIMITIVE_API_KEY."
13789
+ hint: "Run `primitive signin` to create saved OAuth credentials. Existing API keys still work with --api-key or PRIMITIVE_API_KEY."
13207
13790
  };
13208
13791
  if (parsed) return {
13209
13792
  status: "fail",
13210
13793
  message: `${credsPath} exists but contains no OAuth access_token`,
13211
- hint: "Run `primitive logout` to clear it, then `primitive login` to recreate."
13794
+ hint: "Run `primitive logout` to clear it, then `primitive signin` to recreate."
13212
13795
  };
13213
13796
  return {
13214
13797
  status: "fail",
13215
13798
  message: `${credsPath} exists but is unreadable or malformed${parseError ? ` (${parseError})` : ""}`,
13216
- hint: "Run `primitive logout` to clear it, then `primitive login` to recreate."
13799
+ hint: "Run `primitive logout` to clear it, then `primitive signin` to recreate."
13217
13800
  };
13218
13801
  }
13219
13802
  return {
13220
13803
  status: "fail",
13221
13804
  message: "no CLI OAuth session or explicit API key found",
13222
- hint: "Run `primitive login`, pass --api-key explicitly, or export PRIMITIVE_API_KEY=prim_..."
13805
+ hint: "Run `primitive signin`, pass --api-key explicitly, or export PRIMITIVE_API_KEY=prim_..."
13223
13806
  };
13224
13807
  }
13225
13808
  async function checkAccount(client) {
@@ -13379,6 +13962,144 @@ var DoctorCommand = class DoctorCommand extends Command {
13379
13962
  }
13380
13963
  };
13381
13964
  //#endregion
13965
+ //#region src/oclif/commands/domains-zone-file.ts
13966
+ function zoneFileUrl(baseUrl, domainId, outboundOnly) {
13967
+ const url = new URL(`${baseUrl.replace(/\/$/, "")}/domains/${encodeURIComponent(domainId)}/zone-file`);
13968
+ if (outboundOnly) url.searchParams.set("outbound_only", "true");
13969
+ return url.toString();
13970
+ }
13971
+ function contentDispositionFilename(value) {
13972
+ if (!value) return null;
13973
+ const match = /filename\*=UTF-8''([^;]+)|filename="?([^";]+)"?/i.exec(value);
13974
+ const raw = match?.[1] ?? match?.[2];
13975
+ if (!raw) return null;
13976
+ try {
13977
+ return decodeURIComponent(raw);
13978
+ } catch {
13979
+ return raw;
13980
+ }
13981
+ }
13982
+ function findDomain(domains, domain) {
13983
+ const match = domains.find((entry) => entry.domain === domain);
13984
+ if (!match) throw new Errors.CLIError(`Domain ${domain} was not found.`, { exit: 1 });
13985
+ return match;
13986
+ }
13987
+ async function responseErrorPayload(response) {
13988
+ if ((response.headers.get("content-type")?.toLowerCase() ?? "").includes("application/json")) return response.json().catch(() => ({
13989
+ code: "http_error",
13990
+ message: `HTTP ${response.status} ${response.statusText}`.trim()
13991
+ }));
13992
+ return {
13993
+ code: "http_error",
13994
+ message: (await response.text().catch(() => "")).trim() || `HTTP ${response.status} ${response.statusText}`.trim()
13995
+ };
13996
+ }
13997
+ var DomainsZoneFileCommand = class DomainsZoneFileCommand extends Command {
13998
+ static description = `Download a BIND-format DNS zone file for a domain claim.
13999
+
14000
+ Use this when a DNS provider supports zone-file import and you want to publish all required records at once instead of copying each record manually. The file is generated by the Primitive API, matching the dashboard download flow.
14001
+
14002
+ Agents: after claiming a domain, tell users they can run \`primitive domains zone-file --id <domain-id> --output <domain>.zone\` to get an importable DNS zone file.`;
14003
+ static summary = "Download a DNS zone file for a domain";
14004
+ static examples = [
14005
+ "<%= config.bin %> domains zone-file --id <domain-id>",
14006
+ "<%= config.bin %> domains zone-file --id <domain-id> --output example.com.zone",
14007
+ "<%= config.bin %> domains zone-file --domain example.com --output example.com.zone",
14008
+ "<%= config.bin %> domains zone-file --id <domain-id> --outbound-only"
14009
+ ];
14010
+ static flags = {
14011
+ "api-key": Flags.string({
14012
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
14013
+ env: "PRIMITIVE_API_KEY"
14014
+ }),
14015
+ "api-base-url-1": Flags.string({
14016
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
14017
+ env: "PRIMITIVE_API_BASE_URL_1",
14018
+ hidden: true
14019
+ }),
14020
+ "api-base-url-2": Flags.string({
14021
+ description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
14022
+ env: "PRIMITIVE_API_BASE_URL_2",
14023
+ hidden: true
14024
+ }),
14025
+ domain: Flags.string({ description: "Domain name to look up before downloading its zone file. Prefer --id when you have the domain id from `primitive domains add`." }),
14026
+ id: Flags.string({ description: "Domain id returned by `primitive domains add` or `primitive domains list`." }),
14027
+ output: Flags.string({
14028
+ char: "o",
14029
+ description: "Write the zone file to this path instead of stdout. Defaults to stdout."
14030
+ }),
14031
+ "outbound-only": Flags.boolean({ description: "Include only outbound DNS records. Verified domains default to outbound-only at the API; this flag is explicit for agents and scripts." }),
14032
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
14033
+ };
14034
+ async run() {
14035
+ const { flags } = await this.parse(DomainsZoneFileCommand);
14036
+ if (flags.id && flags.domain) throw new Errors.CLIError("Use only one of --id or --domain.", { exit: 1 });
14037
+ if (!flags.id && !flags.domain) throw new Errors.CLIError("Pass --id <domain-id> or --domain <domain>.", { exit: 1 });
14038
+ await runWithTiming(flags.time, async () => {
14039
+ const { apiClient, auth, baseUrlOverridden, requestConfig } = await createAuthenticatedCliApiClient({
14040
+ apiKey: flags["api-key"],
14041
+ apiBaseUrl1: flags["api-base-url-1"],
14042
+ apiBaseUrl2: flags["api-base-url-2"],
14043
+ configDir: this.config.configDir
14044
+ });
14045
+ let domainId = flags.id;
14046
+ if (!domainId && flags.domain) {
14047
+ const result = await listDomains({
14048
+ client: apiClient.client,
14049
+ responseStyle: "fields"
14050
+ });
14051
+ if (result.error) {
14052
+ const errorPayload = extractErrorPayload(result.error);
14053
+ writeErrorWithHints(errorPayload);
14054
+ surfaceUnauthorizedHint({
14055
+ auth,
14056
+ baseUrlOverridden,
14057
+ configDir: this.config.configDir,
14058
+ payload: errorPayload
14059
+ });
14060
+ process.exitCode = 1;
14061
+ return;
14062
+ }
14063
+ const envelope = result.data;
14064
+ domainId = findDomain(envelope?.data ?? [], flags.domain).id;
14065
+ }
14066
+ if (!domainId) throw new Errors.CLIError("Could not resolve a domain id.", { exit: 1 });
14067
+ let response;
14068
+ try {
14069
+ response = await fetch(zoneFileUrl(requestConfig.resolvedApiBaseUrl1, domainId, flags["outbound-only"]), { headers: {
14070
+ ...requestConfig.headers ?? {},
14071
+ ...auth.apiKey ? { authorization: `Bearer ${auth.apiKey}` } : {}
14072
+ } });
14073
+ } catch (error) {
14074
+ writeErrorWithHints(error);
14075
+ process.exitCode = 1;
14076
+ return;
14077
+ }
14078
+ if (!response.ok) {
14079
+ const errorPayload = extractErrorPayload(await responseErrorPayload(response));
14080
+ writeErrorWithHints(errorPayload);
14081
+ surfaceUnauthorizedHint({
14082
+ auth,
14083
+ baseUrlOverridden,
14084
+ configDir: this.config.configDir,
14085
+ payload: errorPayload
14086
+ });
14087
+ process.exitCode = 1;
14088
+ return;
14089
+ }
14090
+ const zoneFile = await response.text();
14091
+ const output = flags.output;
14092
+ if (output) {
14093
+ writeFileSync(output, zoneFile, "utf8");
14094
+ const filename = contentDispositionFilename(response.headers.get("content-disposition"));
14095
+ process.stderr.write(`Wrote ${filename ?? "zone file"} to ${output}\n`);
14096
+ return;
14097
+ }
14098
+ process.stdout.write(zoneFile);
14099
+ });
14100
+ }
14101
+ };
14102
+ //#endregion
13382
14103
  //#region src/oclif/commands/emails-latest.ts
13383
14104
  const DEFAULT_LIMIT = 10;
13384
14105
  const MAX_LIMIT = 100;
@@ -13489,7 +14210,7 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
13489
14210
  //#endregion
13490
14211
  //#region src/oclif/commands/emails-wait.ts
13491
14212
  const DEFAULT_WAIT_TIMEOUT_SECONDS$1 = 300;
13492
- function cliError$4(message) {
14213
+ function cliError$5(message) {
13493
14214
  return new Errors.CLIError(message, { exit: 1 });
13494
14215
  }
13495
14216
  var EmailsWaitCommand = class EmailsWaitCommand extends Command {
@@ -13564,7 +14285,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
13564
14285
  try {
13565
14286
  since = sinceFromFlags(flags);
13566
14287
  } catch (error) {
13567
- throw cliError$4(error instanceof Error ? error.message : String(error));
14288
+ throw cliError$5(error instanceof Error ? error.message : String(error));
13568
14289
  }
13569
14290
  const filters = filtersFromFlags(flags);
13570
14291
  const deadline = flags.timeout === 0 ? null : Date.now() + flags.timeout * 1e3;
@@ -13615,7 +14336,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
13615
14336
  };
13616
14337
  //#endregion
13617
14338
  //#region src/oclif/commands/emails-watch.ts
13618
- function cliError$3(message) {
14339
+ function cliError$4(message) {
13619
14340
  return new Errors.CLIError(message, { exit: 1 });
13620
14341
  }
13621
14342
  var EmailsWatchCommand = class EmailsWatchCommand extends Command {
@@ -13686,7 +14407,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
13686
14407
  try {
13687
14408
  since = sinceFromFlags(flags);
13688
14409
  } catch (error) {
13689
- throw cliError$3(error instanceof Error ? error.message : String(error));
14410
+ throw cliError$4(error instanceof Error ? error.message : String(error));
13690
14411
  }
13691
14412
  const filters = filtersFromFlags(flags);
13692
14413
  const deadline = flags.seconds ? Date.now() + flags.seconds * 1e3 : null;
@@ -14731,7 +15452,7 @@ The deploy step calls \`primitive functions deploy\` (provided by the
14731
15452
  \`npm install -g @primitivedotdev/cli\` or run via
14732
15453
  \`npx @primitivedotdev/cli@latest <command>\`). It requires
14733
15454
  \`PRIMITIVE_API_KEY\` to be set in your shell (or pass \`--api-key\`).
14734
- Run \`primitive login\` once to save a key in your CLI config if you
15455
+ Run \`primitive signin\` once to save a key in your CLI config if you
14735
15456
  prefer that to an env var.
14736
15457
  `;
14737
15458
  }
@@ -15790,7 +16511,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
15790
16511
  //#endregion
15791
16512
  //#region src/oclif/commands/login.ts
15792
16513
  const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
15793
- function cliError$2(message) {
16514
+ function cliError$3(message) {
15794
16515
  return new Errors.CLIError(message, { exit: 1 });
15795
16516
  }
15796
16517
  function sleep(ms) {
@@ -15866,7 +16587,7 @@ async function checkExistingLogin(params) {
15866
16587
  message: code === API_ERROR_CODES.unauthorized ? "Saved Primitive CLI OAuth credentials were rejected by an API URL different from the one they were saved with. Run `primitive logout` to remove them, or switch back to the original environment before logging in again." : "A saved Primitive CLI OAuth session exists, but the CLI could not verify whether it is still valid. Run `primitive logout` before logging in again."
15867
16588
  };
15868
16589
  }
15869
- var LoginCommand = class LoginCommand extends Command {
16590
+ var LoginCommand = class extends Command {
15870
16591
  static description = "Log in by opening Primitive in your browser and saving an org-scoped OAuth session locally.";
15871
16592
  static summary = "Log in with browser approval";
15872
16593
  static examples = [
@@ -15884,24 +16605,28 @@ var LoginCommand = class LoginCommand extends Command {
15884
16605
  "no-browser": Flags.boolean({ description: "Do not attempt to open the browser automatically" }),
15885
16606
  force: Flags.boolean({
15886
16607
  char: "f",
15887
- description: "Replace saved credentials without first verifying the existing login"
16608
+ description: "Replace saved credentials without first verifying the existing session"
15888
16609
  })
15889
16610
  };
15890
16611
  async run() {
15891
- const { flags } = await this.parse(LoginCommand);
16612
+ const commandClass = this.constructor;
16613
+ const { flags } = await this.parse(commandClass);
15892
16614
  let releaseCredentialsLock;
15893
16615
  try {
15894
16616
  releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
15895
16617
  } catch (error) {
15896
- throw cliError$2(error instanceof Error ? error.message : String(error));
16618
+ throw cliError$3(error instanceof Error ? error.message : String(error));
15897
16619
  }
15898
16620
  try {
15899
- await this.runWithCredentialLock(flags);
16621
+ await this.runWithCredentialLock(flags, this.retryCommand());
15900
16622
  } finally {
15901
16623
  releaseCredentialsLock();
15902
16624
  }
15903
16625
  }
15904
- async runWithCredentialLock(flags) {
16626
+ retryCommand() {
16627
+ return "login";
16628
+ }
16629
+ async runWithCredentialLock(flags, retryCommand) {
15905
16630
  const { apiClient, requestConfig } = createCliApiClient({
15906
16631
  apiBaseUrl1: flags["api-base-url-1"],
15907
16632
  configDir: this.config.configDir
@@ -15927,8 +16652,8 @@ var LoginCommand = class LoginCommand extends Command {
15927
16652
  if (existingStatus.status === "removed_stale") process.stderr.write("Continuing with a new Primitive CLI login...\n");
15928
16653
  else if (existingStatus.status === "blocked") {
15929
16654
  writeErrorWithHints(existingStatus.payload);
15930
- throw cliError$2(existingStatus.message);
15931
- } else throw cliError$2(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before logging in again.`);
16655
+ throw cliError$3(existingStatus.message);
16656
+ } else throw cliError$3(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before logging in again.`);
15932
16657
  }
15933
16658
  const started = await startCliLogin({
15934
16659
  body: { device_name: flags["device-name"] ?? hostname() },
@@ -15937,11 +16662,11 @@ var LoginCommand = class LoginCommand extends Command {
15937
16662
  });
15938
16663
  if (started.error) {
15939
16664
  writeErrorWithHints(extractErrorPayload(started.error));
15940
- throw cliError$2("Could not start Primitive CLI login.");
16665
+ throw cliError$3("Could not start Primitive CLI login.");
15941
16666
  }
15942
16667
  const start = unwrapData$2(started.data);
15943
- if (!start) throw cliError$2("Primitive API returned an empty CLI login response.");
15944
- process.stderr.write(`Your login code is: ${start.user_code}\n`);
16668
+ if (!start) throw cliError$3("Primitive API returned an empty CLI login response.");
16669
+ process.stderr.write(`Your sign-in code is: ${start.user_code}\n`);
15945
16670
  if (!flags["no-browser"]) {
15946
16671
  openBrowser(start.verification_uri_complete);
15947
16672
  process.stderr.write("Opening Primitive in your browser...\n");
@@ -15961,7 +16686,7 @@ var LoginCommand = class LoginCommand extends Command {
15961
16686
  });
15962
16687
  if (polled.data) {
15963
16688
  const login = unwrapData$2(polled.data);
15964
- if (!login) throw cliError$2("Primitive API returned an empty CLI poll response.");
16689
+ if (!login) throw cliError$3("Primitive API returned an empty CLI poll response.");
15965
16690
  saveCliCredentials(this.config.configDir, {
15966
16691
  access_token: login.access_token,
15967
16692
  api_base_url_1: apiBaseUrl1,
@@ -15991,25 +16716,25 @@ var LoginCommand = class LoginCommand extends Command {
15991
16716
  nextPollDelay = interval;
15992
16717
  continue;
15993
16718
  }
15994
- if (code === API_ERROR_CODES.accessDenied) throw cliError$2("Primitive CLI login was denied in the browser.");
15995
- if (code === API_ERROR_CODES.expiredToken) throw cliError$2("Primitive CLI login expired. Run `primitive login` again.");
15996
- if (code === API_ERROR_CODES.invalidDeviceCode) throw cliError$2("Primitive CLI login device code is invalid. Run `primitive login` again.");
16719
+ if (code === API_ERROR_CODES.accessDenied) throw cliError$3("Primitive CLI login was denied in the browser.");
16720
+ if (code === API_ERROR_CODES.expiredToken) throw cliError$3(`Primitive CLI login expired. Run \`primitive ${retryCommand}\` again.`);
16721
+ if (code === API_ERROR_CODES.invalidDeviceCode) throw cliError$3(`Primitive CLI login device code is invalid. Run \`primitive ${retryCommand}\` again.`);
15997
16722
  writeErrorWithHints(payload);
15998
- throw cliError$2("Primitive CLI login failed while polling for approval.");
16723
+ throw cliError$3("Primitive CLI login failed while polling for approval.");
15999
16724
  }
16000
- throw cliError$2("Primitive CLI login expired. Run `primitive login` again.");
16725
+ throw cliError$3(`Primitive CLI login expired. Run \`primitive ${retryCommand}\` again.`);
16001
16726
  }
16002
16727
  };
16003
16728
  //#endregion
16004
16729
  //#region src/oclif/commands/logout.ts
16005
- function cliError$1(message) {
16730
+ function cliError$2(message) {
16006
16731
  return new Errors.CLIError(message, { exit: 1 });
16007
16732
  }
16008
16733
  function unwrapData$1(value) {
16009
16734
  return value?.data ?? null;
16010
16735
  }
16011
16736
  function isSavedOAuthSessionExpiredError(error) {
16012
- return error instanceof Error && error.message === "Saved Primitive CLI OAuth session expired or was revoked. Run `primitive login` to authenticate again.";
16737
+ return error instanceof Error && error.message === "Saved Primitive CLI OAuth session expired or was revoked. Run `primitive signin` to authenticate again.";
16013
16738
  }
16014
16739
  async function runLogoutWithCredentialLock(params) {
16015
16740
  const deps = {
@@ -16027,7 +16752,7 @@ async function runLogoutWithCredentialLock(params) {
16027
16752
  process.exitCode = 1;
16028
16753
  return;
16029
16754
  }
16030
- if (!credentials) throw cliError$1("Not logged in. Run `primitive login` to create saved CLI credentials.");
16755
+ if (!credentials) throw cliError$2("Not logged in. Run `primitive signin` to create saved CLI credentials.");
16031
16756
  let authenticated;
16032
16757
  try {
16033
16758
  authenticated = await deps.createAuthenticatedCliApiClient({
@@ -16059,7 +16784,7 @@ async function runLogoutWithCredentialLock(params) {
16059
16784
  return;
16060
16785
  }
16061
16786
  writeErrorWithHints(payload);
16062
- throw cliError$1("Could not revoke the saved Primitive CLI OAuth grant.");
16787
+ throw cliError$2("Could not revoke the saved Primitive CLI OAuth grant.");
16063
16788
  }
16064
16789
  const logout = unwrapData$1(result.data);
16065
16790
  deleteCliCredentials(params.configDir);
@@ -16081,7 +16806,7 @@ var LogoutCommand = class LogoutCommand extends Command {
16081
16806
  try {
16082
16807
  releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
16083
16808
  } catch (error) {
16084
- throw cliError$1(error instanceof Error ? error.message : String(error));
16809
+ throw cliError$2(error instanceof Error ? error.message : String(error));
16085
16810
  }
16086
16811
  try {
16087
16812
  await runLogoutWithCredentialLock({
@@ -16287,12 +17012,46 @@ var ReplyCommand = class ReplyCommand extends Command {
16287
17012
  }
16288
17013
  };
16289
17014
  //#endregion
17015
+ //#region src/oclif/attachments.ts
17016
+ function readAttachmentBytes(path, readFile) {
17017
+ try {
17018
+ return Buffer.from(readFile(path));
17019
+ } catch (error) {
17020
+ const detail = error instanceof Error ? error.message : String(error);
17021
+ throw new Errors.CLIError(`Could not read --attachment ${path}: ${detail}`, { exit: 1 });
17022
+ }
17023
+ }
17024
+ function hasControlCharacter(value) {
17025
+ return Array.from(value).some((character) => {
17026
+ const code = character.charCodeAt(0);
17027
+ return code <= 31 || code >= 127 && code <= 159;
17028
+ });
17029
+ }
17030
+ function validateAttachmentFilename(path, filename) {
17031
+ if (!filename) throw new Errors.CLIError(`Could not derive an attachment filename from ${path}. Pass a file path.`, { exit: 1 });
17032
+ if (hasControlCharacter(filename)) throw new Errors.CLIError(`Attachment filename ${filename} contains control characters.`, { exit: 1 });
17033
+ }
17034
+ function readAttachmentFiles(paths, readFile = readFileSync) {
17035
+ if (!paths || paths.length === 0) return void 0;
17036
+ return paths.map((path) => {
17037
+ const filename = basename(path);
17038
+ validateAttachmentFilename(path, filename);
17039
+ const bytes = readAttachmentBytes(path, readFile);
17040
+ if (bytes.length === 0) throw new Errors.CLIError(`Attachment file ${path} is empty. Attachments must contain at least one byte.`, { exit: 1 });
17041
+ return {
17042
+ content_base64: bytes.toString("base64"),
17043
+ filename
17044
+ };
17045
+ });
17046
+ }
17047
+ //#endregion
16290
17048
  //#region src/oclif/commands/send.ts
16291
17049
  var SendCommand = class SendCommand extends Command {
16292
17050
  static description = `Send an outbound email. Agent-grade shortcut for \`sending send\` with sensible defaults.
16293
17051
 
16294
17052
  --from defaults to agent@<your-first-verified-outbound-domain> when omitted.
16295
17053
  --subject defaults to the first line of the body when omitted.
17054
+ --attachment attaches a file; repeat it to attach multiple files.
16296
17055
 
16297
17056
  For the full flag set (custom message-id threading on the wire,
16298
17057
  references arrays, etc.), use \`primitive sending send\`.`;
@@ -16300,6 +17059,7 @@ var SendCommand = class SendCommand extends Command {
16300
17059
  static examples = [
16301
17060
  "<%= config.bin %> send --to alice@example.com --body 'Hi Alice!'",
16302
17061
  "<%= config.bin %> send --to alice@example.com --body-file ./message.txt",
17062
+ "<%= config.bin %> send --to alice@example.com --body 'See attached.' --attachment ./report.pdf",
16303
17063
  "<%= config.bin %> send --to alice@example.com --from support@yourcompany.com --subject 'Quick question' --body 'Are you free Thursday?'",
16304
17064
  "<%= config.bin %> send --to alice@example.com --html '<p>Hello!</p>'",
16305
17065
  "<%= config.bin %> send --to alice@example.com --body 'Confirmed' --wait",
@@ -16327,11 +17087,15 @@ var SendCommand = class SendCommand extends Command {
16327
17087
  from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
16328
17088
  subject: Flags.string({ description: "Subject line. Defaults to the first line of --body / --html when omitted." }),
16329
17089
  body: Flags.string({ description: "Plain-text message body. Either --body or --html (or both) is required." }),
16330
- "body-file": Flags.string({ description: "Read the plain-text message body from a UTF-8 file. Mutually exclusive with --body and --body-stdin." }),
17090
+ "body-file": Flags.string({ description: "Read the plain-text message body from a UTF-8 file. This does not attach the file; use --attachment for file attachments. Mutually exclusive with --body and --body-stdin." }),
16331
17091
  "body-stdin": Flags.boolean({ description: "Read the plain-text message body from stdin. Mutually exclusive with --body and --body-file. Stdin can only be consumed once." }),
16332
17092
  html: Flags.string({ description: "HTML message body. Either --body or --html (or both) is required." }),
16333
17093
  "html-file": Flags.string({ description: "Read the HTML message body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
16334
17094
  "html-stdin": Flags.boolean({ description: "Read the HTML message body from stdin. Mutually exclusive with --html and --html-file. Stdin can only be consumed once." }),
17095
+ attachment: Flags.string({
17096
+ description: "Attach a file to the email. Repeatable. Sends file bytes as a MIME attachment; use --body-file only for message body text.",
17097
+ multiple: true
17098
+ }),
16335
17099
  "in-reply-to": Flags.string({ description: "Message-Id of the parent email when threading a reply on the wire. For replying to an inbound message you received, prefer `primitive reply --id <inbound-id>`." }),
16336
17100
  wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the message for delivery." }),
16337
17101
  "wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
@@ -16348,6 +17112,7 @@ var SendCommand = class SendCommand extends Command {
16348
17112
  htmlStdin: flags["html-stdin"]
16349
17113
  });
16350
17114
  if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
17115
+ const attachments = readAttachmentFiles(flags.attachment);
16351
17116
  await runWithTiming(flags.time, async () => {
16352
17117
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
16353
17118
  apiKey: flags["api-key"],
@@ -16369,6 +17134,7 @@ var SendCommand = class SendCommand extends Command {
16369
17134
  subject,
16370
17135
  ...bodies.body !== void 0 ? { body_text: bodies.body } : {},
16371
17136
  ...bodies.html !== void 0 ? { body_html: bodies.html } : {},
17137
+ ...attachments !== void 0 ? { attachments } : {},
16372
17138
  ...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {},
16373
17139
  ...flags.wait !== void 0 ? { wait: flags.wait } : {},
16374
17140
  ...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
@@ -16401,7 +17167,14 @@ const EXPIRED_TOKEN = "expired_token";
16401
17167
  const INVALID_SIGNUP_TOKEN = "invalid_signup_token";
16402
17168
  const SLOW_DOWN = "slow_down";
16403
17169
  const PENDING_SIGNUP_FILE = "signup.json";
16404
- function cliError(message) {
17170
+ const DEFAULT_SIGNUP_COMMAND_COPY = {
17171
+ actionNoun: "signup",
17172
+ actionGerund: "creating a new account",
17173
+ confirmCommand: (email) => `signup confirm ${email} <code>`,
17174
+ resendCommand: (email) => `signup resend ${email}`,
17175
+ startCommand: (email) => `signup ${email}`
17176
+ };
17177
+ function cliError$1(message) {
16405
17178
  return new Errors.CLIError(message, { exit: 1 });
16406
17179
  }
16407
17180
  function unwrapData(value) {
@@ -16490,9 +17263,10 @@ function loadPendingAgentSignup(configDir, apiBaseUrl1) {
16490
17263
  };
16491
17264
  }
16492
17265
  function requirePendingSignupForEmail(params) {
17266
+ const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
16493
17267
  const pending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl1);
16494
- if (!pending) throw cliError(`No pending signup for ${params.email}. Run \`primitive signup ${params.email}\` first.`);
16495
- if (normalizeEmail(pending.email) !== normalizeEmail(params.email)) throw cliError(`Pending signup is for ${pending.email}, not ${params.email}. Run \`primitive signup ${params.email} --force\` to replace it.`);
17268
+ if (!pending) throw cliError$1(`No pending ${copy.actionNoun} for ${params.email}. Run \`primitive ${copy.startCommand(params.email)}\` first.`);
17269
+ if (normalizeEmail(pending.email) !== normalizeEmail(params.email)) throw cliError$1(`Pending ${copy.actionNoun} is for ${pending.email}, not ${params.email}. Run \`primitive ${copy.startCommand(params.email)} --force\` to replace it.`);
16496
17270
  return pending;
16497
17271
  }
16498
17272
  function retryAfterSeconds(result) {
@@ -16529,13 +17303,14 @@ async function promptRequired(question) {
16529
17303
  }
16530
17304
  }
16531
17305
  async function confirmTerms() {
16532
- process$1.stderr.write("By creating an account, you agree to Primitive's Terms of Service and Privacy Policy:\n");
17306
+ process$1.stderr.write("By continuing, you agree to Primitive's Terms of Service and Privacy Policy:\n");
16533
17307
  process$1.stderr.write(" https://primitive.dev/terms\n");
16534
17308
  process$1.stderr.write(" https://primitive.dev/privacy\n");
16535
17309
  const answer = (await promptRequired("Type 'yes' to continue: ")).toLowerCase();
16536
- if (answer !== "yes" && answer !== "y") throw cliError("You must accept the terms to create an account.");
17310
+ if (answer !== "yes" && answer !== "y") throw cliError$1("You must accept the terms to create an account.");
16537
17311
  }
16538
17312
  async function checkExistingCredentials(params) {
17313
+ const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
16539
17314
  const checkExistingLoginFn = params.deps.checkExistingLogin ?? checkExistingLogin;
16540
17315
  let existing;
16541
17316
  try {
@@ -16547,7 +17322,7 @@ async function checkExistingCredentials(params) {
16547
17322
  existing = null;
16548
17323
  }
16549
17324
  if (existing && params.flags.force) {
16550
- process$1.stderr.write("Replacing saved Primitive CLI credentials after signup because --force was set.\n");
17325
+ process$1.stderr.write(`Replacing saved Primitive CLI credentials after ${copy.actionNoun} because --force was set.\n`);
16551
17326
  return;
16552
17327
  }
16553
17328
  if (!existing) return;
@@ -16563,9 +17338,9 @@ async function checkExistingCredentials(params) {
16563
17338
  }
16564
17339
  if (existingStatus.status === "blocked") {
16565
17340
  writeErrorWithHints(existingStatus.payload);
16566
- throw cliError(existingStatus.message);
17341
+ throw cliError$1(existingStatus.message);
16567
17342
  }
16568
- throw cliError(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before creating a new account.`);
17343
+ throw cliError$1(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before ${copy.actionGerund}.`);
16569
17344
  }
16570
17345
  function saveSignupCredentials(params) {
16571
17346
  saveCliCredentials(params.configDir, {
@@ -16582,23 +17357,24 @@ function saveSignupCredentials(params) {
16582
17357
  token_type: params.signup.token_type
16583
17358
  });
16584
17359
  }
16585
- function writeStartInstructions(start) {
17360
+ function writeStartInstructions(start, copy = DEFAULT_SIGNUP_COMMAND_COPY) {
16586
17361
  process$1.stderr.write(`Sent a ${start.verification_code_length}-digit verification code to ${start.email}.\n`);
16587
17362
  process$1.stderr.write(`The code expires in ${formatSignupSeconds(start.expires_in)}.\n`);
16588
- process$1.stderr.write(`Run \`primitive signup confirm ${start.email} <code>\` to finish.\n`);
17363
+ process$1.stderr.write(`Run \`primitive ${copy.confirmCommand(start.email)}\` to finish.\n`);
16589
17364
  }
16590
17365
  async function startSignup(params) {
17366
+ const copy = params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY;
16591
17367
  const existingPending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl1);
16592
17368
  if (existingPending && !params.flags.force) {
16593
17369
  if (normalizeEmail(existingPending.email) === normalizeEmail(params.email)) {
16594
- process$1.stderr.write(`Continuing pending Primitive signup for ${existingPending.email}.\n`);
16595
- process$1.stderr.write(`Run \`primitive signup confirm ${existingPending.email} <code>\` to finish, or \`primitive signup resend ${existingPending.email}\` to send a new code.\n`);
17370
+ process$1.stderr.write(`Continuing pending Primitive ${copy.actionNoun} for ${existingPending.email}.\n`);
17371
+ process$1.stderr.write(`Run \`primitive ${copy.confirmCommand(existingPending.email)}\` to finish, or \`primitive ${copy.resendCommand(existingPending.email)}\` to send a new code.\n`);
16596
17372
  return {
16597
17373
  pending: existingPending,
16598
17374
  started: false
16599
17375
  };
16600
17376
  }
16601
- throw cliError(`Pending signup is for ${existingPending.email}. Run \`primitive signup ${params.email} --force\` to replace it.`);
17377
+ throw cliError$1(`Pending ${copy.actionNoun} is for ${existingPending.email}. Run \`primitive ${copy.startCommand(params.email)} --force\` to replace it.`);
16602
17378
  }
16603
17379
  if (params.flags.force) deletePendingAgentSignup(params.configDir);
16604
17380
  const promptRequiredFn = params.deps.promptRequired ?? promptRequired;
@@ -16618,10 +17394,10 @@ async function startSignup(params) {
16618
17394
  });
16619
17395
  if (started.error) {
16620
17396
  writeErrorWithHints(extractErrorPayload(started.error));
16621
- throw cliError("Could not start Primitive agent signup.");
17397
+ throw cliError$1("Could not start Primitive agent signup.");
16622
17398
  }
16623
17399
  const startResult = unwrapData(started.data);
16624
- if (!startResult) throw cliError("Primitive API returned an empty agent signup response.");
17400
+ if (!startResult) throw cliError$1("Primitive API returned an empty agent signup response.");
16625
17401
  return {
16626
17402
  pending: savePendingAgentSignup(params.configDir, startResult, params.apiBaseUrl1),
16627
17403
  started: true
@@ -16659,7 +17435,7 @@ async function resendVerificationCode(params) {
16659
17435
  }
16660
17436
  if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(params.configDir);
16661
17437
  writeErrorWithHints(payload);
16662
- throw cliError("Could not resend Primitive agent signup verification email.");
17438
+ throw cliError$1("Could not resend Primitive agent signup verification email.");
16663
17439
  }
16664
17440
  async function runSignupStartWithCredentialLock(params) {
16665
17441
  const { configDir, flags } = params;
@@ -16669,6 +17445,7 @@ async function runSignupStartWithCredentialLock(params) {
16669
17445
  await checkExistingCredentials({
16670
17446
  apiBaseUrl1: flags["api-base-url-1"],
16671
17447
  configDir,
17448
+ copy: params.copy,
16672
17449
  deps,
16673
17450
  flags
16674
17451
  });
@@ -16680,11 +17457,12 @@ async function runSignupStartWithCredentialLock(params) {
16680
17457
  apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
16681
17458
  apiClient,
16682
17459
  configDir,
17460
+ copy: params.copy,
16683
17461
  deps,
16684
17462
  email,
16685
17463
  flags
16686
17464
  });
16687
- if (start.started) writeStartInstructions(start.pending);
17465
+ if (start.started) writeStartInstructions(start.pending, params.copy);
16688
17466
  }
16689
17467
  async function runSignupConfirmWithCredentialLock(params) {
16690
17468
  const { configDir, flags } = params;
@@ -16692,6 +17470,7 @@ async function runSignupConfirmWithCredentialLock(params) {
16692
17470
  if (!params.skipExistingCredentialCheck) await checkExistingCredentials({
16693
17471
  apiBaseUrl1: flags["api-base-url-1"],
16694
17472
  configDir,
17473
+ copy: params.copy,
16695
17474
  deps,
16696
17475
  flags
16697
17476
  });
@@ -16702,6 +17481,7 @@ async function runSignupConfirmWithCredentialLock(params) {
16702
17481
  const apiBaseUrl1 = requestConfig.resolvedApiBaseUrl1;
16703
17482
  const pending = requirePendingSignupForEmail({
16704
17483
  apiBaseUrl1,
17484
+ copy: params.copy,
16705
17485
  configDir,
16706
17486
  email: params.email
16707
17487
  });
@@ -16716,7 +17496,7 @@ async function runSignupConfirmWithCredentialLock(params) {
16716
17496
  });
16717
17497
  if (verified.data) {
16718
17498
  const signup = unwrapData(verified.data);
16719
- if (!signup) throw cliError("Primitive API returned an empty agent signup verification response.");
17499
+ if (!signup) throw cliError$1("Primitive API returned an empty agent signup verification response.");
16720
17500
  saveSignupCredentials({
16721
17501
  apiBaseUrl1,
16722
17502
  configDir,
@@ -16730,10 +17510,10 @@ async function runSignupConfirmWithCredentialLock(params) {
16730
17510
  }
16731
17511
  const payload = extractErrorPayload(verified.error);
16732
17512
  const code = extractErrorCode(payload);
16733
- if (code === INVALID_VERIFICATION_CODE) throw cliError("Invalid verification code. Try again or run signup resend.");
17513
+ if (code === INVALID_VERIFICATION_CODE) throw cliError$1(`Invalid verification code. Try again or run ${(params.copy ?? DEFAULT_SIGNUP_COMMAND_COPY).resendCommand(params.email)}.`);
16734
17514
  if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(configDir);
16735
17515
  writeErrorWithHints(payload);
16736
- throw cliError("Primitive agent signup failed while verifying the account.");
17516
+ throw cliError$1("Primitive agent signup failed while verifying the account.");
16737
17517
  }
16738
17518
  async function runSignupResendWithCredentialLock(params) {
16739
17519
  const deps = params.deps ?? {};
@@ -16743,6 +17523,7 @@ async function runSignupResendWithCredentialLock(params) {
16743
17523
  });
16744
17524
  const pending = requirePendingSignupForEmail({
16745
17525
  apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
17526
+ copy: params.copy,
16746
17527
  configDir: params.configDir,
16747
17528
  email: params.email
16748
17529
  });
@@ -16857,7 +17638,7 @@ var SignupCommand = class SignupCommand extends Command {
16857
17638
  try {
16858
17639
  releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
16859
17640
  } catch (error) {
16860
- throw cliError(error instanceof Error ? error.message : String(error));
17641
+ throw cliError$1(error instanceof Error ? error.message : String(error));
16861
17642
  }
16862
17643
  try {
16863
17644
  await runSignupStartWithCredentialLock({
@@ -16902,7 +17683,7 @@ var SignupConfirmCommand = class SignupConfirmCommand extends Command {
16902
17683
  try {
16903
17684
  releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
16904
17685
  } catch (error) {
16905
- throw cliError(error instanceof Error ? error.message : String(error));
17686
+ throw cliError$1(error instanceof Error ? error.message : String(error));
16906
17687
  }
16907
17688
  try {
16908
17689
  await runSignupConfirmWithCredentialLock({
@@ -16935,7 +17716,7 @@ var SignupResendCommand = class SignupResendCommand extends Command {
16935
17716
  try {
16936
17717
  releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
16937
17718
  } catch (error) {
16938
- throw cliError(error instanceof Error ? error.message : String(error));
17719
+ throw cliError$1(error instanceof Error ? error.message : String(error));
16939
17720
  }
16940
17721
  try {
16941
17722
  await runSignupResendWithCredentialLock({
@@ -16959,7 +17740,7 @@ var SignupInteractiveCommand = class SignupInteractiveCommand extends Command {
16959
17740
  try {
16960
17741
  releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
16961
17742
  } catch (error) {
16962
- throw cliError(error instanceof Error ? error.message : String(error));
17743
+ throw cliError$1(error instanceof Error ? error.message : String(error));
16963
17744
  }
16964
17745
  try {
16965
17746
  await runSignupInteractiveWithCredentialLock({
@@ -16972,6 +17753,167 @@ var SignupInteractiveCommand = class SignupInteractiveCommand extends Command {
16972
17753
  }
16973
17754
  };
16974
17755
  //#endregion
17756
+ //#region src/oclif/commands/signin.ts
17757
+ function cliError(message) {
17758
+ return new Errors.CLIError(message, { exit: 1 });
17759
+ }
17760
+ const SIGNIN_OTP_COPY = {
17761
+ actionNoun: "sign-in",
17762
+ actionGerund: "signing in",
17763
+ confirmCommand: (email) => `signin otp confirm ${email} <code>`,
17764
+ resendCommand: (email) => `signin otp resend ${email}`,
17765
+ startCommand: (email) => `signin otp ${email}`
17766
+ };
17767
+ function acquireCredentialsLock(configDir) {
17768
+ try {
17769
+ return acquireCliCredentialsLock(configDir);
17770
+ } catch (error) {
17771
+ throw cliError(error instanceof Error ? error.message : String(error));
17772
+ }
17773
+ }
17774
+ function commonOtpStartFlags() {
17775
+ return {
17776
+ "accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy" }),
17777
+ "api-base-url-1": Flags.string({
17778
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
17779
+ env: "PRIMITIVE_API_BASE_URL_1",
17780
+ hidden: true
17781
+ }),
17782
+ "device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
17783
+ force: Flags.boolean({
17784
+ char: "f",
17785
+ description: "Replace saved credentials or pending sign-in state when needed"
17786
+ }),
17787
+ "signup-code": Flags.string({
17788
+ description: "Signup code required to start OTP sign-in",
17789
+ env: "PRIMITIVE_SIGNUP_CODE"
17790
+ })
17791
+ };
17792
+ }
17793
+ var SigninCommand = class extends LoginCommand {
17794
+ static description = `Sign in to an existing Primitive account with browser approval and save an org-scoped OAuth session locally.
17795
+
17796
+ This is the canonical sign-in command. It defaults to the same browser approval flow as \`primitive signin browser\`. For email-code sign-in, use \`primitive signin otp <email> --signup-code <code>\`, then \`primitive signin otp confirm <email> <code>\`. For new account creation, use \`primitive signup <email>\`.`;
17797
+ static summary = "Sign in to an existing account";
17798
+ static examples = [
17799
+ "<%= config.bin %> signin",
17800
+ "<%= config.bin %> signin browser",
17801
+ "<%= config.bin %> signin --no-browser",
17802
+ "<%= config.bin %> signin otp user@example.com --signup-code invite-code --accept-terms",
17803
+ "<%= config.bin %> signin otp confirm user@example.com 123456"
17804
+ ];
17805
+ retryCommand() {
17806
+ return "signin";
17807
+ }
17808
+ };
17809
+ var SigninBrowserCommand = class extends LoginCommand {
17810
+ static description = "Sign in to an existing Primitive account by opening Primitive in your browser and saving an org-scoped OAuth session locally.";
17811
+ static summary = "Sign in with browser approval";
17812
+ static examples = [
17813
+ "<%= config.bin %> signin browser",
17814
+ "<%= config.bin %> signin browser --device-name work-laptop",
17815
+ "<%= config.bin %> signin browser --no-browser",
17816
+ "<%= config.bin %> signin browser --force"
17817
+ ];
17818
+ retryCommand() {
17819
+ return "signin browser";
17820
+ }
17821
+ };
17822
+ var SigninOtpCommand = class SigninOtpCommand extends Command {
17823
+ static args = { email: Args.string({
17824
+ description: "Email address to sign in with",
17825
+ required: false
17826
+ }) };
17827
+ static description = "Start email-code sign-in using Primitive's signup/auth OTP flow, send a verification code, and save the pending token locally. Requires a signup code.";
17828
+ static summary = "Start OTP sign-in";
17829
+ static examples = ["<%= config.bin %> signin otp user@example.com --signup-code invite-code --accept-terms", "<%= config.bin %> signin otp confirm user@example.com 123456"];
17830
+ static flags = commonOtpStartFlags();
17831
+ async run() {
17832
+ const { args, flags } = await this.parse(SigninOtpCommand);
17833
+ const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
17834
+ try {
17835
+ await runSignupStartWithCredentialLock({
17836
+ configDir: this.config.configDir,
17837
+ copy: SIGNIN_OTP_COPY,
17838
+ email: args.email,
17839
+ flags
17840
+ });
17841
+ } finally {
17842
+ releaseCredentialsLock();
17843
+ }
17844
+ }
17845
+ };
17846
+ var SigninOtpConfirmCommand = class SigninOtpConfirmCommand extends Command {
17847
+ static args = {
17848
+ email: Args.string({
17849
+ description: "Email address used to start OTP sign-in",
17850
+ required: true
17851
+ }),
17852
+ code: Args.string({
17853
+ description: "Verification code from the sign-in email",
17854
+ required: true
17855
+ })
17856
+ };
17857
+ static description = "Confirm a pending OTP sign-in, create an OAuth session, and save CLI credentials locally.";
17858
+ static summary = "Confirm OTP sign-in";
17859
+ static examples = ["<%= config.bin %> signin otp confirm user@example.com 123456", "<%= config.bin %> signin otp confirm user@example.com 123456 --org-id 00000000-0000-4000-8000-000000000000"];
17860
+ static flags = {
17861
+ "api-base-url-1": Flags.string({
17862
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
17863
+ env: "PRIMITIVE_API_BASE_URL_1",
17864
+ hidden: true
17865
+ }),
17866
+ force: Flags.boolean({
17867
+ char: "f",
17868
+ description: "Replace saved credentials after verification"
17869
+ }),
17870
+ "org-id": Flags.string({ description: "Workspace id to target when the email belongs to multiple workspaces" })
17871
+ };
17872
+ async run() {
17873
+ const { args, flags } = await this.parse(SigninOtpConfirmCommand);
17874
+ const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
17875
+ try {
17876
+ await runSignupConfirmWithCredentialLock({
17877
+ code: args.code,
17878
+ configDir: this.config.configDir,
17879
+ copy: SIGNIN_OTP_COPY,
17880
+ email: args.email,
17881
+ flags
17882
+ });
17883
+ } finally {
17884
+ releaseCredentialsLock();
17885
+ }
17886
+ }
17887
+ };
17888
+ var SigninOtpResendCommand = class SigninOtpResendCommand extends Command {
17889
+ static args = { email: Args.string({
17890
+ description: "Email address used to start OTP sign-in",
17891
+ required: true
17892
+ }) };
17893
+ static description = "Resend the verification code for a pending OTP sign-in.";
17894
+ static summary = "Resend OTP sign-in code";
17895
+ static examples = ["<%= config.bin %> signin otp resend user@example.com"];
17896
+ static flags = { "api-base-url-1": Flags.string({
17897
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
17898
+ env: "PRIMITIVE_API_BASE_URL_1",
17899
+ hidden: true
17900
+ }) };
17901
+ async run() {
17902
+ const { args, flags } = await this.parse(SigninOtpResendCommand);
17903
+ const releaseCredentialsLock = acquireCredentialsLock(this.config.configDir);
17904
+ try {
17905
+ await runSignupResendWithCredentialLock({
17906
+ configDir: this.config.configDir,
17907
+ copy: SIGNIN_OTP_COPY,
17908
+ email: args.email,
17909
+ flags
17910
+ });
17911
+ } finally {
17912
+ releaseCredentialsLock();
17913
+ }
17914
+ }
17915
+ };
17916
+ //#endregion
16975
17917
  //#region src/oclif/commands/whoami.ts
16976
17918
  var WhoamiCommand = class WhoamiCommand extends Command {
16977
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.`;
@@ -17103,36 +18045,88 @@ function renderFishCompletion(binName) {
17103
18045
  //#endregion
17104
18046
  //#region src/oclif/index.ts
17105
18047
  var ListOperationsCommand = class extends Command {
17106
- static description = "List all generated API operations as JSON. Useful for piping to `jq` to discover available commands, their request/response schemas, and per-field descriptions. For inspecting a single operation in detail, prefer `primitive describe <command>`.";
18048
+ static description = "List all generated API operations as JSON. Useful for piping to `jq` to discover available commands, their request/response schemas, and per-field descriptions. For inspecting a single operation in detail, prefer `primitive describe <command-or-operation-name>`.";
17107
18049
  static summary = "List all generated API operations (JSON)";
17108
18050
  async run() {
17109
18051
  this.log(JSON.stringify(operationManifest, null, 2));
17110
18052
  }
17111
18053
  };
18054
+ function operationId(operation) {
18055
+ return `${operation.tagCommand}:${operation.command}`;
18056
+ }
18057
+ function normalizeLookupToken(value) {
18058
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
18059
+ }
18060
+ function unique(values) {
18061
+ return [...new Set(values)];
18062
+ }
18063
+ function operationLookupTokens(operation) {
18064
+ return unique([
18065
+ operationId(operation),
18066
+ operation.command,
18067
+ operation.operationId,
18068
+ operation.sdkName,
18069
+ `${operation.tagCommand}:${operation.operationId}`,
18070
+ `${operation.tagCommand}:${operation.sdkName}`
18071
+ ]);
18072
+ }
18073
+ function levenshteinDistance(left, right) {
18074
+ if (left === right) return 0;
18075
+ if (left.length === 0) return right.length;
18076
+ if (right.length === 0) return left.length;
18077
+ let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
18078
+ for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
18079
+ const current = [leftIndex + 1];
18080
+ for (let rightIndex = 0; rightIndex < right.length; rightIndex += 1) {
18081
+ const substitutionCost = left[leftIndex] === right[rightIndex] ? 0 : 1;
18082
+ current[rightIndex + 1] = Math.min(current[rightIndex] + 1, previous[rightIndex + 1] + 1, previous[rightIndex] + substitutionCost);
18083
+ }
18084
+ previous = current;
18085
+ }
18086
+ return previous[right.length] ?? Number.POSITIVE_INFINITY;
18087
+ }
18088
+ function scoreLookupToken(query, token) {
18089
+ const normalizedQuery = normalizeLookupToken(query);
18090
+ const normalizedToken = normalizeLookupToken(token);
18091
+ if (!normalizedQuery || !normalizedToken) return 0;
18092
+ if (normalizedQuery === normalizedToken) return 100;
18093
+ if (normalizedToken.includes(normalizedQuery)) return Math.max(50, 90 - (normalizedToken.length - normalizedQuery.length));
18094
+ if (normalizedQuery.includes(normalizedToken)) return Math.max(45, 80 - (normalizedQuery.length - normalizedToken.length));
18095
+ const distance = levenshteinDistance(normalizedQuery, normalizedToken);
18096
+ const maxLength = Math.max(normalizedQuery.length, normalizedToken.length);
18097
+ return Math.round((1 - distance / maxLength) * 75);
18098
+ }
18099
+ function scoreOperation(query, operation) {
18100
+ return Math.max(...operationLookupTokens(operation).map((token) => scoreLookupToken(query, token)));
18101
+ }
17112
18102
  function lookupOperation(id) {
17113
18103
  const trimmed = resolveOperationAlias(id.trim());
17114
- const sep = trimmed.indexOf(":");
17115
- const tag = sep === -1 ? "" : trimmed.slice(0, sep);
17116
- const cmd = sep === -1 ? trimmed : trimmed.slice(sep + 1);
17117
- const match = operationManifest.find((op) => op.command === cmd && op.tagCommand === tag) ?? null;
18104
+ const match = operationManifest.find((op) => operationLookupTokens(op).some((token) => token === trimmed || normalizeLookupToken(token) === normalizeLookupToken(trimmed))) ?? null;
17118
18105
  if (match) return {
17119
18106
  match,
17120
18107
  candidates: []
17121
18108
  };
17122
18109
  return {
17123
18110
  match: null,
17124
- candidates: operationManifest.filter((op) => op.command.includes(cmd) || op.tagCommand.includes(tag)).slice(0, 5).map((op) => op.tagCommand ? `${op.tagCommand}:${op.command}` : op.command)
18111
+ candidates: operationManifest.map((op) => ({
18112
+ id: operationId(op),
18113
+ score: scoreOperation(trimmed, op)
18114
+ })).filter(({ score }) => score >= 45).sort((left, right) => right.score - left.score || left.id.localeCompare(right.id)).slice(0, 5).map(({ id }) => id)
17125
18115
  };
17126
18116
  }
17127
18117
  var DescribeCommand = class DescribeCommand extends Command {
17128
18118
  static args = { command: Args.string({
17129
- description: "Command id to describe, e.g. `emails:list` or `emails:get-email`. Run `primitive list-operations | jq -r '.[] | \"\\(.tagCommand):\\(.command)\"'` to enumerate generated operation ids.",
18119
+ description: "Command id, alias, or SDK operation name to describe, e.g. `emails:list`, `emails:get-email`, or `getEmail`. Run `primitive list-operations | jq -r '.[] | \"\\(.tagCommand):\\(.command) \\(.operationId)\"'` to enumerate generated operation ids.",
17130
18120
  required: true
17131
18121
  }) };
17132
18122
  static description = `Print the full operation manifest entry for a single API command, including the path, request schema, response schema, and per-field descriptions sourced from the OpenAPI spec.
17133
18123
 
17134
18124
  The manifest entry's \`responseSchema\` carries the inlined JSON Schema for the operation's 200/201 \`data\` envelope contents (\`$ref\`s resolved). Use it to look up what specific response fields mean. Examples:
17135
18125
 
18126
+ # Domain setup records returned by add/verify
18127
+ primitive describe domains:add
18128
+ primitive describe addDomain
18129
+
17136
18130
  # Which of EmailDetail's sender-shaped fields is canonical?
17137
18131
  primitive describe emails:get | jq '.responseSchema.properties | keys'
17138
18132
  primitive describe emails:get | jq -r '.responseSchema.properties.from_email.description'
@@ -17142,7 +18136,12 @@ var DescribeCommand = class DescribeCommand extends Command {
17142
18136
 
17143
18137
  \`requestSchema\` is the same shape for the request body when one exists. For a single field across many operations at once, use \`primitive list-operations | jq\` instead.`;
17144
18138
  static summary = "Describe a single API operation in detail";
17145
- static examples = ["<%= config.bin %> describe emails:get", "<%= config.bin %> describe sent:get"];
18139
+ static examples = [
18140
+ "<%= config.bin %> describe addDomain",
18141
+ "<%= config.bin %> describe domains:add",
18142
+ "<%= config.bin %> describe emails:get",
18143
+ "<%= config.bin %> describe sent:get"
18144
+ ];
17146
18145
  async run() {
17147
18146
  const { args } = await this.parse(DescribeCommand);
17148
18147
  const { match, candidates } = lookupOperation(args.command);
@@ -17175,9 +18174,6 @@ var CompletionCommand = class CompletionCommand extends Command {
17175
18174
  await this.config.runCommand("autocomplete", [args.shell]);
17176
18175
  }
17177
18176
  };
17178
- function commandId(operation) {
17179
- return `${operation.tagCommand}:${operation.command}`;
17180
- }
17181
18177
  const CANONICAL_OPERATION_ALIASES = {
17182
18178
  "account:show": "account:get-account",
17183
18179
  "account:storage": "account:get-storage-stats",
@@ -17222,14 +18218,15 @@ const CANONICAL_OPERATION_ALIASES = {
17222
18218
  };
17223
18219
  const DESCRIBE_OPERATION_ALIASES = {
17224
18220
  ...CANONICAL_OPERATION_ALIASES,
18221
+ "domains:zone-file": "domains:download-domain-zone-file",
17225
18222
  "functions:logs": "functions:list-function-logs",
17226
18223
  reply: "sending:reply-to-email"
17227
18224
  };
17228
18225
  function resolveOperationAlias(id) {
17229
18226
  return DESCRIBE_OPERATION_ALIASES[id] ?? id;
17230
18227
  }
17231
- const OVERRIDDEN_OPERATION_IDS = new Set(["functions:test-function"]);
17232
- const generatedCommands = Object.fromEntries(operationManifest.filter((operation) => !OVERRIDDEN_OPERATION_IDS.has(commandId(operation))).map((operation) => [commandId(operation), createOperationCommand(operation)]));
18228
+ const OVERRIDDEN_OPERATION_IDS = new Set(["domains:download-domain-zone-file", "functions:test-function"]);
18229
+ const generatedCommands = Object.fromEntries(operationManifest.filter((operation) => !OVERRIDDEN_OPERATION_IDS.has(operationId(operation))).map((operation) => [operationId(operation), createOperationCommand(operation)]));
17233
18230
  const COMMANDS = {
17234
18231
  completion: CompletionCommand,
17235
18232
  "list-operations": ListOperationsCommand,
@@ -17243,6 +18240,11 @@ const COMMANDS = {
17243
18240
  reply: ReplyCommand,
17244
18241
  chat: ChatCommand,
17245
18242
  login: LoginCommand,
18243
+ signin: SigninCommand,
18244
+ "signin:browser": SigninBrowserCommand,
18245
+ "signin:otp": SigninOtpCommand,
18246
+ "signin:otp:confirm": SigninOtpConfirmCommand,
18247
+ "signin:otp:resend": SigninOtpResendCommand,
17246
18248
  signup: SignupCommand,
17247
18249
  "signup:confirm": SignupConfirmCommand,
17248
18250
  "signup:interactive": SignupInteractiveCommand,
@@ -17253,6 +18255,8 @@ const COMMANDS = {
17253
18255
  "emails:latest": EmailsLatestCommand,
17254
18256
  "emails:watch": EmailsWatchCommand,
17255
18257
  "emails:wait": EmailsWaitCommand,
18258
+ "domains:zone-file": DomainsZoneFileCommand,
18259
+ "domains:download-domain-zone-file": DomainsZoneFileCommand,
17256
18260
  "functions:init": FunctionsInitCommand,
17257
18261
  "functions:templates": FunctionsTemplatesCommand,
17258
18262
  "functions:deploy": FunctionsDeployCommand,