@primitivedotdev/cli 0.31.1 → 0.31.2

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.
Files changed (2) hide show
  1. package/dist/oclif/index.js +1619 -508
  2. package/package.json +4 -1
@@ -667,11 +667,13 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
667
667
  replayDelivery: () => replayDelivery,
668
668
  replayEmailWebhooks: () => replayEmailWebhooks,
669
669
  replyToEmail: () => replyToEmail,
670
+ resendAgentSignupVerification: () => resendAgentSignupVerification,
670
671
  resendCliSignupVerification: () => resendCliSignupVerification,
671
672
  rotateWebhookSecret: () => rotateWebhookSecret,
672
673
  searchEmails: () => searchEmails,
673
674
  sendEmail: () => sendEmail,
674
675
  setFunctionSecret: () => setFunctionSecret,
676
+ startAgentSignup: () => startAgentSignup,
675
677
  startCliLogin: () => startCliLogin,
676
678
  startCliSignup: () => startCliSignup,
677
679
  testEndpoint: () => testEndpoint,
@@ -681,6 +683,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
681
683
  updateEndpoint: () => updateEndpoint,
682
684
  updateFilter: () => updateFilter,
683
685
  updateFunction: () => updateFunction,
686
+ verifyAgentSignup: () => verifyAgentSignup,
684
687
  verifyCliSignup: () => verifyCliSignup,
685
688
  verifyDomain: () => verifyDomain
686
689
  });
@@ -704,8 +707,8 @@ const startCliLogin = (options) => (options?.client ?? client).post({
704
707
  * Poll CLI browser login
705
708
  *
706
709
  * Polls a CLI login session until the browser approval either succeeds,
707
- * is denied, expires, or is polled too quickly. The API key is generated
708
- * only after approval and is returned exactly once.
710
+ * is denied, expires, or is polled too quickly. The OAuth token set is
711
+ * created only after approval and is returned exactly once.
709
712
  *
710
713
  */
711
714
  const pollCliLogin = (options) => (options.client ?? client).post({
@@ -749,11 +752,12 @@ const resendCliSignupVerification = (options) => (options.client ?? client).post
749
752
  }
750
753
  });
751
754
  /**
752
- * Verify CLI signup and create API key
755
+ * Verify CLI signup and create OAuth session
753
756
  *
754
757
  * Verifies the email code for a CLI signup session, creates the account,
755
- * redeems the reserved signup code, mints an org-scoped CLI API key, and
756
- * returns the raw key exactly once. This endpoint does not require an API key.
758
+ * redeems the reserved signup code, creates an org-scoped OAuth CLI
759
+ * session, and returns the token set exactly once. This endpoint does not
760
+ * require an API key.
757
761
  *
758
762
  */
759
763
  const verifyCliSignup = (options) => (options.client ?? client).post({
@@ -765,10 +769,61 @@ const verifyCliSignup = (options) => (options.client ?? client).post({
765
769
  }
766
770
  });
767
771
  /**
768
- * Revoke the current CLI API key
772
+ * Start agent account signup
769
773
  *
770
- * Revokes the API key used to authenticate the request. CLI clients use
771
- * this endpoint during `primitive logout` before removing local credentials.
774
+ * Starts an agent-native signup session. The API validates the signup code,
775
+ * creates a pending signup session, sends an email verification code, and
776
+ * returns an opaque signup token used by the resend and verify steps. This
777
+ * endpoint does not require an API key.
778
+ *
779
+ */
780
+ const startAgentSignup = (options) => (options.client ?? client).post({
781
+ url: "/agent/signup/start",
782
+ ...options,
783
+ headers: {
784
+ ...options.body !== void 0 && { "Content-Type": "application/json" },
785
+ ...options.headers
786
+ }
787
+ });
788
+ /**
789
+ * Resend agent signup verification code
790
+ *
791
+ * Sends a new email verification code for a pending agent signup session.
792
+ * This endpoint does not require an API key.
793
+ *
794
+ */
795
+ const resendAgentSignupVerification = (options) => (options.client ?? client).post({
796
+ url: "/agent/signup/resend",
797
+ ...options,
798
+ headers: {
799
+ ...options.body !== void 0 && { "Content-Type": "application/json" },
800
+ ...options.headers
801
+ }
802
+ });
803
+ /**
804
+ * Verify agent signup and create OAuth tokens
805
+ *
806
+ * Verifies the email code for an agent signup session, creates the account
807
+ * when needed, redeems the reserved signup code, mints an org-scoped OAuth
808
+ * session for CLI authentication, and returns the raw tokens exactly once.
809
+ * For existing users, the optional `org_id` selects which accessible
810
+ * workspace should receive the new session.
811
+ *
812
+ */
813
+ const verifyAgentSignup = (options) => (options.client ?? client).post({
814
+ url: "/agent/signup/verify",
815
+ ...options,
816
+ headers: {
817
+ ...options.body !== void 0 && { "Content-Type": "application/json" },
818
+ ...options.headers
819
+ }
820
+ });
821
+ /**
822
+ * Revoke the current CLI OAuth session
823
+ *
824
+ * Revokes the OAuth grant used to authenticate the request. API-key
825
+ * authenticated legacy logout requests succeed without deleting server API
826
+ * keys so old local CLI state can be cleared safely.
772
827
  *
773
828
  */
774
829
  const cliLogout = (options) => (options?.client ?? client).post({
@@ -1778,7 +1833,7 @@ const openapiDocument = {
1778
1833
  "info": {
1779
1834
  "title": "Primitive API",
1780
1835
  "version": "1.0.0",
1781
- "description": "The Primitive API lets you manage domains, emails, webhook endpoints,\nfilters, and account settings programmatically.\n\n## Authentication\n\nMost endpoints require a Bearer token in the `Authorization` header:\n\n```\nAuthorization: Bearer prim_<your_api_key>\n```\n\nAPI keys are org-scoped. Create and manage them in your dashboard\nunder Settings > API Keys. CLI login and signup endpoints explicitly\ndeclare `security: []`; they do not require an API key because they\nare used to mint one.\n\n## Rate Limiting\n\nThe API enforces a sliding window rate limit of **120 requests per\n60 seconds** per organization. When exceeded, the API returns `429`\nwith a `Retry-After` header indicating how many seconds to wait.\n\n## Pagination\n\nList endpoints use cursor-based pagination. Responses include a\n`meta` object with `total`, `limit`, and `cursor` fields. Pass the\n`cursor` value as a query parameter to fetch the next page. When\n`cursor` is `null`, there are no more results.\n\n## Response Format\n\nAll responses use a consistent envelope:\n\n```json\n{\n \"success\": true,\n \"data\": { ... },\n \"meta\": { \"total\": 42, \"limit\": 50, \"cursor\": \"...\" }\n}\n```\n\nErrors follow the same pattern:\n\n```json\n{\n \"success\": false,\n \"error\": { \"code\": \"not_found\", \"message\": \"Email not found\" }\n}\n```\n\n## Webhook signing\n\nOutbound webhook deliveries (configured via the `endpoints` API)\nare signed so receivers can verify they came from Primitive and\nhave not been tampered with in transit. The signing scheme is\ndeliberately simple so it can be reimplemented in any language\nin a few lines. The Node SDK's `verifyWebhookSignature` helper\nis the reference implementation; the wire details below let you\nwrite a verifier in Python, Go, Ruby, etc. without reading our\nsource.\n\n**Header**: `Primitive-Signature: t=<unix-seconds>,v1=<hex>`\n\nA legacy `MyMX-Signature` header is also sent on every delivery\nwith the same value, retained for back-compatibility with\nintegrations written before the rename. New code should read\n`Primitive-Signature`.\n\n**Signed string**: `${timestamp}.${rawBody}` where `timestamp`\nis the Unix-seconds integer from the `t=` parameter and\n`rawBody` is the exact bytes of the HTTP request body BEFORE\nany JSON decoding. Verify against the raw body, not a\nre-serialized parse, or you will silently mismatch on\ninsignificant whitespace.\n\n**Signature**: HMAC-SHA256 of the signed string, hex-encoded\n(lowercase). Use the account's webhook secret as the HMAC key,\nas a UTF-8 byte sequence.\n\n**Secret**: returned by `GET /account/webhook-secret`. The\nstring looks base64-shaped (e.g. `XNHBBW8VqoBjRfNs1tkZj11jTk...`)\nbut is NOT base64; use it AS-IS as a UTF-8 string for the HMAC\nkey. Base64-decoding before HMAC will silently produce\nmismatched signatures.\n\n**Tolerance**: by convention, reject deliveries whose `t=`\ntimestamp is more than 5 minutes off your wall-clock to defend\nagainst replay attacks. The Node SDK's helper enforces this by\ndefault.\n\n**Verification recipe** (any language):\n\n```\n1. Read the raw HTTP body (do not parse).\n2. Read `Primitive-Signature: t=<ts>,v1=<sig>`.\n3. Reject if abs(now - ts) > 300 seconds.\n4. expected = HMAC_SHA256_hex(secret_utf8, f\"{ts}.{rawBody}\")\n5. Constant-time compare expected to sig. Reject if not equal.\n```\n\nFor Node, use `verifyWebhookSignature` from\n`@primitivedotdev/sdk/webhook` (or the higher-level\n`handleWebhook` helper if you want a one-liner). For other\nlanguages, the recipe above is everything you need.\n\nTest deliveries: `POST /endpoints/{id}/test` triggers a fake\ndelivery to your endpoint URL, signed with your real account\nsecret, so you can confirm verification end-to-end without\nneeding real inbound mail. The test response carries the exact\n`signature` header value sent on the wire so you can compare\nstrings directly.\n",
1836
+ "description": "The Primitive API lets you manage domains, emails, webhook endpoints,\nfilters, and account settings programmatically.\n\n## Authentication\n\nMost endpoints require a Bearer token in the `Authorization` header:\n\n```\nAuthorization: Bearer prim_<your_api_key>\n```\n\nAPI keys are org-scoped. Create and manage them in your dashboard\nunder Settings > API Keys. CLI login plus CLI/agent signup endpoints\nexplicitly declare `security: []`; they do not require an API key because\nthey are used to create OAuth CLI sessions.\n\n## Rate Limiting\n\nThe API enforces a sliding window rate limit of **120 requests per\n60 seconds** per organization. When exceeded, the API returns `429`\nwith a `Retry-After` header indicating how many seconds to wait.\n\n## Pagination\n\nList endpoints use cursor-based pagination. Responses include a\n`meta` object with `total`, `limit`, and `cursor` fields. Pass the\n`cursor` value as a query parameter to fetch the next page. When\n`cursor` is `null`, there are no more results.\n\n## Response Format\n\nAll responses use a consistent envelope:\n\n```json\n{\n \"success\": true,\n \"data\": { ... },\n \"meta\": { \"total\": 42, \"limit\": 50, \"cursor\": \"...\" }\n}\n```\n\nErrors follow the same pattern:\n\n```json\n{\n \"success\": false,\n \"error\": { \"code\": \"not_found\", \"message\": \"Email not found\" }\n}\n```\n\n## Webhook signing\n\nOutbound webhook deliveries (configured via the `endpoints` API)\nare signed so receivers can verify they came from Primitive and\nhave not been tampered with in transit. The signing scheme is\ndeliberately simple so it can be reimplemented in any language\nin a few lines. The Node SDK's `verifyWebhookSignature` helper\nis the reference implementation; the wire details below let you\nwrite a verifier in Python, Go, Ruby, etc. without reading our\nsource.\n\n**Header**: `Primitive-Signature: t=<unix-seconds>,v1=<hex>`\n\nA legacy `MyMX-Signature` header is also sent on every delivery\nwith the same value, retained for back-compatibility with\nintegrations written before the rename. New code should read\n`Primitive-Signature`.\n\n**Signed string**: `${timestamp}.${rawBody}` where `timestamp`\nis the Unix-seconds integer from the `t=` parameter and\n`rawBody` is the exact bytes of the HTTP request body BEFORE\nany JSON decoding. Verify against the raw body, not a\nre-serialized parse, or you will silently mismatch on\ninsignificant whitespace.\n\n**Signature**: HMAC-SHA256 of the signed string, hex-encoded\n(lowercase). Use the account's webhook secret as the HMAC key,\nas a UTF-8 byte sequence.\n\n**Secret**: returned by `GET /account/webhook-secret`. The\nstring looks base64-shaped (e.g. `XNHBBW8VqoBjRfNs1tkZj11jTk...`)\nbut is NOT base64; use it AS-IS as a UTF-8 string for the HMAC\nkey. Base64-decoding before HMAC will silently produce\nmismatched signatures.\n\n**Tolerance**: by convention, reject deliveries whose `t=`\ntimestamp is more than 5 minutes off your wall-clock to defend\nagainst replay attacks. The Node SDK's helper enforces this by\ndefault.\n\n**Verification recipe** (any language):\n\n```\n1. Read the raw HTTP body (do not parse).\n2. Read `Primitive-Signature: t=<ts>,v1=<sig>`.\n3. Reject if abs(now - ts) > 300 seconds.\n4. expected = HMAC_SHA256_hex(secret_utf8, f\"{ts}.{rawBody}\")\n5. Constant-time compare expected to sig. Reject if not equal.\n```\n\nFor Node, use `verifyWebhookSignature` from\n`@primitivedotdev/sdk/webhook` (or the higher-level\n`handleWebhook` helper if you want a one-liner). For other\nlanguages, the recipe above is everything you need.\n\nTest deliveries: `POST /endpoints/{id}/test` triggers a fake\ndelivery to your endpoint URL, signed with your real account\nsecret, so you can confirm verification end-to-end without\nneeding real inbound mail. The test response carries the exact\n`signature` header value sent on the wire so you can compare\nstrings directly.\n",
1782
1837
  "contact": {
1783
1838
  "name": "Primitive",
1784
1839
  "url": "https://primitive.dev"
@@ -1801,6 +1856,10 @@ const openapiDocument = {
1801
1856
  "name": "CLI",
1802
1857
  "description": "Browser-assisted CLI authentication"
1803
1858
  },
1859
+ {
1860
+ "name": "Agent",
1861
+ "description": "Agent signup and authentication"
1862
+ },
1804
1863
  {
1805
1864
  "name": "Account",
1806
1865
  "description": "Manage your account settings, storage, and webhook secret"
@@ -1864,7 +1923,7 @@ const openapiDocument = {
1864
1923
  "/cli/login/poll": { "post": {
1865
1924
  "operationId": "pollCliLogin",
1866
1925
  "summary": "Poll CLI browser login",
1867
- "description": "Polls a CLI login session until the browser approval either succeeds,\nis denied, expires, or is polled too quickly. The API key is generated\nonly after approval and is returned exactly once.\n",
1926
+ "description": "Polls a CLI login session until the browser approval either succeeds,\nis denied, expires, or is polled too quickly. The OAuth token set is\ncreated only after approval and is returned exactly once.\n",
1868
1927
  "tags": ["CLI"],
1869
1928
  "security": [],
1870
1929
  "requestBody": {
@@ -1873,7 +1932,7 @@ const openapiDocument = {
1873
1932
  },
1874
1933
  "responses": {
1875
1934
  "200": {
1876
- "description": "CLI login approved and API key created",
1935
+ "description": "CLI login approved and OAuth token set created",
1877
1936
  "headers": { "Cache-Control": {
1878
1937
  "schema": { "type": "string" },
1879
1938
  "description": "Always `no-store`"
@@ -2014,8 +2073,8 @@ const openapiDocument = {
2014
2073
  } },
2015
2074
  "/cli/signup/verify": { "post": {
2016
2075
  "operationId": "verifyCliSignup",
2017
- "summary": "Verify CLI signup and create API key",
2018
- "description": "Verifies the email code for a CLI signup session, creates the account,\nredeems the reserved signup code, mints an org-scoped CLI API key, and\nreturns the raw key exactly once. This endpoint does not require an API key.\n",
2076
+ "summary": "Verify CLI signup and create OAuth session",
2077
+ "description": "Verifies the email code for a CLI signup session, creates the account,\nredeems the reserved signup code, creates an org-scoped OAuth CLI\nsession, and returns the token set exactly once. This endpoint does not\nrequire an API key.\n",
2019
2078
  "tags": ["CLI"],
2020
2079
  "security": [],
2021
2080
  "requestBody": {
@@ -2024,7 +2083,7 @@ const openapiDocument = {
2024
2083
  },
2025
2084
  "responses": {
2026
2085
  "200": {
2027
- "description": "CLI signup verified and API key created",
2086
+ "description": "CLI signup verified and OAuth token set created",
2028
2087
  "headers": { "Cache-Control": {
2029
2088
  "schema": { "type": "string" },
2030
2089
  "description": "Always `no-store`"
@@ -2041,10 +2100,106 @@ const openapiDocument = {
2041
2100
  "429": { "$ref": "#/components/responses/RateLimited" }
2042
2101
  }
2043
2102
  } },
2103
+ "/agent/signup/start": { "post": {
2104
+ "operationId": "startAgentSignup",
2105
+ "summary": "Start agent account signup",
2106
+ "description": "Starts an agent-native signup session. The API validates the signup code,\ncreates a pending signup session, sends an email verification code, and\nreturns an opaque signup token used by the resend and verify steps. This\nendpoint does not require an API key.\n",
2107
+ "tags": ["Agent"],
2108
+ "security": [],
2109
+ "requestBody": {
2110
+ "required": true,
2111
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StartAgentSignupInput" } } }
2112
+ },
2113
+ "responses": {
2114
+ "201": {
2115
+ "description": "Agent signup session created and verification email sent",
2116
+ "headers": { "Cache-Control": {
2117
+ "schema": { "type": "string" },
2118
+ "description": "Always `no-store`"
2119
+ } },
2120
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
2121
+ "type": "object",
2122
+ "properties": { "data": { "$ref": "#/components/schemas/AgentSignupStartResult" } }
2123
+ }] } } }
2124
+ },
2125
+ "400": { "$ref": "#/components/responses/ValidationError" },
2126
+ "429": { "$ref": "#/components/responses/RateLimited" }
2127
+ }
2128
+ } },
2129
+ "/agent/signup/resend": { "post": {
2130
+ "operationId": "resendAgentSignupVerification",
2131
+ "summary": "Resend agent signup verification code",
2132
+ "description": "Sends a new email verification code for a pending agent signup session.\nThis endpoint does not require an API key.\n",
2133
+ "tags": ["Agent"],
2134
+ "security": [],
2135
+ "requestBody": {
2136
+ "required": true,
2137
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResendAgentSignupVerificationInput" } } }
2138
+ },
2139
+ "responses": {
2140
+ "200": {
2141
+ "description": "Verification email resent",
2142
+ "headers": { "Cache-Control": {
2143
+ "schema": { "type": "string" },
2144
+ "description": "Always `no-store`"
2145
+ } },
2146
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
2147
+ "type": "object",
2148
+ "properties": { "data": { "$ref": "#/components/schemas/AgentSignupResendResult" } }
2149
+ }] } } }
2150
+ },
2151
+ "400": {
2152
+ "description": "Invalid token or expired token",
2153
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
2154
+ },
2155
+ "429": {
2156
+ "description": "Global rate limit exceeded or resend requested too quickly",
2157
+ "headers": { "Retry-After": {
2158
+ "schema": { "type": "integer" },
2159
+ "description": "Seconds to wait before retrying"
2160
+ } },
2161
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
2162
+ }
2163
+ }
2164
+ } },
2165
+ "/agent/signup/verify": { "post": {
2166
+ "operationId": "verifyAgentSignup",
2167
+ "summary": "Verify agent signup and create OAuth tokens",
2168
+ "description": "Verifies the email code for an agent signup session, creates the account\nwhen needed, redeems the reserved signup code, mints an org-scoped OAuth\nsession for CLI authentication, and returns the raw tokens exactly once.\nFor existing users, the optional `org_id` selects which accessible\nworkspace should receive the new session.\n",
2169
+ "tags": ["Agent"],
2170
+ "security": [],
2171
+ "requestBody": {
2172
+ "required": true,
2173
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VerifyAgentSignupInput" } } }
2174
+ },
2175
+ "responses": {
2176
+ "200": {
2177
+ "description": "Agent signup verified and OAuth tokens created",
2178
+ "headers": { "Cache-Control": {
2179
+ "schema": { "type": "string" },
2180
+ "description": "Always `no-store`"
2181
+ } },
2182
+ "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
2183
+ "type": "object",
2184
+ "properties": { "data": { "$ref": "#/components/schemas/AgentSignupVerifyResult" } }
2185
+ }] } } }
2186
+ },
2187
+ "400": {
2188
+ "description": "Invalid request, invalid verification code, expired token, invalid signup code, or account creation failure",
2189
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
2190
+ },
2191
+ "403": { "$ref": "#/components/responses/Forbidden" },
2192
+ "409": {
2193
+ "description": "Existing account is not in a usable workspace state",
2194
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
2195
+ },
2196
+ "429": { "$ref": "#/components/responses/RateLimited" }
2197
+ }
2198
+ } },
2044
2199
  "/cli/logout": { "post": {
2045
2200
  "operationId": "cliLogout",
2046
- "summary": "Revoke the current CLI API key",
2047
- "description": "Revokes the API key used to authenticate the request. CLI clients use\nthis endpoint during `primitive logout` before removing local credentials.\n",
2201
+ "summary": "Revoke the current CLI OAuth session",
2202
+ "description": "Revokes the OAuth grant used to authenticate the request. API-key\nauthenticated legacy logout requests succeed without deleting server API\nkeys so old local CLI state can be cleared safely.\n",
2048
2203
  "tags": ["CLI"],
2049
2204
  "requestBody": {
2050
2205
  "required": false,
@@ -2052,7 +2207,7 @@ const openapiDocument = {
2052
2207
  },
2053
2208
  "responses": {
2054
2209
  "200": {
2055
- "description": "CLI API key revoked",
2210
+ "description": "CLI logout completed",
2056
2211
  "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/SuccessEnvelope" }, {
2057
2212
  "type": "object",
2058
2213
  "properties": { "data": { "$ref": "#/components/schemas/CliLogoutResult" } }
@@ -3780,7 +3935,14 @@ const openapiDocument = {
3780
3935
  "slow_down",
3781
3936
  "access_denied",
3782
3937
  "expired_token",
3783
- "invalid_device_code"
3938
+ "invalid_device_code",
3939
+ "invalid_signup_code",
3940
+ "invalid_signup_token",
3941
+ "invalid_verification_code",
3942
+ "email_delivery_failed",
3943
+ "clerk_signup_failed",
3944
+ "no_orgs_for_user",
3945
+ "org_not_accessible"
3784
3946
  ]
3785
3947
  },
3786
3948
  "message": { "type": "string" },
@@ -3964,13 +4126,42 @@ const openapiDocument = {
3964
4126
  "properties": {
3965
4127
  "api_key": {
3966
4128
  "type": "string",
3967
- "description": "Newly-created API key for CLI authentication"
4129
+ "description": "Legacy alias for access_token. New CLI builds should persist access_token and refresh_token."
3968
4130
  },
3969
4131
  "key_id": {
4132
+ "type": "string",
4133
+ "format": "uuid",
4134
+ "description": "Legacy alias for oauth_grant_id"
4135
+ },
4136
+ "key_prefix": {
4137
+ "type": "string",
4138
+ "description": "Legacy display prefix derived from access_token"
4139
+ },
4140
+ "access_token": {
4141
+ "type": "string",
4142
+ "description": "OAuth access token for CLI API authentication"
4143
+ },
4144
+ "refresh_token": {
4145
+ "type": "string",
4146
+ "description": "OAuth refresh token used by the CLI to renew access"
4147
+ },
4148
+ "token_type": {
4149
+ "type": "string",
4150
+ "enum": ["Bearer"]
4151
+ },
4152
+ "expires_in": {
4153
+ "type": "integer",
4154
+ "description": "Seconds until access_token expires"
4155
+ },
4156
+ "auth_method": {
4157
+ "type": "string",
4158
+ "enum": ["oauth"]
4159
+ },
4160
+ "oauth_grant_id": {
3970
4161
  "type": "string",
3971
4162
  "format": "uuid"
3972
4163
  },
3973
- "key_prefix": { "type": "string" },
4164
+ "oauth_client_id": { "type": "string" },
3974
4165
  "org_id": {
3975
4166
  "type": "string",
3976
4167
  "format": "uuid"
@@ -3981,6 +4172,13 @@ const openapiDocument = {
3981
4172
  "api_key",
3982
4173
  "key_id",
3983
4174
  "key_prefix",
4175
+ "access_token",
4176
+ "refresh_token",
4177
+ "token_type",
4178
+ "expires_in",
4179
+ "auth_method",
4180
+ "oauth_grant_id",
4181
+ "oauth_client_id",
3984
4182
  "org_id",
3985
4183
  "org_name"
3986
4184
  ]
@@ -4008,7 +4206,7 @@ const openapiDocument = {
4008
4206
  "type": "string",
4009
4207
  "minLength": 1,
4010
4208
  "maxLength": 80,
4011
- "description": "Human-readable device name used for the created CLI API key"
4209
+ "description": "Human-readable device name used for the created CLI OAuth grant"
4012
4210
  },
4013
4211
  "metadata": {
4014
4212
  "type": "object",
@@ -4109,24 +4307,49 @@ const openapiDocument = {
4109
4307
  "maxLength": 1024
4110
4308
  }
4111
4309
  },
4112
- "required": [
4113
- "signup_token",
4114
- "verification_code",
4115
- "password"
4116
- ]
4310
+ "required": ["signup_token", "verification_code"]
4117
4311
  },
4118
4312
  "CliSignupVerifyResult": {
4119
4313
  "type": "object",
4120
4314
  "properties": {
4121
4315
  "api_key": {
4122
4316
  "type": "string",
4123
- "description": "Newly-created API key for CLI authentication"
4317
+ "description": "Legacy alias for access_token. New CLI builds should persist access_token and refresh_token."
4124
4318
  },
4125
4319
  "key_id": {
4320
+ "type": "string",
4321
+ "format": "uuid",
4322
+ "description": "Legacy alias for oauth_grant_id"
4323
+ },
4324
+ "key_prefix": {
4325
+ "type": "string",
4326
+ "description": "Legacy display prefix derived from access_token"
4327
+ },
4328
+ "access_token": {
4329
+ "type": "string",
4330
+ "description": "OAuth access token for CLI API authentication"
4331
+ },
4332
+ "refresh_token": {
4333
+ "type": "string",
4334
+ "description": "OAuth refresh token used by the CLI to renew access"
4335
+ },
4336
+ "token_type": {
4337
+ "type": "string",
4338
+ "enum": ["Bearer"]
4339
+ },
4340
+ "expires_in": {
4341
+ "type": "integer",
4342
+ "description": "Seconds until access_token expires"
4343
+ },
4344
+ "auth_method": {
4345
+ "type": "string",
4346
+ "enum": ["oauth"]
4347
+ },
4348
+ "oauth_grant_id": {
4126
4349
  "type": "string",
4127
4350
  "format": "uuid"
4128
4351
  },
4129
- "key_prefix": { "type": "string" },
4352
+ "oauth_client_id": { "type": "string" },
4130
4353
  "org_id": {
4131
4354
  "type": "string",
4132
4355
  "format": "uuid"
@@ -4137,93 +4360,311 @@ const openapiDocument = {
4137
4360
  "api_key",
4138
4361
  "key_id",
4139
4362
  "key_prefix",
4363
+ "access_token",
4364
+ "refresh_token",
4365
+ "token_type",
4366
+ "expires_in",
4367
+ "auth_method",
4368
+ "oauth_grant_id",
4369
+ "oauth_client_id",
4140
4370
  "org_id",
4141
4371
  "org_name"
4142
4372
  ]
4143
4373
  },
4144
- "CliLogoutInput": {
4374
+ "StartAgentSignupInput": {
4145
4375
  "type": "object",
4146
4376
  "additionalProperties": false,
4147
- "properties": { "key_id": {
4148
- "type": "string",
4149
- "format": "uuid",
4150
- "description": "Optional key id guard; when provided it must match the authenticated API key"
4151
- } }
4152
- },
4153
- "CliLogoutResult": {
4154
- "type": "object",
4155
4377
  "properties": {
4156
- "revoked": {
4378
+ "email": {
4379
+ "type": "string",
4380
+ "format": "email",
4381
+ "maxLength": 254
4382
+ },
4383
+ "signup_code": {
4384
+ "type": "string",
4385
+ "minLength": 1,
4386
+ "maxLength": 128
4387
+ },
4388
+ "terms_accepted": {
4157
4389
  "type": "boolean",
4158
- "const": true
4390
+ "const": true,
4391
+ "description": "Must be true to confirm acceptance of Primitive's Terms of Service and Privacy Policy"
4159
4392
  },
4160
- "key_id": {
4393
+ "device_name": {
4161
4394
  "type": "string",
4162
- "format": "uuid"
4395
+ "minLength": 1,
4396
+ "maxLength": 80,
4397
+ "description": "Human-readable device name used for the created agent OAuth session"
4398
+ },
4399
+ "metadata": {
4400
+ "type": "object",
4401
+ "additionalProperties": true,
4402
+ "description": "Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer"
4163
4403
  }
4164
4404
  },
4165
- "required": ["revoked", "key_id"]
4405
+ "required": [
4406
+ "email",
4407
+ "signup_code",
4408
+ "terms_accepted"
4409
+ ]
4166
4410
  },
4167
- "Account": {
4411
+ "AgentSignupStartResult": {
4168
4412
  "type": "object",
4169
4413
  "properties": {
4170
- "id": {
4414
+ "signup_token": {
4171
4415
  "type": "string",
4172
- "format": "uuid"
4416
+ "description": "Opaque token used to verify or resend the pending agent signup"
4173
4417
  },
4174
- "email": { "type": "string" },
4175
- "plan": { "type": "string" },
4176
- "created_at": {
4418
+ "email": {
4177
4419
  "type": "string",
4178
- "format": "date-time"
4420
+ "format": "email"
4179
4421
  },
4180
- "onboarding_completed": { "type": "boolean" },
4181
- "onboarding_step": { "type": ["string", "null"] },
4182
- "stripe_subscription_status": { "type": ["string", "null"] },
4183
- "subscription_current_period_end": {
4184
- "type": ["string", "null"],
4185
- "format": "date-time"
4422
+ "expires_in": {
4423
+ "type": "integer",
4424
+ "description": "Seconds until the pending signup expires"
4186
4425
  },
4187
- "subscription_cancel_at_period_end": { "type": ["boolean", "null"] },
4188
- "spam_threshold": {
4189
- "type": ["number", "null"],
4190
- "minimum": 0,
4191
- "maximum": 15
4426
+ "resend_after": {
4427
+ "type": "integer",
4428
+ "description": "Minimum seconds before requesting another verification email"
4192
4429
  },
4193
- "discard_content_on_webhook_confirmed": { "type": "boolean" },
4194
- "webhook_secret_rotated_at": {
4195
- "type": ["string", "null"],
4196
- "format": "date-time"
4430
+ "verification_code_length": {
4431
+ "type": "integer",
4432
+ "description": "Number of digits in the emailed verification code"
4197
4433
  }
4198
4434
  },
4199
4435
  "required": [
4200
- "id",
4436
+ "signup_token",
4201
4437
  "email",
4202
- "plan",
4203
- "created_at",
4204
- "discard_content_on_webhook_confirmed"
4438
+ "expires_in",
4439
+ "resend_after",
4440
+ "verification_code_length"
4205
4441
  ]
4206
4442
  },
4207
- "AccountUpdated": {
4443
+ "ResendAgentSignupVerificationInput": {
4444
+ "type": "object",
4445
+ "additionalProperties": false,
4446
+ "properties": { "signup_token": {
4447
+ "type": "string",
4448
+ "minLength": 1
4449
+ } },
4450
+ "required": ["signup_token"]
4451
+ },
4452
+ "AgentSignupResendResult": {
4208
4453
  "type": "object",
4209
4454
  "properties": {
4210
- "id": {
4455
+ "email": {
4211
4456
  "type": "string",
4212
- "format": "uuid"
4457
+ "format": "email"
4213
4458
  },
4214
- "email": { "type": "string" },
4215
- "plan": { "type": "string" },
4216
- "spam_threshold": {
4217
- "type": ["number", "null"],
4218
- "minimum": 0,
4219
- "maximum": 15
4459
+ "expires_in": {
4460
+ "type": "integer",
4461
+ "description": "Seconds until the pending signup expires"
4220
4462
  },
4221
- "discard_content_on_webhook_confirmed": { "type": "boolean" }
4463
+ "resend_after": {
4464
+ "type": "integer",
4465
+ "description": "Minimum seconds before requesting another verification email"
4466
+ },
4467
+ "verification_code_length": {
4468
+ "type": "integer",
4469
+ "description": "Number of digits in the emailed verification code"
4470
+ }
4222
4471
  },
4223
4472
  "required": [
4224
- "id",
4225
4473
  "email",
4226
- "plan",
4474
+ "expires_in",
4475
+ "resend_after",
4476
+ "verification_code_length"
4477
+ ]
4478
+ },
4479
+ "VerifyAgentSignupInput": {
4480
+ "type": "object",
4481
+ "additionalProperties": false,
4482
+ "properties": {
4483
+ "signup_token": {
4484
+ "type": "string",
4485
+ "minLength": 1
4486
+ },
4487
+ "verification_code": {
4488
+ "type": "string",
4489
+ "minLength": 1,
4490
+ "maxLength": 32
4491
+ },
4492
+ "org_id": {
4493
+ "type": "string",
4494
+ "format": "uuid",
4495
+ "description": "Optional workspace id to target when the verified email already belongs to multiple workspaces"
4496
+ }
4497
+ },
4498
+ "required": ["signup_token", "verification_code"]
4499
+ },
4500
+ "AgentOrgRef": {
4501
+ "type": "object",
4502
+ "properties": {
4503
+ "id": {
4504
+ "type": "string",
4505
+ "format": "uuid"
4506
+ },
4507
+ "name": { "type": ["string", "null"] }
4508
+ },
4509
+ "required": ["id", "name"]
4510
+ },
4511
+ "AgentSignupVerifyResult": {
4512
+ "type": "object",
4513
+ "properties": {
4514
+ "api_key": {
4515
+ "type": "string",
4516
+ "description": "Legacy alias for access_token. New CLI builds should persist access_token and refresh_token."
4517
+ },
4518
+ "key_id": {
4519
+ "type": "string",
4520
+ "format": "uuid",
4521
+ "description": "Legacy alias for oauth_grant_id"
4522
+ },
4523
+ "key_prefix": {
4524
+ "type": "string",
4525
+ "description": "Legacy display prefix derived from access_token"
4526
+ },
4527
+ "access_token": {
4528
+ "type": "string",
4529
+ "description": "OAuth access token for CLI API authentication"
4530
+ },
4531
+ "refresh_token": {
4532
+ "type": "string",
4533
+ "description": "OAuth refresh token used by the CLI to renew access"
4534
+ },
4535
+ "token_type": {
4536
+ "type": "string",
4537
+ "enum": ["Bearer"]
4538
+ },
4539
+ "expires_in": {
4540
+ "type": "integer",
4541
+ "description": "Seconds until access_token expires"
4542
+ },
4543
+ "auth_method": {
4544
+ "type": "string",
4545
+ "enum": ["oauth"]
4546
+ },
4547
+ "oauth_grant_id": {
4548
+ "type": "string",
4549
+ "format": "uuid"
4550
+ },
4551
+ "oauth_client_id": { "type": "string" },
4552
+ "org_id": {
4553
+ "type": "string",
4554
+ "format": "uuid"
4555
+ },
4556
+ "org_name": { "type": ["string", "null"] },
4557
+ "orgs": {
4558
+ "type": "array",
4559
+ "items": { "$ref": "#/components/schemas/AgentOrgRef" },
4560
+ "description": "Workspaces available to the verified email. The minted session targets `org_id`."
4561
+ }
4562
+ },
4563
+ "required": [
4564
+ "api_key",
4565
+ "key_id",
4566
+ "key_prefix",
4567
+ "access_token",
4568
+ "refresh_token",
4569
+ "token_type",
4570
+ "expires_in",
4571
+ "auth_method",
4572
+ "oauth_grant_id",
4573
+ "oauth_client_id",
4574
+ "org_id",
4575
+ "org_name",
4576
+ "orgs"
4577
+ ]
4578
+ },
4579
+ "CliLogoutInput": {
4580
+ "type": "object",
4581
+ "additionalProperties": false,
4582
+ "properties": { "key_id": {
4583
+ "type": "string",
4584
+ "format": "uuid",
4585
+ "description": "Optional id guard; when provided it must match the authenticated OAuth grant id or API key id"
4586
+ } }
4587
+ },
4588
+ "CliLogoutResult": {
4589
+ "type": "object",
4590
+ "properties": {
4591
+ "revoked": {
4592
+ "type": "boolean",
4593
+ "description": "True when an OAuth grant was revoked. False for API-key-authenticated legacy logout, which only clears local CLI state."
4594
+ },
4595
+ "key_id": {
4596
+ "type": "string",
4597
+ "format": "uuid",
4598
+ "description": "API key id for API-key-authenticated legacy logout"
4599
+ },
4600
+ "oauth_grant_id": {
4601
+ "type": "string",
4602
+ "format": "uuid",
4603
+ "description": "OAuth grant id revoked by OAuth-authenticated logout"
4604
+ }
4605
+ },
4606
+ "required": ["revoked"]
4607
+ },
4608
+ "Account": {
4609
+ "type": "object",
4610
+ "properties": {
4611
+ "id": {
4612
+ "type": "string",
4613
+ "format": "uuid"
4614
+ },
4615
+ "email": { "type": "string" },
4616
+ "plan": { "type": "string" },
4617
+ "created_at": {
4618
+ "type": "string",
4619
+ "format": "date-time"
4620
+ },
4621
+ "onboarding_completed": { "type": "boolean" },
4622
+ "onboarding_step": { "type": ["string", "null"] },
4623
+ "stripe_subscription_status": { "type": ["string", "null"] },
4624
+ "subscription_current_period_end": {
4625
+ "type": ["string", "null"],
4626
+ "format": "date-time"
4627
+ },
4628
+ "subscription_cancel_at_period_end": { "type": ["boolean", "null"] },
4629
+ "spam_threshold": {
4630
+ "type": ["number", "null"],
4631
+ "minimum": 0,
4632
+ "maximum": 15
4633
+ },
4634
+ "discard_content_on_webhook_confirmed": { "type": "boolean" },
4635
+ "webhook_secret_rotated_at": {
4636
+ "type": ["string", "null"],
4637
+ "format": "date-time"
4638
+ }
4639
+ },
4640
+ "required": [
4641
+ "id",
4642
+ "email",
4643
+ "plan",
4644
+ "created_at",
4645
+ "discard_content_on_webhook_confirmed"
4646
+ ]
4647
+ },
4648
+ "AccountUpdated": {
4649
+ "type": "object",
4650
+ "properties": {
4651
+ "id": {
4652
+ "type": "string",
4653
+ "format": "uuid"
4654
+ },
4655
+ "email": { "type": "string" },
4656
+ "plan": { "type": "string" },
4657
+ "spam_threshold": {
4658
+ "type": ["number", "null"],
4659
+ "minimum": 0,
4660
+ "maximum": 15
4661
+ },
4662
+ "discard_content_on_webhook_confirmed": { "type": "boolean" }
4663
+ },
4664
+ "required": [
4665
+ "id",
4666
+ "email",
4667
+ "plan",
4227
4668
  "discard_content_on_webhook_confirmed"
4228
4669
  ]
4229
4670
  },
@@ -6366,105 +6807,15 @@ const operationManifest = [
6366
6807
  "tag": "Account",
6367
6808
  "tagCommand": "account"
6368
6809
  },
6369
- {
6370
- "binaryResponse": false,
6371
- "bodyRequired": false,
6372
- "command": "cli-logout",
6373
- "description": "Revokes the API key used to authenticate the request. CLI clients use\nthis endpoint during `primitive logout` before removing local credentials.\n",
6374
- "hasJsonBody": true,
6375
- "method": "POST",
6376
- "operationId": "cliLogout",
6377
- "path": "/cli/logout",
6378
- "pathParams": [],
6379
- "queryParams": [],
6380
- "requestSchema": {
6381
- "type": "object",
6382
- "additionalProperties": false,
6383
- "properties": { "key_id": {
6384
- "type": "string",
6385
- "format": "uuid",
6386
- "description": "Optional key id guard; when provided it must match the authenticated API key"
6387
- } }
6388
- },
6389
- "responseSchema": {
6390
- "type": "object",
6391
- "properties": {
6392
- "revoked": {
6393
- "type": "boolean",
6394
- "const": true
6395
- },
6396
- "key_id": {
6397
- "type": "string",
6398
- "format": "uuid"
6399
- }
6400
- },
6401
- "required": ["revoked", "key_id"]
6402
- },
6403
- "sdkName": "cliLogout",
6404
- "summary": "Revoke the current CLI API key",
6405
- "tag": "CLI",
6406
- "tagCommand": "cli"
6407
- },
6408
- {
6409
- "binaryResponse": false,
6410
- "bodyRequired": true,
6411
- "command": "poll-cli-login",
6412
- "description": "Polls a CLI login session until the browser approval either succeeds,\nis denied, expires, or is polled too quickly. The API key is generated\nonly after approval and is returned exactly once.\n",
6413
- "hasJsonBody": true,
6414
- "method": "POST",
6415
- "operationId": "pollCliLogin",
6416
- "path": "/cli/login/poll",
6417
- "pathParams": [],
6418
- "queryParams": [],
6419
- "requestSchema": {
6420
- "type": "object",
6421
- "additionalProperties": false,
6422
- "properties": { "device_code": {
6423
- "type": "string",
6424
- "minLength": 1
6425
- } },
6426
- "required": ["device_code"]
6427
- },
6428
- "responseSchema": {
6429
- "type": "object",
6430
- "properties": {
6431
- "api_key": {
6432
- "type": "string",
6433
- "description": "Newly-created API key for CLI authentication"
6434
- },
6435
- "key_id": {
6436
- "type": "string",
6437
- "format": "uuid"
6438
- },
6439
- "key_prefix": { "type": "string" },
6440
- "org_id": {
6441
- "type": "string",
6442
- "format": "uuid"
6443
- },
6444
- "org_name": { "type": ["string", "null"] }
6445
- },
6446
- "required": [
6447
- "api_key",
6448
- "key_id",
6449
- "key_prefix",
6450
- "org_id",
6451
- "org_name"
6452
- ]
6453
- },
6454
- "sdkName": "pollCliLogin",
6455
- "summary": "Poll CLI browser login",
6456
- "tag": "CLI",
6457
- "tagCommand": "cli"
6458
- },
6459
6810
  {
6460
6811
  "binaryResponse": false,
6461
6812
  "bodyRequired": true,
6462
- "command": "resend-cli-signup-verification",
6463
- "description": "Sends a new email verification code for a pending CLI signup session.\nThis endpoint does not require an API key.\n",
6813
+ "command": "resend-agent-signup-verification",
6814
+ "description": "Sends a new email verification code for a pending agent signup session.\nThis endpoint does not require an API key.\n",
6464
6815
  "hasJsonBody": true,
6465
6816
  "method": "POST",
6466
- "operationId": "resendCliSignupVerification",
6467
- "path": "/cli/signup/resend",
6817
+ "operationId": "resendAgentSignupVerification",
6818
+ "path": "/agent/signup/resend",
6468
6819
  "pathParams": [],
6469
6820
  "queryParams": [],
6470
6821
  "requestSchema": {
@@ -6503,31 +6854,415 @@ const operationManifest = [
6503
6854
  "verification_code_length"
6504
6855
  ]
6505
6856
  },
6506
- "sdkName": "resendCliSignupVerification",
6507
- "summary": "Resend CLI signup verification code",
6508
- "tag": "CLI",
6509
- "tagCommand": "cli"
6857
+ "sdkName": "resendAgentSignupVerification",
6858
+ "summary": "Resend agent signup verification code",
6859
+ "tag": "Agent",
6860
+ "tagCommand": "agent"
6510
6861
  },
6511
6862
  {
6512
6863
  "binaryResponse": false,
6513
- "bodyRequired": false,
6514
- "command": "start-cli-login",
6515
- "description": "Starts a browser-assisted CLI login session. The response includes a\ndevice code for polling and a user code that the user approves in the\nbrowser. This endpoint does not require an API key.\n",
6864
+ "bodyRequired": true,
6865
+ "command": "start-agent-signup",
6866
+ "description": "Starts an agent-native signup session. The API validates the signup code,\ncreates a pending signup session, sends an email verification code, and\nreturns an opaque signup token used by the resend and verify steps. This\nendpoint does not require an API key.\n",
6516
6867
  "hasJsonBody": true,
6517
6868
  "method": "POST",
6518
- "operationId": "startCliLogin",
6519
- "path": "/cli/login/start",
6869
+ "operationId": "startAgentSignup",
6870
+ "path": "/agent/signup/start",
6520
6871
  "pathParams": [],
6521
6872
  "queryParams": [],
6522
6873
  "requestSchema": {
6523
6874
  "type": "object",
6524
6875
  "additionalProperties": false,
6525
6876
  "properties": {
6526
- "device_name": {
6877
+ "email": {
6527
6878
  "type": "string",
6528
- "minLength": 1,
6529
- "maxLength": 80,
6530
- "description": "Human-readable device name shown during browser approval"
6879
+ "format": "email",
6880
+ "maxLength": 254
6881
+ },
6882
+ "signup_code": {
6883
+ "type": "string",
6884
+ "minLength": 1,
6885
+ "maxLength": 128
6886
+ },
6887
+ "terms_accepted": {
6888
+ "type": "boolean",
6889
+ "const": true,
6890
+ "description": "Must be true to confirm acceptance of Primitive's Terms of Service and Privacy Policy"
6891
+ },
6892
+ "device_name": {
6893
+ "type": "string",
6894
+ "minLength": 1,
6895
+ "maxLength": 80,
6896
+ "description": "Human-readable device name used for the created agent OAuth session"
6897
+ },
6898
+ "metadata": {
6899
+ "type": "object",
6900
+ "additionalProperties": true,
6901
+ "description": "Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer"
6902
+ }
6903
+ },
6904
+ "required": [
6905
+ "email",
6906
+ "signup_code",
6907
+ "terms_accepted"
6908
+ ]
6909
+ },
6910
+ "responseSchema": {
6911
+ "type": "object",
6912
+ "properties": {
6913
+ "signup_token": {
6914
+ "type": "string",
6915
+ "description": "Opaque token used to verify or resend the pending agent signup"
6916
+ },
6917
+ "email": {
6918
+ "type": "string",
6919
+ "format": "email"
6920
+ },
6921
+ "expires_in": {
6922
+ "type": "integer",
6923
+ "description": "Seconds until the pending signup expires"
6924
+ },
6925
+ "resend_after": {
6926
+ "type": "integer",
6927
+ "description": "Minimum seconds before requesting another verification email"
6928
+ },
6929
+ "verification_code_length": {
6930
+ "type": "integer",
6931
+ "description": "Number of digits in the emailed verification code"
6932
+ }
6933
+ },
6934
+ "required": [
6935
+ "signup_token",
6936
+ "email",
6937
+ "expires_in",
6938
+ "resend_after",
6939
+ "verification_code_length"
6940
+ ]
6941
+ },
6942
+ "sdkName": "startAgentSignup",
6943
+ "summary": "Start agent account signup",
6944
+ "tag": "Agent",
6945
+ "tagCommand": "agent"
6946
+ },
6947
+ {
6948
+ "binaryResponse": false,
6949
+ "bodyRequired": true,
6950
+ "command": "verify-agent-signup",
6951
+ "description": "Verifies the email code for an agent signup session, creates the account\nwhen needed, redeems the reserved signup code, mints an org-scoped OAuth\nsession for CLI authentication, and returns the raw tokens exactly once.\nFor existing users, the optional `org_id` selects which accessible\nworkspace should receive the new session.\n",
6952
+ "hasJsonBody": true,
6953
+ "method": "POST",
6954
+ "operationId": "verifyAgentSignup",
6955
+ "path": "/agent/signup/verify",
6956
+ "pathParams": [],
6957
+ "queryParams": [],
6958
+ "requestSchema": {
6959
+ "type": "object",
6960
+ "additionalProperties": false,
6961
+ "properties": {
6962
+ "signup_token": {
6963
+ "type": "string",
6964
+ "minLength": 1
6965
+ },
6966
+ "verification_code": {
6967
+ "type": "string",
6968
+ "minLength": 1,
6969
+ "maxLength": 32
6970
+ },
6971
+ "org_id": {
6972
+ "type": "string",
6973
+ "format": "uuid",
6974
+ "description": "Optional workspace id to target when the verified email already belongs to multiple workspaces"
6975
+ }
6976
+ },
6977
+ "required": ["signup_token", "verification_code"]
6978
+ },
6979
+ "responseSchema": {
6980
+ "type": "object",
6981
+ "properties": {
6982
+ "api_key": {
6983
+ "type": "string",
6984
+ "description": "Legacy alias for access_token. New CLI builds should persist access_token and refresh_token."
6985
+ },
6986
+ "key_id": {
6987
+ "type": "string",
6988
+ "format": "uuid",
6989
+ "description": "Legacy alias for oauth_grant_id"
6990
+ },
6991
+ "key_prefix": {
6992
+ "type": "string",
6993
+ "description": "Legacy display prefix derived from access_token"
6994
+ },
6995
+ "access_token": {
6996
+ "type": "string",
6997
+ "description": "OAuth access token for CLI API authentication"
6998
+ },
6999
+ "refresh_token": {
7000
+ "type": "string",
7001
+ "description": "OAuth refresh token used by the CLI to renew access"
7002
+ },
7003
+ "token_type": {
7004
+ "type": "string",
7005
+ "enum": ["Bearer"]
7006
+ },
7007
+ "expires_in": {
7008
+ "type": "integer",
7009
+ "description": "Seconds until access_token expires"
7010
+ },
7011
+ "auth_method": {
7012
+ "type": "string",
7013
+ "enum": ["oauth"]
7014
+ },
7015
+ "oauth_grant_id": {
7016
+ "type": "string",
7017
+ "format": "uuid"
7018
+ },
7019
+ "oauth_client_id": { "type": "string" },
7020
+ "org_id": {
7021
+ "type": "string",
7022
+ "format": "uuid"
7023
+ },
7024
+ "org_name": { "type": ["string", "null"] },
7025
+ "orgs": {
7026
+ "type": "array",
7027
+ "items": {
7028
+ "type": "object",
7029
+ "properties": {
7030
+ "id": {
7031
+ "type": "string",
7032
+ "format": "uuid"
7033
+ },
7034
+ "name": { "type": ["string", "null"] }
7035
+ },
7036
+ "required": ["id", "name"]
7037
+ },
7038
+ "description": "Workspaces available to the verified email. The minted session targets `org_id`."
7039
+ }
7040
+ },
7041
+ "required": [
7042
+ "api_key",
7043
+ "key_id",
7044
+ "key_prefix",
7045
+ "access_token",
7046
+ "refresh_token",
7047
+ "token_type",
7048
+ "expires_in",
7049
+ "auth_method",
7050
+ "oauth_grant_id",
7051
+ "oauth_client_id",
7052
+ "org_id",
7053
+ "org_name",
7054
+ "orgs"
7055
+ ]
7056
+ },
7057
+ "sdkName": "verifyAgentSignup",
7058
+ "summary": "Verify agent signup and create OAuth tokens",
7059
+ "tag": "Agent",
7060
+ "tagCommand": "agent"
7061
+ },
7062
+ {
7063
+ "binaryResponse": false,
7064
+ "bodyRequired": false,
7065
+ "command": "cli-logout",
7066
+ "description": "Revokes the OAuth grant used to authenticate the request. API-key\nauthenticated legacy logout requests succeed without deleting server API\nkeys so old local CLI state can be cleared safely.\n",
7067
+ "hasJsonBody": true,
7068
+ "method": "POST",
7069
+ "operationId": "cliLogout",
7070
+ "path": "/cli/logout",
7071
+ "pathParams": [],
7072
+ "queryParams": [],
7073
+ "requestSchema": {
7074
+ "type": "object",
7075
+ "additionalProperties": false,
7076
+ "properties": { "key_id": {
7077
+ "type": "string",
7078
+ "format": "uuid",
7079
+ "description": "Optional id guard; when provided it must match the authenticated OAuth grant id or API key id"
7080
+ } }
7081
+ },
7082
+ "responseSchema": {
7083
+ "type": "object",
7084
+ "properties": {
7085
+ "revoked": {
7086
+ "type": "boolean",
7087
+ "description": "True when an OAuth grant was revoked. False for API-key-authenticated legacy logout, which only clears local CLI state."
7088
+ },
7089
+ "key_id": {
7090
+ "type": "string",
7091
+ "format": "uuid",
7092
+ "description": "API key id for API-key-authenticated legacy logout"
7093
+ },
7094
+ "oauth_grant_id": {
7095
+ "type": "string",
7096
+ "format": "uuid",
7097
+ "description": "OAuth grant id revoked by OAuth-authenticated logout"
7098
+ }
7099
+ },
7100
+ "required": ["revoked"]
7101
+ },
7102
+ "sdkName": "cliLogout",
7103
+ "summary": "Revoke the current CLI OAuth session",
7104
+ "tag": "CLI",
7105
+ "tagCommand": "cli"
7106
+ },
7107
+ {
7108
+ "binaryResponse": false,
7109
+ "bodyRequired": true,
7110
+ "command": "poll-cli-login",
7111
+ "description": "Polls a CLI login session until the browser approval either succeeds,\nis denied, expires, or is polled too quickly. The OAuth token set is\ncreated only after approval and is returned exactly once.\n",
7112
+ "hasJsonBody": true,
7113
+ "method": "POST",
7114
+ "operationId": "pollCliLogin",
7115
+ "path": "/cli/login/poll",
7116
+ "pathParams": [],
7117
+ "queryParams": [],
7118
+ "requestSchema": {
7119
+ "type": "object",
7120
+ "additionalProperties": false,
7121
+ "properties": { "device_code": {
7122
+ "type": "string",
7123
+ "minLength": 1
7124
+ } },
7125
+ "required": ["device_code"]
7126
+ },
7127
+ "responseSchema": {
7128
+ "type": "object",
7129
+ "properties": {
7130
+ "api_key": {
7131
+ "type": "string",
7132
+ "description": "Legacy alias for access_token. New CLI builds should persist access_token and refresh_token."
7133
+ },
7134
+ "key_id": {
7135
+ "type": "string",
7136
+ "format": "uuid",
7137
+ "description": "Legacy alias for oauth_grant_id"
7138
+ },
7139
+ "key_prefix": {
7140
+ "type": "string",
7141
+ "description": "Legacy display prefix derived from access_token"
7142
+ },
7143
+ "access_token": {
7144
+ "type": "string",
7145
+ "description": "OAuth access token for CLI API authentication"
7146
+ },
7147
+ "refresh_token": {
7148
+ "type": "string",
7149
+ "description": "OAuth refresh token used by the CLI to renew access"
7150
+ },
7151
+ "token_type": {
7152
+ "type": "string",
7153
+ "enum": ["Bearer"]
7154
+ },
7155
+ "expires_in": {
7156
+ "type": "integer",
7157
+ "description": "Seconds until access_token expires"
7158
+ },
7159
+ "auth_method": {
7160
+ "type": "string",
7161
+ "enum": ["oauth"]
7162
+ },
7163
+ "oauth_grant_id": {
7164
+ "type": "string",
7165
+ "format": "uuid"
7166
+ },
7167
+ "oauth_client_id": { "type": "string" },
7168
+ "org_id": {
7169
+ "type": "string",
7170
+ "format": "uuid"
7171
+ },
7172
+ "org_name": { "type": ["string", "null"] }
7173
+ },
7174
+ "required": [
7175
+ "api_key",
7176
+ "key_id",
7177
+ "key_prefix",
7178
+ "access_token",
7179
+ "refresh_token",
7180
+ "token_type",
7181
+ "expires_in",
7182
+ "auth_method",
7183
+ "oauth_grant_id",
7184
+ "oauth_client_id",
7185
+ "org_id",
7186
+ "org_name"
7187
+ ]
7188
+ },
7189
+ "sdkName": "pollCliLogin",
7190
+ "summary": "Poll CLI browser login",
7191
+ "tag": "CLI",
7192
+ "tagCommand": "cli"
7193
+ },
7194
+ {
7195
+ "binaryResponse": false,
7196
+ "bodyRequired": true,
7197
+ "command": "resend-cli-signup-verification",
7198
+ "description": "Sends a new email verification code for a pending CLI signup session.\nThis endpoint does not require an API key.\n",
7199
+ "hasJsonBody": true,
7200
+ "method": "POST",
7201
+ "operationId": "resendCliSignupVerification",
7202
+ "path": "/cli/signup/resend",
7203
+ "pathParams": [],
7204
+ "queryParams": [],
7205
+ "requestSchema": {
7206
+ "type": "object",
7207
+ "additionalProperties": false,
7208
+ "properties": { "signup_token": {
7209
+ "type": "string",
7210
+ "minLength": 1
7211
+ } },
7212
+ "required": ["signup_token"]
7213
+ },
7214
+ "responseSchema": {
7215
+ "type": "object",
7216
+ "properties": {
7217
+ "email": {
7218
+ "type": "string",
7219
+ "format": "email"
7220
+ },
7221
+ "expires_in": {
7222
+ "type": "integer",
7223
+ "description": "Seconds until the pending signup expires"
7224
+ },
7225
+ "resend_after": {
7226
+ "type": "integer",
7227
+ "description": "Minimum seconds before requesting another verification email"
7228
+ },
7229
+ "verification_code_length": {
7230
+ "type": "integer",
7231
+ "description": "Number of digits in the emailed verification code"
7232
+ }
7233
+ },
7234
+ "required": [
7235
+ "email",
7236
+ "expires_in",
7237
+ "resend_after",
7238
+ "verification_code_length"
7239
+ ]
7240
+ },
7241
+ "sdkName": "resendCliSignupVerification",
7242
+ "summary": "Resend CLI signup verification code",
7243
+ "tag": "CLI",
7244
+ "tagCommand": "cli"
7245
+ },
7246
+ {
7247
+ "binaryResponse": false,
7248
+ "bodyRequired": false,
7249
+ "command": "start-cli-login",
7250
+ "description": "Starts a browser-assisted CLI login session. The response includes a\ndevice code for polling and a user code that the user approves in the\nbrowser. This endpoint does not require an API key.\n",
7251
+ "hasJsonBody": true,
7252
+ "method": "POST",
7253
+ "operationId": "startCliLogin",
7254
+ "path": "/cli/login/start",
7255
+ "pathParams": [],
7256
+ "queryParams": [],
7257
+ "requestSchema": {
7258
+ "type": "object",
7259
+ "additionalProperties": false,
7260
+ "properties": {
7261
+ "device_name": {
7262
+ "type": "string",
7263
+ "minLength": 1,
7264
+ "maxLength": 80,
7265
+ "description": "Human-readable device name shown during browser approval"
6531
7266
  },
6532
7267
  "metadata": {
6533
7268
  "type": "object",
@@ -6613,7 +7348,7 @@ const operationManifest = [
6613
7348
  "type": "string",
6614
7349
  "minLength": 1,
6615
7350
  "maxLength": 80,
6616
- "description": "Human-readable device name used for the created CLI API key"
7351
+ "description": "Human-readable device name used for the created CLI OAuth grant"
6617
7352
  },
6618
7353
  "metadata": {
6619
7354
  "type": "object",
@@ -6668,7 +7403,7 @@ const operationManifest = [
6668
7403
  "binaryResponse": false,
6669
7404
  "bodyRequired": true,
6670
7405
  "command": "verify-cli-signup",
6671
- "description": "Verifies the email code for a CLI signup session, creates the account,\nredeems the reserved signup code, mints an org-scoped CLI API key, and\nreturns the raw key exactly once. This endpoint does not require an API key.\n",
7406
+ "description": "Verifies the email code for a CLI signup session, creates the account,\nredeems the reserved signup code, creates an org-scoped OAuth CLI\nsession, and returns the token set exactly once. This endpoint does not\nrequire an API key.\n",
6672
7407
  "hasJsonBody": true,
6673
7408
  "method": "POST",
6674
7409
  "operationId": "verifyCliSignup",
@@ -6694,24 +7429,49 @@ const operationManifest = [
6694
7429
  "maxLength": 1024
6695
7430
  }
6696
7431
  },
6697
- "required": [
6698
- "signup_token",
6699
- "verification_code",
6700
- "password"
6701
- ]
7432
+ "required": ["signup_token", "verification_code"]
6702
7433
  },
6703
7434
  "responseSchema": {
6704
7435
  "type": "object",
6705
7436
  "properties": {
6706
7437
  "api_key": {
6707
7438
  "type": "string",
6708
- "description": "Newly-created API key for CLI authentication"
7439
+ "description": "Legacy alias for access_token. New CLI builds should persist access_token and refresh_token."
6709
7440
  },
6710
7441
  "key_id": {
7442
+ "type": "string",
7443
+ "format": "uuid",
7444
+ "description": "Legacy alias for oauth_grant_id"
7445
+ },
7446
+ "key_prefix": {
7447
+ "type": "string",
7448
+ "description": "Legacy display prefix derived from access_token"
7449
+ },
7450
+ "access_token": {
7451
+ "type": "string",
7452
+ "description": "OAuth access token for CLI API authentication"
7453
+ },
7454
+ "refresh_token": {
7455
+ "type": "string",
7456
+ "description": "OAuth refresh token used by the CLI to renew access"
7457
+ },
7458
+ "token_type": {
7459
+ "type": "string",
7460
+ "enum": ["Bearer"]
7461
+ },
7462
+ "expires_in": {
7463
+ "type": "integer",
7464
+ "description": "Seconds until access_token expires"
7465
+ },
7466
+ "auth_method": {
7467
+ "type": "string",
7468
+ "enum": ["oauth"]
7469
+ },
7470
+ "oauth_grant_id": {
6711
7471
  "type": "string",
6712
7472
  "format": "uuid"
6713
7473
  },
6714
- "key_prefix": { "type": "string" },
7474
+ "oauth_client_id": { "type": "string" },
6715
7475
  "org_id": {
6716
7476
  "type": "string",
6717
7477
  "format": "uuid"
@@ -6722,12 +7482,19 @@ const operationManifest = [
6722
7482
  "api_key",
6723
7483
  "key_id",
6724
7484
  "key_prefix",
7485
+ "access_token",
7486
+ "refresh_token",
7487
+ "token_type",
7488
+ "expires_in",
7489
+ "auth_method",
7490
+ "oauth_grant_id",
7491
+ "oauth_client_id",
6725
7492
  "org_id",
6726
7493
  "org_name"
6727
7494
  ]
6728
7495
  },
6729
7496
  "sdkName": "verifyCliSignup",
6730
- "summary": "Verify CLI signup and create API key",
7497
+ "summary": "Verify CLI signup and create OAuth session",
6731
7498
  "tag": "CLI",
6732
7499
  "tagCommand": "cli"
6733
7500
  },
@@ -10751,28 +11518,34 @@ function requireString(value, key) {
10751
11518
  return raw;
10752
11519
  }
10753
11520
  /**
10754
- * Sentinel returned by parseCredentials when the on-disk credentials
10755
- * were written by a pre-dual-host CLI version (i.e. they have
10756
- * `base_url` instead of `api_base_url_1`). The caller treats this as
10757
- * "no saved credentials" after auto-cleaning the stale file. Defined
10758
- * as a class-tagged error so loadCliCredentials can distinguish it
10759
- * from a genuine malformed-credentials error.
11521
+ * Sentinel returned by parseCredentials when the on-disk credentials were
11522
+ * written by an API-key-based CLI. The caller treats this as "not logged in"
11523
+ * after clearing the local file. The backing API key is intentionally not
11524
+ * revoked; API keys still work when passed explicitly via --api-key/env.
10760
11525
  */
10761
- var StaleCredentialFormatError = class extends Error {
11526
+ var LegacyApiKeyCredentialFormatError = class extends Error {
10762
11527
  constructor() {
10763
- super("stale_credential_format");
10764
- this.name = "StaleCredentialFormatError";
11528
+ super("legacy_api_key_credential_format");
11529
+ this.name = "LegacyApiKeyCredentialFormatError";
10765
11530
  }
10766
11531
  };
10767
11532
  function parseCredentials(raw) {
10768
11533
  if (!isRecord$2(raw)) throw new Error(`Stored Primitive CLI credentials are malformed: expected a JSON object. ${MALFORMED_CREDENTIALS_HINT}`);
10769
- if (typeof raw.api_base_url_1 !== "string" && typeof raw.base_url === "string") throw new StaleCredentialFormatError();
11534
+ if (raw.auth_method !== "oauth") {
11535
+ if (typeof raw.api_key === "string" || typeof raw.key_id === "string" || typeof raw.base_url === "string") throw new LegacyApiKeyCredentialFormatError();
11536
+ throw new Error(`Stored Primitive CLI credentials are malformed: auth_method must be oauth. ${MALFORMED_CREDENTIALS_HINT}`);
11537
+ }
10770
11538
  const orgName = raw.org_name;
10771
11539
  if (orgName !== null && typeof orgName !== "string") throw new Error(`Stored Primitive CLI credentials are malformed: org_name must be a string or null. ${MALFORMED_CREDENTIALS_HINT}`);
11540
+ if (requireString(raw, "token_type") !== "Bearer") throw new Error(`Stored Primitive CLI credentials are malformed: token_type must be Bearer. ${MALFORMED_CREDENTIALS_HINT}`);
10772
11541
  return {
10773
- api_key: requireString(raw, "api_key"),
10774
- key_id: requireString(raw, "key_id"),
10775
- key_prefix: requireString(raw, "key_prefix"),
11542
+ auth_method: "oauth",
11543
+ access_token: requireString(raw, "access_token"),
11544
+ refresh_token: requireString(raw, "refresh_token"),
11545
+ token_type: "Bearer",
11546
+ expires_at: requireString(raw, "expires_at"),
11547
+ oauth_grant_id: requireString(raw, "oauth_grant_id"),
11548
+ oauth_client_id: requireString(raw, "oauth_client_id"),
10776
11549
  org_id: requireString(raw, "org_id"),
10777
11550
  org_name: orgName,
10778
11551
  api_base_url_1: requireString(raw, "api_base_url_1"),
@@ -10793,6 +11566,9 @@ function normalizeApiBaseUrl1(url) {
10793
11566
  function normalizeApiBaseUrl2(url) {
10794
11567
  return normalize(url, DEFAULT_API_BASE_URL_2);
10795
11568
  }
11569
+ function cliAccessTokenExpiresAt(expiresInSeconds, now = Date.now) {
11570
+ return new Date(now() + expiresInSeconds * 1e3).toISOString();
11571
+ }
10796
11572
  function loadCliCredentials(configDir) {
10797
11573
  const path = credentialsPath(configDir);
10798
11574
  let contents;
@@ -10806,11 +11582,11 @@ function loadCliCredentials(configDir) {
10806
11582
  try {
10807
11583
  return parseCredentials(JSON.parse(contents));
10808
11584
  } catch (error) {
10809
- if (error instanceof StaleCredentialFormatError) {
11585
+ if (error instanceof LegacyApiKeyCredentialFormatError) {
10810
11586
  try {
10811
11587
  rmSync(path, { force: true });
10812
11588
  } catch {}
10813
- process.stderr.write("You've been logged out: your saved Primitive CLI credentials were created by an older CLI version and are no longer compatible. Run `primitive login` to re-authenticate.\n");
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");
10814
11590
  return null;
10815
11591
  }
10816
11592
  if (error instanceof SyntaxError) throw new Error("Stored Primitive CLI credentials are not valid JSON. Run `primitive logout` and then `primitive login`.");
@@ -10895,7 +11671,7 @@ function resolveCliAuth(params) {
10895
11671
  };
10896
11672
  const credentials = loadCliCredentials(params.configDir);
10897
11673
  if (credentials) return {
10898
- apiKey: credentials.api_key,
11674
+ apiKey: credentials.access_token,
10899
11675
  apiBaseUrl1: params.apiBaseUrl1 ? normalizeApiBaseUrl1(params.apiBaseUrl1) : credentials.api_base_url_1,
10900
11676
  apiBaseUrl2,
10901
11677
  credentials,
@@ -10933,7 +11709,7 @@ function validateCliHeaderName(name) {
10933
11709
  const trimmed = name.trim();
10934
11710
  if (!trimmed) throw new Errors.CLIError("Header name must be a non-empty string.", { exit: 1 });
10935
11711
  if (!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(trimmed)) throw new Errors.CLIError(`Invalid header name: ${name}`, { exit: 1 });
10936
- if (trimmed.toLowerCase() === "authorization") throw new Errors.CLIError("The Authorization header is managed by PRIMITIVE_API_KEY or saved CLI credentials.", { exit: 1 });
11712
+ if (trimmed.toLowerCase() === "authorization") throw new Errors.CLIError("The Authorization header is managed by PRIMITIVE_API_KEY or saved OAuth CLI credentials.", { exit: 1 });
10937
11713
  return trimmed;
10938
11714
  }
10939
11715
  function validateCliHeaderValue(value, name) {
@@ -11095,6 +11871,8 @@ function redactCliEnvironment(environment) {
11095
11871
  //#endregion
11096
11872
  //#region src/oclif/api-client.ts
11097
11873
  const API_HEADERS_ENV = "PRIMITIVE_API_HEADERS";
11874
+ 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.";
11098
11876
  function mergeHeaders(...headers) {
11099
11877
  const merged = {};
11100
11878
  for (const headerSet of headers) {
@@ -11153,14 +11931,115 @@ function createCliApiClient(params) {
11153
11931
  requestConfig
11154
11932
  };
11155
11933
  }
11156
- function createAuthenticatedCliApiClient(params) {
11934
+ function oauthTokenEndpoint(apiBaseUrl1) {
11935
+ const url = new URL(apiBaseUrl1);
11936
+ url.pathname = "/oauth/token";
11937
+ url.search = "";
11938
+ url.hash = "";
11939
+ return url.toString();
11940
+ }
11941
+ function shouldRefresh(credentials, now) {
11942
+ const expiresAt = Date.parse(credentials.expires_at);
11943
+ if (!Number.isFinite(expiresAt)) return true;
11944
+ return expiresAt <= now() + OAUTH_REFRESH_SKEW_MS;
11945
+ }
11946
+ function isOAuthRefreshSuccess(value) {
11947
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
11948
+ const row = value;
11949
+ return typeof row.access_token === "string" && typeof row.refresh_token === "string" && row.token_type === "Bearer" && typeof row.expires_in === "number" && Number.isFinite(row.expires_in) && row.expires_in > 0;
11950
+ }
11951
+ function oauthErrorCode(value) {
11952
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
11953
+ const raw = value.error;
11954
+ return typeof raw === "string" ? raw : null;
11955
+ }
11956
+ function oauthErrorDescription(value) {
11957
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
11958
+ const raw = value.error_description;
11959
+ return typeof raw === "string" && raw.trim() ? raw : null;
11960
+ }
11961
+ async function refreshStoredCliCredentials(params) {
11962
+ const now = params.now ?? Date.now;
11963
+ if (!shouldRefresh(params.credentials, now)) return params.credentials;
11964
+ let releaseLock;
11965
+ try {
11966
+ if (!params.credentialsLockHeld) try {
11967
+ releaseLock = acquireCliCredentialsLock(params.configDir);
11968
+ } catch (error) {
11969
+ const detail = error instanceof Error ? error.message : String(error);
11970
+ throw new Errors.CLIError(detail, { exit: 1 });
11971
+ }
11972
+ 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 });
11974
+ if (!shouldRefresh(current, now)) return current;
11975
+ const fetchImpl = params.fetch ?? fetch;
11976
+ const body = new URLSearchParams({
11977
+ client_id: current.oauth_client_id,
11978
+ grant_type: "refresh_token",
11979
+ refresh_token: current.refresh_token
11980
+ });
11981
+ let response;
11982
+ try {
11983
+ response = await fetchImpl(oauthTokenEndpoint(params.apiBaseUrl1), {
11984
+ body,
11985
+ headers: {
11986
+ ...params.headers ?? {},
11987
+ "content-type": "application/x-www-form-urlencoded"
11988
+ },
11989
+ method: "POST"
11990
+ });
11991
+ } catch (error) {
11992
+ const detail = error instanceof Error ? error.message : String(error);
11993
+ throw new Errors.CLIError(`Could not refresh saved Primitive CLI OAuth credentials: ${detail}`, { exit: 1 });
11994
+ }
11995
+ const payload = await response.json().catch(() => null);
11996
+ if (!response.ok) {
11997
+ const code = oauthErrorCode(payload);
11998
+ const description = oauthErrorDescription(payload);
11999
+ if (code === "invalid_grant") {
12000
+ deleteCliCredentials(params.configDir);
12001
+ throw new Errors.CLIError(SAVED_CLI_OAUTH_SESSION_EXPIRED_MESSAGE, { exit: 1 });
12002
+ }
12003
+ throw new Errors.CLIError(`Could not refresh saved Primitive CLI OAuth credentials${description ? `: ${description}` : "."}`, { exit: 1 });
12004
+ }
12005
+ if (!isOAuthRefreshSuccess(payload)) throw new Errors.CLIError("Primitive OAuth token endpoint returned an unexpected refresh response.", { exit: 1 });
12006
+ const next = {
12007
+ ...current,
12008
+ access_token: payload.access_token,
12009
+ expires_at: cliAccessTokenExpiresAt(payload.expires_in, now),
12010
+ refresh_token: payload.refresh_token,
12011
+ token_type: payload.token_type
12012
+ };
12013
+ saveCliCredentials(params.configDir, next);
12014
+ return next;
12015
+ } finally {
12016
+ releaseLock?.();
12017
+ }
12018
+ }
12019
+ async function createAuthenticatedCliApiClient(params) {
11157
12020
  const requestConfig = resolveCliApiRequestConfig(params);
11158
- const auth = resolveCliAuth({
12021
+ let auth = resolveCliAuth({
11159
12022
  apiKey: params.apiKey,
11160
12023
  apiBaseUrl1: requestConfig.apiBaseUrl1,
11161
12024
  apiBaseUrl2: requestConfig.apiBaseUrl2,
11162
12025
  configDir: params.configDir
11163
12026
  });
12027
+ if (auth.source === "stored" && auth.credentials) {
12028
+ const refreshed = await refreshStoredCliCredentials({
12029
+ apiBaseUrl1: auth.apiBaseUrl1,
12030
+ configDir: params.configDir,
12031
+ credentials: auth.credentials,
12032
+ credentialsLockHeld: params.credentialsLockHeld,
12033
+ fetch: params.fetch,
12034
+ headers: requestConfig.headers,
12035
+ now: params.now
12036
+ });
12037
+ auth = {
12038
+ ...auth,
12039
+ apiKey: refreshed.access_token,
12040
+ credentials: refreshed
12041
+ };
12042
+ }
11164
12043
  return {
11165
12044
  apiClient: new PrimitiveApiClient({
11166
12045
  apiKey: auth.apiKey,
@@ -11476,7 +12355,7 @@ function extractErrorCode(payload) {
11476
12355
  if (typeof direct === "string") return direct;
11477
12356
  }
11478
12357
  }
11479
- 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 a key is live." };
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." };
11480
12359
  const NETWORK_ERROR_HINTS = {
11481
12360
  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.",
11482
12361
  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.",
@@ -11514,7 +12393,7 @@ function surfaceUnauthorizedHint(params) {
11514
12393
  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");
11515
12394
  return;
11516
12395
  }
11517
- process.stderr.write("Your saved Primitive CLI credential 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 credential.\n");
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");
11518
12397
  }
11519
12398
  function formatElapsed(ms) {
11520
12399
  const seconds = ms / 1e3;
@@ -11564,7 +12443,7 @@ function bodyFieldFlag(field) {
11564
12443
  function buildFlags(operation) {
11565
12444
  const flags = {
11566
12445
  "api-key": Flags.string({
11567
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
12446
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
11568
12447
  env: "PRIMITIVE_API_KEY"
11569
12448
  }),
11570
12449
  "api-base-url-1": Flags.string({
@@ -11642,7 +12521,7 @@ function createOperationCommand(operation) {
11642
12521
  const { flags } = await this.parse(OperationCommand);
11643
12522
  const parsedFlags = flags;
11644
12523
  await runWithTiming(parsedFlags.time === true, async () => {
11645
- const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
12524
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
11646
12525
  apiKey: typeof parsedFlags["api-key"] === "string" ? parsedFlags["api-key"] : void 0,
11647
12526
  apiBaseUrl1: typeof parsedFlags["api-base-url-1"] === "string" ? parsedFlags["api-base-url-1"] : void 0,
11648
12527
  apiBaseUrl2: typeof parsedFlags["api-base-url-2"] === "string" ? parsedFlags["api-base-url-2"] : void 0,
@@ -11765,7 +12644,7 @@ async function pickDefaultFromAddress(apiClient, authFailureContext) {
11765
12644
  ...authFailureContext,
11766
12645
  payload: errorPayload
11767
12646
  });
11768
- throw new Errors.CLIError("Cannot send: API key is missing or invalid (see hint above).", { exit: 1 });
12647
+ throw new Errors.CLIError("Cannot send: CLI auth is missing or invalid (see hint above).", { exit: 1 });
11769
12648
  }
11770
12649
  throw new Errors.CLIError(`Could not look up your verified domains to default --from. Pass --from explicitly. Underlying error: ${formatErrorPayload(errorPayload)}`);
11771
12650
  }
@@ -11964,7 +12843,7 @@ var ChatCommand = class ChatCommand extends Command {
11964
12843
  const message = args.message !== void 0 && args.message !== "" ? args.message : await readStdinToString();
11965
12844
  if (!message.trim()) throw cliError$5("Message body is empty.");
11966
12845
  await runWithTiming(flags.time, async () => {
11967
- const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
12846
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
11968
12847
  apiKey: flags["api-key"],
11969
12848
  apiBaseUrl1: flags["api-base-url-1"],
11970
12849
  apiBaseUrl2: flags["api-base-url-2"],
@@ -12317,13 +13196,18 @@ function checkApiKey(opts) {
12317
13196
  } catch (error) {
12318
13197
  parseError = error instanceof Error ? error.message : String(error);
12319
13198
  }
12320
- if (parsed?.api_key) return {
13199
+ if (parsed?.auth_method === "oauth" && typeof parsed.access_token === "string" && parsed.access_token.length > 0) return {
12321
13200
  status: "ok",
12322
- message: `loaded from ${credsPath}`
13201
+ message: `loaded OAuth session from ${credsPath}`
13202
+ };
13203
+ if (typeof parsed?.api_key === "string" && parsed.api_key.length > 0) return {
13204
+ status: "fail",
13205
+ 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."
12323
13207
  };
12324
13208
  if (parsed) return {
12325
13209
  status: "fail",
12326
- message: `${credsPath} exists but contains no api_key`,
13210
+ message: `${credsPath} exists but contains no OAuth access_token`,
12327
13211
  hint: "Run `primitive logout` to clear it, then `primitive login` to recreate."
12328
13212
  };
12329
13213
  return {
@@ -12334,7 +13218,7 @@ function checkApiKey(opts) {
12334
13218
  }
12335
13219
  return {
12336
13220
  status: "fail",
12337
- message: "no API key found",
13221
+ message: "no CLI OAuth session or explicit API key found",
12338
13222
  hint: "Run `primitive login`, pass --api-key explicitly, or export PRIMITIVE_API_KEY=prim_..."
12339
13223
  };
12340
13224
  }
@@ -12417,12 +13301,12 @@ async function checkDomains(client) {
12417
13301
  }
12418
13302
  }
12419
13303
  var DoctorCommand = class DoctorCommand extends Command {
12420
- static description = `Run a one-shot environment health check: Node version, proxy env, API key resolution, /account reachability, and verified-domain status. Fails fast on anything that would block other commands and prints actionable hints for each warning or failure.`;
13304
+ static description = `Run a one-shot environment health check: Node version, proxy env, CLI auth resolution, /account reachability, and verified-domain status. Fails fast on anything that would block other commands and prints actionable hints for each warning or failure.`;
12421
13305
  static summary = "Check the local environment and live API for common problems";
12422
13306
  static examples = ["<%= config.bin %> doctor", "<%= config.bin %> doctor --api-key prim_..."];
12423
13307
  static flags = {
12424
13308
  "api-key": Flags.string({
12425
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
13309
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
12426
13310
  env: "PRIMITIVE_API_KEY"
12427
13311
  }),
12428
13312
  "api-base-url-1": Flags.string({
@@ -12452,11 +13336,11 @@ var DoctorCommand = class DoctorCommand extends Command {
12452
13336
  configDir: this.config.configDir
12453
13337
  });
12454
13338
  rows.push({
12455
- label: "API key",
13339
+ label: "Auth",
12456
13340
  outcome: apiKeyCheck
12457
13341
  });
12458
13342
  if (apiKeyCheck.status !== "fail") {
12459
- const { apiClient, auth } = createAuthenticatedCliApiClient({
13343
+ const { apiClient, auth } = await createAuthenticatedCliApiClient({
12460
13344
  apiKey: flags["api-key"],
12461
13345
  apiBaseUrl1: flags["api-base-url-1"],
12462
13346
  apiBaseUrl2: flags["api-base-url-2"],
@@ -12538,7 +13422,7 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
12538
13422
  ];
12539
13423
  static flags = {
12540
13424
  "api-key": Flags.string({
12541
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
13425
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
12542
13426
  env: "PRIMITIVE_API_KEY"
12543
13427
  }),
12544
13428
  "api-base-url-1": Flags.string({
@@ -12563,7 +13447,7 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
12563
13447
  async run() {
12564
13448
  const { flags } = await this.parse(EmailsLatestCommand);
12565
13449
  await runWithTiming(flags.time, async () => {
12566
- const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
13450
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
12567
13451
  apiKey: flags["api-key"],
12568
13452
  apiBaseUrl1: flags["api-base-url-1"],
12569
13453
  apiBaseUrl2: flags["api-base-url-2"],
@@ -12618,7 +13502,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
12618
13502
  ];
12619
13503
  static flags = {
12620
13504
  "api-key": Flags.string({
12621
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
13505
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
12622
13506
  env: "PRIMITIVE_API_KEY"
12623
13507
  }),
12624
13508
  "api-base-url-1": Flags.string({
@@ -12670,7 +13554,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
12670
13554
  };
12671
13555
  async run() {
12672
13556
  const { flags } = await this.parse(EmailsWaitCommand);
12673
- const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
13557
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
12674
13558
  apiKey: flags["api-key"],
12675
13559
  apiBaseUrl1: flags["api-base-url-1"],
12676
13560
  apiBaseUrl2: flags["api-base-url-2"],
@@ -12744,7 +13628,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
12744
13628
  ];
12745
13629
  static flags = {
12746
13630
  "api-key": Flags.string({
12747
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
13631
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
12748
13632
  env: "PRIMITIVE_API_KEY"
12749
13633
  }),
12750
13634
  "api-base-url-1": Flags.string({
@@ -12792,7 +13676,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
12792
13676
  };
12793
13677
  async run() {
12794
13678
  const { flags } = await this.parse(EmailsWatchCommand);
12795
- const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
13679
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
12796
13680
  apiKey: flags["api-key"],
12797
13681
  apiBaseUrl1: flags["api-base-url-1"],
12798
13682
  apiBaseUrl2: flags["api-base-url-2"],
@@ -13410,7 +14294,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
13410
14294
  ];
13411
14295
  static flags = {
13412
14296
  "api-key": Flags.string({
13413
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
14297
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
13414
14298
  env: "PRIMITIVE_API_KEY"
13415
14299
  }),
13416
14300
  "api-base-url-1": Flags.string({
@@ -13487,7 +14371,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
13487
14371
  const code = readTextFileFlag(flags.file, "--file");
13488
14372
  const sourceMap = flags["source-map-file"] ? readTextFileFlag(flags["source-map-file"], "--source-map-file") : void 0;
13489
14373
  emitRawSendMailFetchWarning(code, (chunk) => process.stderr.write(chunk));
13490
- const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
14374
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
13491
14375
  apiKey: flags["api-key"],
13492
14376
  apiBaseUrl1: flags["api-base-url-1"],
13493
14377
  apiBaseUrl2: flags["api-base-url-2"],
@@ -14073,7 +14957,7 @@ var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
14073
14957
  ];
14074
14958
  static flags = {
14075
14959
  "api-key": Flags.string({
14076
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
14960
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
14077
14961
  env: "PRIMITIVE_API_KEY"
14078
14962
  }),
14079
14963
  "api-base-url-1": Flags.string({
@@ -14112,7 +14996,7 @@ var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
14112
14996
  if (flags["poll-interval"] <= 0) this.error("--poll-interval must be greater than 0.", { exit: 2 });
14113
14997
  if (flags.follow && flags.cursor) this.error("--cursor cannot be combined with --follow.", { exit: 2 });
14114
14998
  await runWithTiming(flags.time, async () => {
14115
- const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
14999
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
14116
15000
  apiKey: flags["api-key"],
14117
15001
  apiBaseUrl1: flags["api-base-url-1"],
14118
15002
  apiBaseUrl2: flags["api-base-url-2"],
@@ -14271,7 +15155,7 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
14271
15155
  ];
14272
15156
  static flags = {
14273
15157
  "api-key": Flags.string({
14274
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
15158
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
14275
15159
  env: "PRIMITIVE_API_KEY"
14276
15160
  }),
14277
15161
  "api-base-url-1": Flags.string({
@@ -14348,7 +15232,7 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
14348
15232
  const code = readTextFileFlag(flags.file, "--file");
14349
15233
  const sourceMap = flags["source-map-file"] ? readTextFileFlag(flags["source-map-file"], "--source-map-file") : void 0;
14350
15234
  emitRawSendMailFetchWarning(code, (chunk) => process.stderr.write(chunk));
14351
- const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
15235
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
14352
15236
  apiKey: flags["api-key"],
14353
15237
  apiBaseUrl1: flags["api-base-url-1"],
14354
15238
  apiBaseUrl2: flags["api-base-url-2"],
@@ -14531,7 +15415,7 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
14531
15415
  ];
14532
15416
  static flags = {
14533
15417
  "api-key": Flags.string({
14534
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
15418
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
14535
15419
  env: "PRIMITIVE_API_KEY"
14536
15420
  }),
14537
15421
  "api-base-url-1": Flags.string({
@@ -14563,7 +15447,7 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
14563
15447
  async run() {
14564
15448
  const { flags } = await this.parse(FunctionsSetSecretCommand);
14565
15449
  await runWithTiming(flags.time, async () => {
14566
- const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
15450
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
14567
15451
  apiKey: flags["api-key"],
14568
15452
  apiBaseUrl1: flags["api-base-url-1"],
14569
15453
  apiBaseUrl2: flags["api-base-url-2"],
@@ -14754,7 +15638,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
14754
15638
  ];
14755
15639
  static flags = {
14756
15640
  "api-key": Flags.string({
14757
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
15641
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
14758
15642
  env: "PRIMITIVE_API_KEY"
14759
15643
  }),
14760
15644
  "api-base-url-1": Flags.string({
@@ -14790,7 +15674,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
14790
15674
  const { flags } = await this.parse(FunctionsTestFunctionCommand);
14791
15675
  const shouldWait = flags.wait || flags["show-sends"];
14792
15676
  const shouldShowSends = flags["show-sends"];
14793
- const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
15677
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
14794
15678
  apiKey: flags["api-key"],
14795
15679
  apiBaseUrl1: flags["api-base-url-1"],
14796
15680
  apiBaseUrl2: flags["api-base-url-2"],
@@ -14940,8 +15824,25 @@ async function checkExistingLogin(params) {
14940
15824
  configDir: params.configDir
14941
15825
  });
14942
15826
  const probeApiBaseUrl1 = requestConfig.apiBaseUrl1 ?? params.credentials.api_base_url_1;
15827
+ let credentials = params.credentials;
15828
+ try {
15829
+ credentials = await refreshStoredCliCredentials({
15830
+ apiBaseUrl1: probeApiBaseUrl1,
15831
+ configDir: params.configDir,
15832
+ credentials,
15833
+ credentialsLockHeld: params.credentialsLockHeld,
15834
+ headers: requestConfig.headers
15835
+ });
15836
+ } catch (error) {
15837
+ if (loadCliCredentials(params.configDir) === null) return { status: "removed_stale" };
15838
+ return {
15839
+ status: "blocked",
15840
+ payload: error,
15841
+ message: "A saved Primitive CLI OAuth session exists, but the CLI could not refresh it. Run `primitive logout` before logging in again."
15842
+ };
15843
+ }
14943
15844
  const apiClient = new PrimitiveApiClient({
14944
- apiKey: params.credentials.api_key,
15845
+ apiKey: credentials.access_token,
14945
15846
  apiBaseUrl1: probeApiBaseUrl1,
14946
15847
  apiBaseUrl2: requestConfig.resolvedApiBaseUrl2,
14947
15848
  headers: requestConfig.headers
@@ -14956,17 +15857,17 @@ async function checkExistingLogin(params) {
14956
15857
  const baseUrlDiffersFromSaved = requestConfig.baseUrlOverridden && requestConfig.apiBaseUrl1 !== params.credentials.api_base_url_1;
14957
15858
  if (code === API_ERROR_CODES.unauthorized && !baseUrlDiffersFromSaved) {
14958
15859
  deleteCliCredentials(params.configDir);
14959
- process.stderr.write("Removed saved Primitive CLI credentials because the existing key was rejected during login. Continuing with a fresh login.\n");
15860
+ process.stderr.write("Removed saved Primitive CLI OAuth credentials because the existing session was rejected during login. Continuing with a fresh login.\n");
14960
15861
  return { status: "removed_stale" };
14961
15862
  }
14962
15863
  return {
14963
15864
  status: "blocked",
14964
15865
  payload,
14965
- message: code === API_ERROR_CODES.unauthorized ? "Saved Primitive CLI 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 login exists, but the CLI could not verify whether it is still valid. Run `primitive logout` before logging in again."
15866
+ 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."
14966
15867
  };
14967
15868
  }
14968
15869
  var LoginCommand = class LoginCommand extends Command {
14969
- static description = "Log in by opening Primitive in your browser and saving an org-scoped CLI API key locally.";
15870
+ static description = "Log in by opening Primitive in your browser and saving an org-scoped OAuth session locally.";
14970
15871
  static summary = "Log in with browser approval";
14971
15872
  static examples = [
14972
15873
  "<%= config.bin %> login",
@@ -15020,7 +15921,8 @@ var LoginCommand = class LoginCommand extends Command {
15020
15921
  const existingStatus = await checkExistingLogin({
15021
15922
  apiBaseUrl1: flags["api-base-url-1"],
15022
15923
  configDir: this.config.configDir,
15023
- credentials: existing
15924
+ credentials: existing,
15925
+ credentialsLockHeld: true
15024
15926
  });
15025
15927
  if (existingStatus.status === "removed_stale") process.stderr.write("Continuing with a new Primitive CLI login...\n");
15026
15928
  else if (existingStatus.status === "blocked") {
@@ -15061,13 +15963,17 @@ var LoginCommand = class LoginCommand extends Command {
15061
15963
  const login = unwrapData$2(polled.data);
15062
15964
  if (!login) throw cliError$2("Primitive API returned an empty CLI poll response.");
15063
15965
  saveCliCredentials(this.config.configDir, {
15064
- api_key: login.api_key,
15966
+ access_token: login.access_token,
15065
15967
  api_base_url_1: apiBaseUrl1,
15968
+ auth_method: "oauth",
15066
15969
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
15067
- key_id: login.key_id,
15068
- key_prefix: login.key_prefix,
15970
+ expires_at: cliAccessTokenExpiresAt(login.expires_in),
15971
+ oauth_client_id: login.oauth_client_id,
15972
+ oauth_grant_id: login.oauth_grant_id,
15069
15973
  org_id: login.org_id,
15070
- org_name: login.org_name
15974
+ org_name: login.org_name,
15975
+ refresh_token: login.refresh_token,
15976
+ token_type: login.token_type
15071
15977
  });
15072
15978
  const org = login.org_name ? ` (${login.org_name})` : "";
15073
15979
  process.stderr.write(`Logged in to org ${login.org_id}${org}.\n`);
@@ -15089,22 +15995,80 @@ var LoginCommand = class LoginCommand extends Command {
15089
15995
  if (code === API_ERROR_CODES.expiredToken) throw cliError$2("Primitive CLI login expired. Run `primitive login` again.");
15090
15996
  if (code === API_ERROR_CODES.invalidDeviceCode) throw cliError$2("Primitive CLI login device code is invalid. Run `primitive login` again.");
15091
15997
  writeErrorWithHints(payload);
15092
- throw cliError$2("Primitive CLI login failed while polling for approval.");
15998
+ throw cliError$2("Primitive CLI login failed while polling for approval.");
15999
+ }
16000
+ throw cliError$2("Primitive CLI login expired. Run `primitive login` again.");
16001
+ }
16002
+ };
16003
+ //#endregion
16004
+ //#region src/oclif/commands/logout.ts
16005
+ function cliError$1(message) {
16006
+ return new Errors.CLIError(message, { exit: 1 });
16007
+ }
16008
+ function unwrapData$1(value) {
16009
+ return value?.data ?? null;
16010
+ }
16011
+ 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.";
16013
+ }
16014
+ async function runLogoutWithCredentialLock(params) {
16015
+ const deps = {
16016
+ cliLogout,
16017
+ createAuthenticatedCliApiClient,
16018
+ ...params.deps
16019
+ };
16020
+ let credentials;
16021
+ try {
16022
+ credentials = loadCliCredentials(params.configDir);
16023
+ } catch (error) {
16024
+ deleteCliCredentials(params.configDir);
16025
+ const detail = error instanceof Error ? error.message : String(error);
16026
+ process.stderr.write(`Removed unreadable Primitive CLI credentials. Backing OAuth grant was not revoked: ${detail}\n`);
16027
+ process.exitCode = 1;
16028
+ return;
16029
+ }
16030
+ if (!credentials) throw cliError$1("Not logged in. Run `primitive login` to create saved CLI credentials.");
16031
+ let authenticated;
16032
+ try {
16033
+ authenticated = await deps.createAuthenticatedCliApiClient({
16034
+ apiBaseUrl1: params.flags["api-base-url-1"],
16035
+ configDir: params.configDir,
16036
+ credentialsLockHeld: true
16037
+ });
16038
+ } catch (error) {
16039
+ if (isSavedOAuthSessionExpiredError(error) && loadCliCredentials(params.configDir) === null) {
16040
+ process.stderr.write("Logged out (OAuth session was already expired or revoked on the server).\n");
16041
+ return;
16042
+ }
16043
+ throw error;
16044
+ }
16045
+ const freshCredentials = authenticated.auth.credentials ?? credentials;
16046
+ const result = await deps.cliLogout({
16047
+ body: { key_id: freshCredentials.oauth_grant_id },
16048
+ client: authenticated.apiClient.client,
16049
+ responseStyle: "fields"
16050
+ });
16051
+ if (result.error) {
16052
+ const payload = extractErrorPayload(result.error);
16053
+ const code = extractErrorCode(payload);
16054
+ if (code === API_ERROR_CODES.unauthorized || code === API_ERROR_CODES.notFound) {
16055
+ deleteCliCredentials(params.configDir);
16056
+ writeErrorWithHints(payload);
16057
+ process.stderr.write("Removed saved Primitive CLI credentials because the backing OAuth grant is already unavailable.\n");
16058
+ process.exitCode = 1;
16059
+ return;
15093
16060
  }
15094
- throw cliError$2("Primitive CLI login expired. Run `primitive login` again.");
16061
+ writeErrorWithHints(payload);
16062
+ throw cliError$1("Could not revoke the saved Primitive CLI OAuth grant.");
15095
16063
  }
15096
- };
15097
- //#endregion
15098
- //#region src/oclif/commands/logout.ts
15099
- function cliError$1(message) {
15100
- return new Errors.CLIError(message, { exit: 1 });
15101
- }
15102
- function unwrapData$1(value) {
15103
- return value?.data ?? null;
16064
+ const logout = unwrapData$1(result.data);
16065
+ deleteCliCredentials(params.configDir);
16066
+ const grantId = logout?.oauth_grant_id ?? freshCredentials.oauth_grant_id;
16067
+ process.stderr.write(`Logged out and revoked OAuth grant ${grantId}.\n`);
15104
16068
  }
15105
16069
  var LogoutCommand = class LogoutCommand extends Command {
15106
- static description = "Log out by revoking the saved Primitive CLI API key and deleting local credentials.";
15107
- static summary = "Log out and revoke the saved CLI key";
16070
+ static description = "Log out by revoking the saved Primitive CLI OAuth grant and deleting local credentials.";
16071
+ static summary = "Log out and revoke the saved CLI OAuth grant";
15108
16072
  static examples = ["<%= config.bin %> logout"];
15109
16073
  static flags = { "api-base-url-1": Flags.string({
15110
16074
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
@@ -15120,56 +16084,14 @@ var LogoutCommand = class LogoutCommand extends Command {
15120
16084
  throw cliError$1(error instanceof Error ? error.message : String(error));
15121
16085
  }
15122
16086
  try {
15123
- await this.runWithCredentialLock(flags);
16087
+ await runLogoutWithCredentialLock({
16088
+ configDir: this.config.configDir,
16089
+ flags
16090
+ });
15124
16091
  } finally {
15125
16092
  releaseCredentialsLock();
15126
16093
  }
15127
16094
  }
15128
- async runWithCredentialLock(flags) {
15129
- let credentials;
15130
- try {
15131
- credentials = loadCliCredentials(this.config.configDir);
15132
- } catch (error) {
15133
- deleteCliCredentials(this.config.configDir);
15134
- const detail = error instanceof Error ? error.message : String(error);
15135
- process.stderr.write(`Removed unreadable Primitive CLI credentials. Backing API key was not revoked: ${detail}\n`);
15136
- process.exitCode = 1;
15137
- return;
15138
- }
15139
- if (!credentials) throw cliError$1("Not logged in. Run `primitive login` to create saved CLI credentials.");
15140
- const requestConfig = resolveCliApiRequestConfig({
15141
- apiBaseUrl1: flags["api-base-url-1"],
15142
- configDir: this.config.configDir
15143
- });
15144
- const apiBaseUrl1 = requestConfig.apiBaseUrl1 ? normalizeApiBaseUrl1(requestConfig.apiBaseUrl1) : credentials.api_base_url_1;
15145
- const apiClient = new PrimitiveApiClient({
15146
- apiKey: credentials.api_key,
15147
- apiBaseUrl1,
15148
- headers: requestConfig.headers
15149
- });
15150
- const result = await cliLogout({
15151
- body: { key_id: credentials.key_id },
15152
- client: apiClient.client,
15153
- responseStyle: "fields"
15154
- });
15155
- if (result.error) {
15156
- const payload = extractErrorPayload(result.error);
15157
- const code = extractErrorCode(payload);
15158
- if (code === API_ERROR_CODES.unauthorized || code === API_ERROR_CODES.notFound) {
15159
- deleteCliCredentials(this.config.configDir);
15160
- writeErrorWithHints(payload);
15161
- process.stderr.write("Removed saved Primitive CLI credentials because the backing API key is already unavailable.\n");
15162
- process.exitCode = 1;
15163
- return;
15164
- }
15165
- writeErrorWithHints(payload);
15166
- throw cliError$1("Could not revoke the saved Primitive CLI API key.");
15167
- }
15168
- const logout = unwrapData$1(result.data);
15169
- deleteCliCredentials(this.config.configDir);
15170
- const keyId = logout?.key_id ?? credentials.key_id;
15171
- process.stderr.write(`Logged out and revoked API key ${keyId}.\n`);
15172
- }
15173
16095
  };
15174
16096
  //#endregion
15175
16097
  //#region src/oclif/message-body-sources.ts
@@ -15286,7 +16208,7 @@ var ReplyCommand = class ReplyCommand extends Command {
15286
16208
  ];
15287
16209
  static flags = {
15288
16210
  "api-key": Flags.string({
15289
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
16211
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
15290
16212
  env: "PRIMITIVE_API_KEY"
15291
16213
  }),
15292
16214
  "api-base-url-1": Flags.string({
@@ -15326,7 +16248,7 @@ var ReplyCommand = class ReplyCommand extends Command {
15326
16248
  });
15327
16249
  if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
15328
16250
  await runWithTiming(flags.time, async () => {
15329
- const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
16251
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
15330
16252
  apiKey: flags["api-key"],
15331
16253
  apiBaseUrl1: flags["api-base-url-1"],
15332
16254
  apiBaseUrl2: flags["api-base-url-2"],
@@ -15385,7 +16307,7 @@ var SendCommand = class SendCommand extends Command {
15385
16307
  ];
15386
16308
  static flags = {
15387
16309
  "api-key": Flags.string({
15388
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
16310
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
15389
16311
  env: "PRIMITIVE_API_KEY"
15390
16312
  }),
15391
16313
  "api-base-url-1": Flags.string({
@@ -15427,7 +16349,7 @@ var SendCommand = class SendCommand extends Command {
15427
16349
  });
15428
16350
  if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
15429
16351
  await runWithTiming(flags.time, async () => {
15430
- const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
16352
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
15431
16353
  apiKey: flags["api-key"],
15432
16354
  apiBaseUrl1: flags["api-base-url-1"],
15433
16355
  apiBaseUrl2: flags["api-base-url-2"],
@@ -15475,7 +16397,6 @@ var SendCommand = class SendCommand extends Command {
15475
16397
  //#endregion
15476
16398
  //#region src/oclif/commands/signup.ts
15477
16399
  const INVALID_VERIFICATION_CODE = "invalid_verification_code";
15478
- const CLERK_PASSWORD_REJECTED = "clerk_password_rejected";
15479
16400
  const EXPIRED_TOKEN = "expired_token";
15480
16401
  const INVALID_SIGNUP_TOKEN = "invalid_signup_token";
15481
16402
  const SLOW_DOWN = "slow_down";
@@ -15489,6 +16410,9 @@ function unwrapData(value) {
15489
16410
  function isRecord(value) {
15490
16411
  return value !== null && typeof value === "object" && !Array.isArray(value);
15491
16412
  }
16413
+ function normalizeEmail(email) {
16414
+ return email.trim().toLowerCase();
16415
+ }
15492
16416
  function pendingSignupFromJson(value) {
15493
16417
  if (!isRecord(value)) return null;
15494
16418
  if (typeof value.signup_token !== "string" || typeof value.email !== "string" || typeof value.expires_in !== "number" || typeof value.resend_after !== "number" || typeof value.verification_code_length !== "number" || typeof value.api_base_url_1 !== "string" || typeof value.created_at !== "string" || typeof value.expires_at !== "string") return null;
@@ -15506,7 +16430,7 @@ function pendingSignupFromJson(value) {
15506
16430
  function pendingSignupPath(configDir) {
15507
16431
  return join(configDir, PENDING_SIGNUP_FILE);
15508
16432
  }
15509
- function deletePendingCliSignup(configDir) {
16433
+ function deletePendingAgentSignup(configDir) {
15510
16434
  rmSync(pendingSignupPath(configDir), { force: true });
15511
16435
  }
15512
16436
  function pendingSignupFromStart(start, apiBaseUrl1) {
@@ -15517,7 +16441,7 @@ function pendingSignupFromStart(start, apiBaseUrl1) {
15517
16441
  expires_at: new Date(Date.now() + start.expires_in * 1e3).toISOString()
15518
16442
  };
15519
16443
  }
15520
- function savePendingCliSignup(configDir, start, apiBaseUrl1) {
16444
+ function savePendingAgentSignup(configDir, start, apiBaseUrl1) {
15521
16445
  mkdirSync(configDir, {
15522
16446
  mode: 448,
15523
16447
  recursive: true
@@ -15536,7 +16460,7 @@ function savePendingCliSignup(configDir, start, apiBaseUrl1) {
15536
16460
  throw error;
15537
16461
  }
15538
16462
  }
15539
- function loadPendingCliSignup(configDir, apiBaseUrl1) {
16463
+ function loadPendingAgentSignup(configDir, apiBaseUrl1) {
15540
16464
  const path = pendingSignupPath(configDir);
15541
16465
  let contents;
15542
16466
  try {
@@ -15552,12 +16476,12 @@ function loadPendingCliSignup(configDir, apiBaseUrl1) {
15552
16476
  pending = null;
15553
16477
  }
15554
16478
  if (!pending) {
15555
- deletePendingCliSignup(configDir);
16479
+ deletePendingAgentSignup(configDir);
15556
16480
  return null;
15557
16481
  }
15558
16482
  if (pending.api_base_url_1 !== apiBaseUrl1) return null;
15559
16483
  if (new Date(pending.expires_at).getTime() <= Date.now()) {
15560
- deletePendingCliSignup(configDir);
16484
+ deletePendingAgentSignup(configDir);
15561
16485
  return null;
15562
16486
  }
15563
16487
  return {
@@ -15565,6 +16489,12 @@ function loadPendingCliSignup(configDir, apiBaseUrl1) {
15565
16489
  expires_in: Math.max(0, Math.ceil((new Date(pending.expires_at).getTime() - Date.now()) / 1e3))
15566
16490
  };
15567
16491
  }
16492
+ function requirePendingSignupForEmail(params) {
16493
+ 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.`);
16496
+ return pending;
16497
+ }
15568
16498
  function retryAfterSeconds(result) {
15569
16499
  const raw = result.response?.headers.get("retry-after");
15570
16500
  if (!raw) return null;
@@ -15585,61 +16515,12 @@ async function promptLine(question) {
15585
16515
  rl.close();
15586
16516
  }
15587
16517
  }
15588
- async function promptHidden(question) {
15589
- if (!process$1.stdin.isTTY || !process$1.stderr.isTTY || !process$1.stdin.setRawMode) throw cliError("Password input requires an interactive terminal with hidden input support.");
15590
- return new Promise((resolve, reject) => {
15591
- const input = process$1.stdin;
15592
- let value = "";
15593
- const cleanup = () => {
15594
- input.setRawMode(false);
15595
- input.pause();
15596
- input.off("data", onData);
15597
- };
15598
- const finish = () => {
15599
- cleanup();
15600
- process$1.stderr.write("\n");
15601
- resolve(value);
15602
- };
15603
- const onData = (chunk) => {
15604
- const text = chunk.toString("utf8");
15605
- for (const char of text) {
15606
- if (char === "") {
15607
- cleanup();
15608
- process$1.stderr.write("\n");
15609
- reject(cliError("Signup cancelled."));
15610
- return;
15611
- }
15612
- if (char === "\r" || char === "\n") {
15613
- finish();
15614
- return;
15615
- }
15616
- if (char === "\b" || char === "") {
15617
- value = value.slice(0, -1);
15618
- continue;
15619
- }
15620
- value += char;
15621
- }
15622
- };
15623
- process$1.stderr.write(question);
15624
- input.setRawMode(true);
15625
- input.resume();
15626
- input.on("data", onData);
15627
- });
15628
- }
15629
16518
  function formatSignupSeconds(seconds) {
15630
16519
  if (typeof seconds !== "number" || !Number.isFinite(seconds) || seconds <= 0) return "soon";
15631
16520
  if (seconds < 60) return `${Math.ceil(seconds)} seconds`;
15632
16521
  const minutes = Math.ceil(seconds / 60);
15633
16522
  return `${minutes} minute${minutes === 1 ? "" : "s"}`;
15634
16523
  }
15635
- function shouldRetrySignupPassword(errorCode) {
15636
- return errorCode === CLERK_PASSWORD_REJECTED;
15637
- }
15638
- function signupErrorMessage(payload) {
15639
- if (!isRecord(payload)) return null;
15640
- const message = (isRecord(payload.error) ? payload.error : payload).message;
15641
- return typeof message === "string" && message.trim() ? message : null;
15642
- }
15643
16524
  async function promptRequired(question) {
15644
16525
  while (true) {
15645
16526
  const value = await promptLine(question);
@@ -15647,20 +16528,6 @@ async function promptRequired(question) {
15647
16528
  process$1.stderr.write("Please enter a value.\n");
15648
16529
  }
15649
16530
  }
15650
- async function promptRequiredPassword(question) {
15651
- while (true) {
15652
- const value = await promptHidden(question);
15653
- if (value) return value;
15654
- process$1.stderr.write("Please enter a password.\n");
15655
- }
15656
- }
15657
- async function promptNewPassword() {
15658
- while (true) {
15659
- const password = await promptRequiredPassword("Password: ");
15660
- if (password === await promptRequiredPassword("Confirm password: ")) return password;
15661
- process$1.stderr.write("Passwords did not match. Try again.\n");
15662
- }
15663
- }
15664
16531
  async function confirmTerms() {
15665
16532
  process$1.stderr.write("By creating an account, you agree to Primitive's Terms of Service and Privacy Policy:\n");
15666
16533
  process$1.stderr.write(" https://primitive.dev/terms\n");
@@ -15668,8 +16535,100 @@ async function confirmTerms() {
15668
16535
  const answer = (await promptRequired("Type 'yes' to continue: ")).toLowerCase();
15669
16536
  if (answer !== "yes" && answer !== "y") throw cliError("You must accept the terms to create an account.");
15670
16537
  }
16538
+ async function checkExistingCredentials(params) {
16539
+ const checkExistingLoginFn = params.deps.checkExistingLogin ?? checkExistingLogin;
16540
+ let existing;
16541
+ try {
16542
+ existing = loadCliCredentials(params.configDir);
16543
+ } catch (error) {
16544
+ if (!params.flags.force) throw error;
16545
+ const detail = error instanceof Error ? error.message : String(error);
16546
+ process$1.stderr.write(`Replacing unreadable Primitive CLI credentials because --force was set: ${detail}\n`);
16547
+ existing = null;
16548
+ }
16549
+ if (existing && params.flags.force) {
16550
+ process$1.stderr.write("Replacing saved Primitive CLI credentials after signup because --force was set.\n");
16551
+ return;
16552
+ }
16553
+ if (!existing) return;
16554
+ const existingStatus = await checkExistingLoginFn({
16555
+ apiBaseUrl1: params.apiBaseUrl1,
16556
+ configDir: params.configDir,
16557
+ credentials: existing,
16558
+ credentialsLockHeld: true
16559
+ });
16560
+ if (existingStatus.status === "removed_stale") {
16561
+ process$1.stderr.write("Continuing with Primitive signup...\n");
16562
+ return;
16563
+ }
16564
+ if (existingStatus.status === "blocked") {
16565
+ writeErrorWithHints(existingStatus.payload);
16566
+ throw cliError(existingStatus.message);
16567
+ }
16568
+ throw cliError(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before creating a new account.`);
16569
+ }
16570
+ function saveSignupCredentials(params) {
16571
+ saveCliCredentials(params.configDir, {
16572
+ access_token: params.signup.access_token,
16573
+ api_base_url_1: params.apiBaseUrl1,
16574
+ auth_method: "oauth",
16575
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
16576
+ expires_at: cliAccessTokenExpiresAt(params.signup.expires_in),
16577
+ oauth_client_id: params.signup.oauth_client_id,
16578
+ oauth_grant_id: params.signup.oauth_grant_id,
16579
+ org_id: params.signup.org_id,
16580
+ org_name: params.signup.org_name,
16581
+ refresh_token: params.signup.refresh_token,
16582
+ token_type: params.signup.token_type
16583
+ });
16584
+ }
16585
+ function writeStartInstructions(start) {
16586
+ process$1.stderr.write(`Sent a ${start.verification_code_length}-digit verification code to ${start.email}.\n`);
16587
+ 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`);
16589
+ }
16590
+ async function startSignup(params) {
16591
+ const existingPending = loadPendingAgentSignup(params.configDir, params.apiBaseUrl1);
16592
+ if (existingPending && !params.flags.force) {
16593
+ 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`);
16596
+ return {
16597
+ pending: existingPending,
16598
+ started: false
16599
+ };
16600
+ }
16601
+ throw cliError(`Pending signup is for ${existingPending.email}. Run \`primitive signup ${params.email} --force\` to replace it.`);
16602
+ }
16603
+ if (params.flags.force) deletePendingAgentSignup(params.configDir);
16604
+ const promptRequiredFn = params.deps.promptRequired ?? promptRequired;
16605
+ const confirmTermsFn = params.deps.confirmTerms ?? confirmTerms;
16606
+ const startFn = params.deps.startAgentSignup ?? startAgentSignup;
16607
+ const signupCode = params.flags["signup-code"] ?? await promptRequiredFn("Signup code: ");
16608
+ if (!params.flags["accept-terms"]) await confirmTermsFn();
16609
+ const started = await startFn({
16610
+ body: {
16611
+ device_name: params.flags["device-name"] ?? hostname(),
16612
+ email: params.email,
16613
+ signup_code: signupCode,
16614
+ terms_accepted: true
16615
+ },
16616
+ client: params.apiClient.client,
16617
+ responseStyle: "fields"
16618
+ });
16619
+ if (started.error) {
16620
+ writeErrorWithHints(extractErrorPayload(started.error));
16621
+ throw cliError("Could not start Primitive agent signup.");
16622
+ }
16623
+ const startResult = unwrapData(started.data);
16624
+ if (!startResult) throw cliError("Primitive API returned an empty agent signup response.");
16625
+ return {
16626
+ pending: savePendingAgentSignup(params.configDir, startResult, params.apiBaseUrl1),
16627
+ started: true
16628
+ };
16629
+ }
15671
16630
  async function resendVerificationCode(params) {
15672
- const resent = await (params.deps.resendCliSignupVerification ?? resendCliSignupVerification)({
16631
+ const resent = await (params.deps.resendAgentSignupVerification ?? resendAgentSignupVerification)({
15673
16632
  body: { signup_token: params.start.signup_token },
15674
16633
  client: params.apiClient.client,
15675
16634
  responseStyle: "fields"
@@ -15683,167 +16642,262 @@ async function resendVerificationCode(params) {
15683
16642
  signup_token: params.start.signup_token,
15684
16643
  verification_code_length: resend.verification_code_length
15685
16644
  } : params.start;
15686
- savePendingCliSignup(params.configDir, next, params.apiBaseUrl1);
15687
- process$1.stderr.write(`Sent a new ${next.verification_code_length}-digit verification code. It expires in ${formatSignupSeconds(next.expires_in)}.\n`);
15688
- return next;
16645
+ return {
16646
+ pending: savePendingAgentSignup(params.configDir, next, params.apiBaseUrl1),
16647
+ resent: true
16648
+ };
15689
16649
  }
15690
16650
  const payload = extractErrorPayload(resent.error);
15691
- if (extractErrorCode(payload) === SLOW_DOWN) {
15692
- const suffix = ` Wait ${formatSignupSeconds(retryAfterSeconds(resent) ?? params.start.resend_after)} before trying again.`;
15693
- process$1.stderr.write(`Verification email was sent recently.${suffix}\n`);
15694
- return params.start;
16651
+ const code = extractErrorCode(payload);
16652
+ if (code === SLOW_DOWN) {
16653
+ const retryAfter = retryAfterSeconds(resent) ?? params.start.resend_after;
16654
+ process$1.stderr.write(`Verification email was sent recently. Wait ${formatSignupSeconds(retryAfter)} before trying again.\n`);
16655
+ return {
16656
+ pending: params.start,
16657
+ resent: false
16658
+ };
15695
16659
  }
16660
+ if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(params.configDir);
15696
16661
  writeErrorWithHints(payload);
15697
- throw cliError("Could not resend Primitive CLI signup verification email.");
16662
+ throw cliError("Could not resend Primitive agent signup verification email.");
15698
16663
  }
15699
- async function runSignupWithCredentialLock(params) {
16664
+ async function runSignupStartWithCredentialLock(params) {
15700
16665
  const { configDir, flags } = params;
15701
16666
  const deps = params.deps ?? {};
15702
16667
  const promptRequiredFn = deps.promptRequired ?? promptRequired;
15703
- const promptNewPasswordFn = deps.promptNewPassword ?? promptNewPassword;
15704
- const confirmTermsFn = deps.confirmTerms ?? confirmTerms;
15705
- const startFn = deps.startCliSignup ?? startCliSignup;
15706
- const verifyFn = deps.verifyCliSignup ?? verifyCliSignup;
15707
- const checkExistingLoginFn = deps.checkExistingLogin ?? checkExistingLogin;
16668
+ const email = params.email ?? await promptRequiredFn("Email: ");
16669
+ await checkExistingCredentials({
16670
+ apiBaseUrl1: flags["api-base-url-1"],
16671
+ configDir,
16672
+ deps,
16673
+ flags
16674
+ });
16675
+ const { apiClient, requestConfig } = createCliApiClient({
16676
+ apiBaseUrl1: flags["api-base-url-1"],
16677
+ configDir
16678
+ });
16679
+ const start = await startSignup({
16680
+ apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
16681
+ apiClient,
16682
+ configDir,
16683
+ deps,
16684
+ email,
16685
+ flags
16686
+ });
16687
+ if (start.started) writeStartInstructions(start.pending);
16688
+ }
16689
+ async function runSignupConfirmWithCredentialLock(params) {
16690
+ const { configDir, flags } = params;
16691
+ const deps = params.deps ?? {};
16692
+ if (!params.skipExistingCredentialCheck) await checkExistingCredentials({
16693
+ apiBaseUrl1: flags["api-base-url-1"],
16694
+ configDir,
16695
+ deps,
16696
+ flags
16697
+ });
15708
16698
  const { apiClient, requestConfig } = createCliApiClient({
15709
16699
  apiBaseUrl1: flags["api-base-url-1"],
15710
16700
  configDir
15711
16701
  });
15712
16702
  const apiBaseUrl1 = requestConfig.resolvedApiBaseUrl1;
15713
- let existing;
15714
- try {
15715
- existing = loadCliCredentials(configDir);
15716
- } catch (error) {
15717
- if (!flags.force) throw error;
15718
- const detail = error instanceof Error ? error.message : String(error);
15719
- process$1.stderr.write(`Replacing unreadable Primitive CLI credentials because --force was set: ${detail}\n`);
15720
- existing = null;
15721
- }
15722
- if (existing && flags.force) process$1.stderr.write("Replacing saved Primitive CLI credentials after signup because --force was set.\n");
15723
- else if (existing) {
15724
- const existingStatus = await checkExistingLoginFn({
15725
- apiBaseUrl1: flags["api-base-url-1"],
16703
+ const pending = requirePendingSignupForEmail({
16704
+ apiBaseUrl1,
16705
+ configDir,
16706
+ email: params.email
16707
+ });
16708
+ const verified = await (deps.verifyAgentSignup ?? verifyAgentSignup)({
16709
+ body: {
16710
+ ...flags["org-id"] ? { org_id: flags["org-id"] } : {},
16711
+ signup_token: pending.signup_token,
16712
+ verification_code: params.code
16713
+ },
16714
+ client: apiClient.client,
16715
+ responseStyle: "fields"
16716
+ });
16717
+ if (verified.data) {
16718
+ const signup = unwrapData(verified.data);
16719
+ if (!signup) throw cliError("Primitive API returned an empty agent signup verification response.");
16720
+ saveSignupCredentials({
16721
+ apiBaseUrl1,
15726
16722
  configDir,
15727
- credentials: existing
15728
- });
15729
- if (existingStatus.status === "removed_stale") process$1.stderr.write("Continuing with Primitive CLI signup...\n");
15730
- else if (existingStatus.status === "blocked") {
15731
- writeErrorWithHints(existingStatus.payload);
15732
- throw cliError(existingStatus.message);
15733
- } else throw cliError(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before creating a new account.`);
15734
- }
15735
- if (flags.force) deletePendingCliSignup(configDir);
15736
- let start = flags.force ? null : loadPendingCliSignup(configDir, apiBaseUrl1);
15737
- const resumed = Boolean(start);
15738
- if (start) process$1.stderr.write(`Continuing pending Primitive CLI signup for ${start.email}.\n`);
15739
- else {
15740
- const signupCode = await promptRequiredFn("Signup code: ");
15741
- await confirmTermsFn();
15742
- const email = await promptRequiredFn("Email: ");
15743
- const started = await startFn({
15744
- body: {
15745
- device_name: flags["device-name"] ?? hostname(),
15746
- email,
15747
- signup_code: signupCode,
15748
- terms_accepted: true
15749
- },
15750
- client: apiClient.client,
15751
- responseStyle: "fields"
16723
+ signup
15752
16724
  });
15753
- if (started.error) {
15754
- writeErrorWithHints(extractErrorPayload(started.error));
15755
- throw cliError("Could not start Primitive CLI signup.");
15756
- }
15757
- const startResult = unwrapData(started.data);
15758
- if (!startResult) throw cliError("Primitive API returned an empty CLI signup response.");
15759
- start = savePendingCliSignup(configDir, startResult, apiBaseUrl1);
16725
+ deletePendingAgentSignup(configDir);
16726
+ const org = signup.org_name ? ` (${signup.org_name})` : "";
16727
+ process$1.stderr.write(`Logged in to org ${signup.org_id}${org}.\n`);
16728
+ process$1.stderr.write(`Saved credentials to ${credentialsPath(configDir)}.\n`);
16729
+ return;
15760
16730
  }
15761
- if (resumed) process$1.stderr.write(`Check your email for the ${start.verification_code_length}-digit verification code sent to ${start.email}.\n`);
15762
- else process$1.stderr.write(`Sent a ${start.verification_code_length}-digit verification code to ${start.email}.\n`);
16731
+ const payload = extractErrorPayload(verified.error);
16732
+ const code = extractErrorCode(payload);
16733
+ if (code === INVALID_VERIFICATION_CODE) throw cliError("Invalid verification code. Try again or run signup resend.");
16734
+ if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingAgentSignup(configDir);
16735
+ writeErrorWithHints(payload);
16736
+ throw cliError("Primitive agent signup failed while verifying the account.");
16737
+ }
16738
+ async function runSignupResendWithCredentialLock(params) {
16739
+ const deps = params.deps ?? {};
16740
+ const { apiClient, requestConfig } = createCliApiClient({
16741
+ apiBaseUrl1: params.flags["api-base-url-1"],
16742
+ configDir: params.configDir
16743
+ });
16744
+ const pending = requirePendingSignupForEmail({
16745
+ apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
16746
+ configDir: params.configDir,
16747
+ email: params.email
16748
+ });
16749
+ const resend = await resendVerificationCode({
16750
+ apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
16751
+ apiClient,
16752
+ configDir: params.configDir,
16753
+ deps,
16754
+ start: pending
16755
+ });
16756
+ if (resend.resent) process$1.stderr.write(`Sent a new ${resend.pending.verification_code_length}-digit verification code to ${resend.pending.email}. It expires in ${formatSignupSeconds(resend.pending.expires_in)}.\n`);
16757
+ }
16758
+ async function runSignupInteractiveWithCredentialLock(params) {
16759
+ const { configDir, flags } = params;
16760
+ const deps = params.deps ?? {};
16761
+ const promptRequiredFn = deps.promptRequired ?? promptRequired;
16762
+ await checkExistingCredentials({
16763
+ apiBaseUrl1: flags["api-base-url-1"],
16764
+ configDir,
16765
+ deps,
16766
+ flags
16767
+ });
16768
+ const { apiClient, requestConfig } = createCliApiClient({
16769
+ apiBaseUrl1: flags["api-base-url-1"],
16770
+ configDir
16771
+ });
16772
+ const apiBaseUrl1 = requestConfig.resolvedApiBaseUrl1;
16773
+ let start = flags.force ? null : loadPendingAgentSignup(configDir, apiBaseUrl1);
16774
+ if (start) process$1.stderr.write(`Continuing pending Primitive signup for ${start.email}.\n`);
16775
+ else start = (await startSignup({
16776
+ apiBaseUrl1,
16777
+ apiClient,
16778
+ configDir,
16779
+ deps,
16780
+ email: await promptRequiredFn("Email: "),
16781
+ flags
16782
+ })).pending;
16783
+ process$1.stderr.write(`Check your email for the ${start.verification_code_length}-digit verification code sent to ${start.email}.\n`);
15763
16784
  process$1.stderr.write(`The code expires in ${formatSignupSeconds(start.expires_in)}.\n`);
15764
16785
  process$1.stderr.write(`Enter the code from the email, or type \`resend\` to send a new code after ${formatSignupSeconds(start.resend_after)}.\n`);
15765
16786
  while (true) {
15766
16787
  const verificationCode = await promptRequiredFn(`Verification code (${start.verification_code_length} digits): `);
15767
16788
  if (verificationCode.toLowerCase() === "resend") {
15768
- start = await resendVerificationCode({
16789
+ const resend = await resendVerificationCode({
15769
16790
  apiBaseUrl1,
15770
16791
  apiClient,
15771
16792
  configDir,
15772
16793
  deps,
15773
16794
  start
15774
16795
  });
16796
+ start = resend.pending;
16797
+ if (resend.resent) process$1.stderr.write(`Sent a new ${start.verification_code_length}-digit verification code. It expires in ${formatSignupSeconds(start.expires_in)}.\n`);
15775
16798
  continue;
15776
16799
  }
15777
- let password = await promptNewPasswordFn();
15778
- while (true) {
15779
- const verified = await verifyFn({
15780
- body: {
15781
- password,
15782
- signup_token: start.signup_token,
15783
- verification_code: verificationCode
16800
+ try {
16801
+ await runSignupConfirmWithCredentialLock({
16802
+ code: verificationCode,
16803
+ configDir,
16804
+ deps,
16805
+ email: start.email,
16806
+ flags: {
16807
+ "api-base-url-1": flags["api-base-url-1"],
16808
+ force: true
15784
16809
  },
15785
- client: apiClient.client,
15786
- responseStyle: "fields"
16810
+ skipExistingCredentialCheck: true
15787
16811
  });
15788
- if (verified.data) {
15789
- const signup = unwrapData(verified.data);
15790
- if (!signup) throw cliError("Primitive API returned an empty CLI signup verification response.");
15791
- saveCliCredentials(configDir, {
15792
- api_key: signup.api_key,
15793
- api_base_url_1: apiBaseUrl1,
15794
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
15795
- key_id: signup.key_id,
15796
- key_prefix: signup.key_prefix,
15797
- org_id: signup.org_id,
15798
- org_name: signup.org_name
15799
- });
15800
- deletePendingCliSignup(configDir);
15801
- const org = signup.org_name ? ` (${signup.org_name})` : "";
15802
- process$1.stderr.write(`Created account and logged in to org ${signup.org_id}${org}.\n`);
15803
- process$1.stderr.write(`Saved credentials to ${credentialsPath(configDir)}.\n`);
15804
- return;
15805
- }
15806
- const payload = extractErrorPayload(verified.error);
15807
- const code = extractErrorCode(payload);
15808
- if (code === INVALID_VERIFICATION_CODE) {
16812
+ return;
16813
+ } catch (error) {
16814
+ if (error instanceof Errors.CLIError && error.message.startsWith("Invalid verification code.")) {
15809
16815
  process$1.stderr.write("Invalid verification code. Try again or type `resend`.\n");
15810
- break;
15811
- }
15812
- if (shouldRetrySignupPassword(code)) {
15813
- const message = signupErrorMessage(payload);
15814
- if (message) process$1.stderr.write(`Password rejected: ${message}\n`);
15815
- process$1.stderr.write("Choose a different password and try again.\n");
15816
- password = await promptNewPasswordFn();
15817
16816
  continue;
15818
16817
  }
15819
- if (code === EXPIRED_TOKEN || code === INVALID_SIGNUP_TOKEN) deletePendingCliSignup(configDir);
15820
- writeErrorWithHints(payload);
15821
- throw cliError("Primitive CLI signup failed while verifying the account.");
16818
+ throw error;
15822
16819
  }
15823
16820
  }
15824
16821
  }
16822
+ function commonStartFlags() {
16823
+ return {
16824
+ "accept-terms": Flags.boolean({ description: "Confirm acceptance of Primitive's Terms of Service and Privacy Policy" }),
16825
+ "api-base-url-1": Flags.string({
16826
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
16827
+ env: "PRIMITIVE_API_BASE_URL_1",
16828
+ hidden: true
16829
+ }),
16830
+ "device-name": Flags.string({ description: "Device name used for the created CLI OAuth session" }),
16831
+ force: Flags.boolean({
16832
+ char: "f",
16833
+ description: "Replace saved credentials or pending signup state when needed"
16834
+ }),
16835
+ "signup-code": Flags.string({
16836
+ description: "Signup code required to create an account",
16837
+ env: "PRIMITIVE_SIGNUP_CODE"
16838
+ })
16839
+ };
16840
+ }
15825
16841
  var SignupCommand = class SignupCommand extends Command {
15826
- static description = "Create a Primitive account from the terminal, verify your email, and save an org-scoped CLI API key locally.";
15827
- static summary = "Create an account and log in";
16842
+ static args = { email: Args.string({
16843
+ description: "Email address to sign up",
16844
+ required: false
16845
+ }) };
16846
+ static description = "Start a Primitive account signup, send an email verification code, and save a pending signup token locally.";
16847
+ static summary = "Start account signup";
15828
16848
  static examples = [
15829
- "<%= config.bin %> signup",
15830
- "<%= config.bin %> signup --device-name work-laptop",
15831
- "<%= config.bin %> signup --force"
16849
+ "<%= config.bin %> signup user@example.com",
16850
+ "<%= config.bin %> signup user@example.com --signup-code invite-code --accept-terms",
16851
+ "<%= config.bin %> signup confirm user@example.com 123456"
15832
16852
  ];
16853
+ static flags = commonStartFlags();
16854
+ async run() {
16855
+ const { args, flags } = await this.parse(SignupCommand);
16856
+ let releaseCredentialsLock;
16857
+ try {
16858
+ releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
16859
+ } catch (error) {
16860
+ throw cliError(error instanceof Error ? error.message : String(error));
16861
+ }
16862
+ try {
16863
+ await runSignupStartWithCredentialLock({
16864
+ configDir: this.config.configDir,
16865
+ email: args.email,
16866
+ flags
16867
+ });
16868
+ } finally {
16869
+ releaseCredentialsLock();
16870
+ }
16871
+ }
16872
+ };
16873
+ var SignupConfirmCommand = class SignupConfirmCommand extends Command {
16874
+ static args = {
16875
+ email: Args.string({
16876
+ description: "Email address used to start signup",
16877
+ required: true
16878
+ }),
16879
+ code: Args.string({
16880
+ description: "Verification code from the signup email",
16881
+ required: true
16882
+ })
16883
+ };
16884
+ static description = "Confirm a pending Primitive signup, create an OAuth session, and save CLI credentials locally.";
16885
+ static summary = "Confirm account signup";
16886
+ static examples = ["<%= config.bin %> signup confirm user@example.com 123456", "<%= config.bin %> signup confirm user@example.com 123456 --org-id 00000000-0000-4000-8000-000000000000"];
15833
16887
  static flags = {
15834
16888
  "api-base-url-1": Flags.string({
15835
16889
  description: "Override the primary API base URL. Internal testing only; not documented to customers.",
15836
16890
  env: "PRIMITIVE_API_BASE_URL_1",
15837
16891
  hidden: true
15838
16892
  }),
15839
- "device-name": Flags.string({ description: "Device name used for the created CLI API key" }),
15840
16893
  force: Flags.boolean({
15841
16894
  char: "f",
15842
- description: "Replace saved credentials without first verifying the existing login"
15843
- })
16895
+ description: "Replace saved credentials after verification"
16896
+ }),
16897
+ "org-id": Flags.string({ description: "Workspace id to target when the email belongs to multiple workspaces" })
15844
16898
  };
15845
16899
  async run() {
15846
- const { flags } = await this.parse(SignupCommand);
16900
+ const { args, flags } = await this.parse(SignupConfirmCommand);
15847
16901
  let releaseCredentialsLock;
15848
16902
  try {
15849
16903
  releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
@@ -15851,27 +16905,81 @@ var SignupCommand = class SignupCommand extends Command {
15851
16905
  throw cliError(error instanceof Error ? error.message : String(error));
15852
16906
  }
15853
16907
  try {
15854
- await this.runWithCredentialLock(flags);
16908
+ await runSignupConfirmWithCredentialLock({
16909
+ code: args.code,
16910
+ configDir: this.config.configDir,
16911
+ email: args.email,
16912
+ flags
16913
+ });
15855
16914
  } finally {
15856
16915
  releaseCredentialsLock();
15857
16916
  }
15858
16917
  }
15859
- async runWithCredentialLock(flags) {
15860
- await runSignupWithCredentialLock({
15861
- configDir: this.config.configDir,
15862
- flags
15863
- });
16918
+ };
16919
+ var SignupResendCommand = class SignupResendCommand extends Command {
16920
+ static args = { email: Args.string({
16921
+ description: "Email address used to start signup",
16922
+ required: true
16923
+ }) };
16924
+ static description = "Resend the verification code for a pending signup.";
16925
+ static summary = "Resend signup verification code";
16926
+ static examples = ["<%= config.bin %> signup resend user@example.com"];
16927
+ static flags = { "api-base-url-1": Flags.string({
16928
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
16929
+ env: "PRIMITIVE_API_BASE_URL_1",
16930
+ hidden: true
16931
+ }) };
16932
+ async run() {
16933
+ const { args, flags } = await this.parse(SignupResendCommand);
16934
+ let releaseCredentialsLock;
16935
+ try {
16936
+ releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
16937
+ } catch (error) {
16938
+ throw cliError(error instanceof Error ? error.message : String(error));
16939
+ }
16940
+ try {
16941
+ await runSignupResendWithCredentialLock({
16942
+ configDir: this.config.configDir,
16943
+ email: args.email,
16944
+ flags
16945
+ });
16946
+ } finally {
16947
+ releaseCredentialsLock();
16948
+ }
16949
+ }
16950
+ };
16951
+ var SignupInteractiveCommand = class SignupInteractiveCommand extends Command {
16952
+ static description = "Run the full signup flow in one interactive terminal session.";
16953
+ static summary = "Run interactive account signup";
16954
+ static examples = ["<%= config.bin %> signup interactive"];
16955
+ static flags = commonStartFlags();
16956
+ async run() {
16957
+ const { flags } = await this.parse(SignupInteractiveCommand);
16958
+ let releaseCredentialsLock;
16959
+ try {
16960
+ releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
16961
+ } catch (error) {
16962
+ throw cliError(error instanceof Error ? error.message : String(error));
16963
+ }
16964
+ try {
16965
+ await runSignupInteractiveWithCredentialLock({
16966
+ configDir: this.config.configDir,
16967
+ flags
16968
+ });
16969
+ } finally {
16970
+ releaseCredentialsLock();
16971
+ }
15864
16972
  }
15865
16973
  };
15866
16974
  //#endregion
15867
16975
  //#region src/oclif/commands/whoami.ts
15868
16976
  var WhoamiCommand = class WhoamiCommand extends Command {
15869
- static description = `Print the account currently authenticated by the API key. Useful as a credentials smoke test: confirms the key is live and shows which account it belongs to.`;
16977
+ 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.`;
15870
16978
  static summary = "Print the authenticated account (credentials smoke test)";
15871
16979
  static examples = ["<%= config.bin %> whoami", "<%= config.bin %> whoami --api-key prim_..."];
15872
16980
  static flags = {
15873
16981
  "api-key": Flags.string({
15874
- description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
16982
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
15875
16983
  env: "PRIMITIVE_API_KEY"
15876
16984
  }),
15877
16985
  "api-base-url-1": Flags.string({
@@ -15889,7 +16997,7 @@ var WhoamiCommand = class WhoamiCommand extends Command {
15889
16997
  async run() {
15890
16998
  const { flags } = await this.parse(WhoamiCommand);
15891
16999
  await runWithTiming(flags.time, async () => {
15892
- const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
17000
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
15893
17001
  apiKey: flags["api-key"],
15894
17002
  apiBaseUrl1: flags["api-base-url-1"],
15895
17003
  apiBaseUrl2: flags["api-base-url-2"],
@@ -15983,7 +17091,7 @@ function renderFishCompletion(binName) {
15983
17091
  for (const operation of topicOperations) {
15984
17092
  lines.push(`complete -c ${binName} -f -n '__fish_${binName}_topic_needs_subcommand ${fishEscape(topic)}' -a '${fishEscape(operation.command)}' -d '${fishEscape(operation.summary ?? `${operation.method} ${operation.path}`)}'`);
15985
17093
  for (const parameter of [...operation.pathParams, ...operation.queryParams]) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l '${fishEscape(parameter.name.replace(/_/g, "-"))}' -r -d '${fishEscape(parameter.description ?? parameter.name)}'`);
15986
- lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'api-key' -r -d 'Primitive API key (defaults to PRIMITIVE_API_KEY or saved primitive login credentials)'`);
17094
+ lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'api-key' -r -d 'Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)'`);
15987
17095
  if (!operation.binaryResponse) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'envelope' -d 'Print the full response envelope, including pagination metadata'`);
15988
17096
  if (operation.hasJsonBody) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'body' -r -d 'JSON request body'`, `complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'body-file' -r -d 'Path to a JSON file used as the request body'`);
15989
17097
  if (operation.binaryResponse) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'output' -r -d 'Write binary response bytes to a file'`);
@@ -16136,6 +17244,9 @@ const COMMANDS = {
16136
17244
  chat: ChatCommand,
16137
17245
  login: LoginCommand,
16138
17246
  signup: SignupCommand,
17247
+ "signup:confirm": SignupConfirmCommand,
17248
+ "signup:interactive": SignupInteractiveCommand,
17249
+ "signup:resend": SignupResendCommand,
16139
17250
  logout: LogoutCommand,
16140
17251
  whoami: WhoamiCommand,
16141
17252
  doctor: DoctorCommand,