@primitivedotdev/cli 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/oclif/index.js +415 -10
  2. package/package.json +7 -1
@@ -3,9 +3,9 @@ import { Args, Command, Errors, Flags, ux } from "@oclif/core";
3
3
  import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import path, { basename, dirname, join, relative, resolve, sep } from "node:path";
6
- import { hostname } from "node:os";
7
6
  import process$1 from "node:process";
8
7
  import { createInterface } from "node:readline/promises";
8
+ import { hostname } from "node:os";
9
9
  import { spawn } from "node:child_process";
10
10
  //#region \0rolldown/runtime.js
11
11
  var __defProp = Object.defineProperty;
@@ -1194,10 +1194,12 @@ const listFunctions = (options) => (options?.client ?? client).get({
1194
1194
  * each delivery and forwards the `Primitive-Signature` header to
1195
1195
  * the handler. Verify the raw request body with
1196
1196
  * `PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification
1197
- * the request body parses to an `email.received` event (see
1198
- * `EmailReceivedEvent` and the Webhook payload section for the full
1199
- * schema). Code is bundled before being uploaded; ship a single
1200
- * self-contained file rather than relying on external imports.
1197
+ * the request body parses to a webhook event whose `event` field is
1198
+ * `email.received` for normal inbound mail, or a machine-mail type
1199
+ * (`email.bounced`, `email.tls_report`, `email.dmarc_report`,
1200
+ * `email.dmarc_failure`) for bounces and reports. Code is bundled
1201
+ * before being uploaded; ship a single self-contained file rather
1202
+ * than relying on external imports.
1201
1203
  *
1202
1204
  * **Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`
1203
1205
  * (optional) is capped at 5 MiB UTF-8, stored with each deployment
@@ -1618,7 +1620,7 @@ const openapiDocument = {
1618
1620
  },
1619
1621
  {
1620
1622
  "name": "Functions",
1621
- "description": "Deploy JavaScript handlers that run on inbound mail. Each function\nis a single ESM module whose default export is an object with an\nasync `fetch(request, env)` method, in the shape of a Workers-style\nhandler. Primitive signs each delivery and forwards the\n`Primitive-Signature` header to the handler; verify the raw request\nbody with `PRIMITIVE_WEBHOOK_SECRET` before trusting the parsed\n`email.received` event (see `EmailReceivedEvent` and the Webhook\npayload section for the full schema). Code runs on\nPrimitive's edge runtime; there is no infrastructure to manage.\nSecrets land in `env` as encrypted bindings and are refreshed on\nevery redeploy.\n"
1623
+ "description": "Deploy JavaScript handlers that run on inbound mail. Each function\nis a single ESM module whose default export is an object with an\nasync `fetch(request, env)` method, in the shape of a Workers-style\nhandler. Primitive signs each delivery and forwards the\n`Primitive-Signature` header to the handler; verify the raw request\nbody with `PRIMITIVE_WEBHOOK_SECRET` before trusting the parsed event.\nThe `event` field is `email.received` for normal inbound mail, or a\nmachine-mail type (`email.bounced`, `email.tls_report`,\n`email.dmarc_report`, `email.dmarc_failure`) for bounces and reports;\nthe payload shape is otherwise identical. Code runs on\nPrimitive's edge runtime; there is no infrastructure to manage.\nSecrets land in `env` as encrypted bindings and are refreshed on\nevery redeploy.\n"
1622
1624
  }
1623
1625
  ],
1624
1626
  "paths": {
@@ -2390,6 +2392,25 @@ const openapiDocument = {
2390
2392
  "format": "date-time"
2391
2393
  },
2392
2394
  "description": "Filter emails created on or before this timestamp"
2395
+ },
2396
+ {
2397
+ "name": "since",
2398
+ "in": "query",
2399
+ "schema": {
2400
+ "type": "string",
2401
+ "maxLength": 200
2402
+ },
2403
+ "description": "Forward-tail cursor. Returns rows that became visible AFTER this\ncursor, oldest-first, so a caller can stream new inbound mail by\nre-passing the cursor from each response. Mutually exclusive with\n`cursor` (which pages history newest-first). Pass the `meta.cursor`\nfrom the previous `since` response; an empty page means caught up.\n"
2404
+ },
2405
+ {
2406
+ "name": "wait",
2407
+ "in": "query",
2408
+ "schema": {
2409
+ "type": "integer",
2410
+ "minimum": 0,
2411
+ "maximum": 30
2412
+ },
2413
+ "description": "Long-poll: hold the request up to this many seconds waiting for new\nmail past `since`, returning as soon as any arrives (or an empty\npage when the wait elapses). Requires `since`. Omitted means no wait\n(returns immediately); the server treats an absent value as 0. NOT\ngiven an OpenAPI `default` on purpose: a default makes some\ngenerators (e.g. openapi-python-client) send `wait=0` on every call,\nwhich then fails the `wait` requires `since` check for plain history\nlistings.\n"
2393
2414
  }
2394
2415
  ],
2395
2416
  "responses": {
@@ -3329,7 +3350,7 @@ const openapiDocument = {
3329
3350
  "post": {
3330
3351
  "operationId": "createFunction",
3331
3352
  "summary": "Deploy a function",
3332
- "description": "Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). Primitive signs\neach delivery and forwards the `Primitive-Signature` header to\nthe handler. Verify the raw request body with\n`PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification\nthe request body parses to an `email.received` event (see\n`EmailReceivedEvent` and the Webhook payload section for the full\nschema). Code is bundled before being uploaded; ship a single\nself-contained file rather than relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8, stored with each deployment\nattempt, and sent to the runtime so stack traces can resolve to\noriginal source files.\n\n**Routing.** On successful deploy, the function code is live\nin the runtime, but inbound mail will not reach it until at\nleast one route is bound. Routes are managed from the Primitive\ndashboard. A `deploy_status` of `deployed` means the script is\ninstalled, not that the function is receiving mail. The\ninternal runtime URL is not returned by the API and is not a\ncustomer-facing integration surface.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,\n`PRIMITIVE_API_BASE_URL`) already bound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n",
3353
+ "description": "Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). Primitive signs\neach delivery and forwards the `Primitive-Signature` header to\nthe handler. Verify the raw request body with\n`PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification\nthe request body parses to a webhook event whose `event` field is\n`email.received` for normal inbound mail, or a machine-mail type\n(`email.bounced`, `email.tls_report`, `email.dmarc_report`,\n`email.dmarc_failure`) for bounces and reports. Code is bundled\nbefore being uploaded; ship a single self-contained file rather\nthan relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8, stored with each deployment\nattempt, and sent to the runtime so stack traces can resolve to\noriginal source files.\n\n**Routing.** On successful deploy, the function code is live\nin the runtime, but inbound mail will not reach it until at\nleast one route is bound. Routes are managed from the Primitive\ndashboard. A `deploy_status` of `deployed` means the script is\ninstalled, not that the function is receiving mail. The\ninternal runtime URL is not returned by the API and is not a\ncustomer-facing integration surface.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,\n`PRIMITIVE_API_BASE_URL`) already bound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n",
3333
3354
  "tags": ["Functions"],
3334
3355
  "requestBody": {
3335
3356
  "required": true,
@@ -4849,6 +4870,16 @@ const openapiDocument = {
4849
4870
  },
4850
4871
  "email": { "type": "string" },
4851
4872
  "plan": { "type": "string" },
4873
+ "limits": { "$ref": "#/components/schemas/PlanLimits" },
4874
+ "entitlements": {
4875
+ "type": "array",
4876
+ "items": { "type": "string" },
4877
+ "description": "Granted org entitlement keys (sorted). A headless caller reads its\ncapabilities here — e.g. an emailless agent seeing only\n[\"send_mail\", \"send_to_known_addresses\"] knows it is reply-only.\n"
4878
+ },
4879
+ "managed_inbox_address": {
4880
+ "type": ["string", "null"],
4881
+ "description": "The managed inbox FQDN to reply as, or null if the org has no managed inbox."
4882
+ },
4852
4883
  "created_at": {
4853
4884
  "type": "string",
4854
4885
  "format": "date-time"
@@ -4876,6 +4907,9 @@ const openapiDocument = {
4876
4907
  "id",
4877
4908
  "email",
4878
4909
  "plan",
4910
+ "limits",
4911
+ "entitlements",
4912
+ "managed_inbox_address",
4879
4913
  "created_at",
4880
4914
  "discard_content_on_webhook_confirmed"
4881
4915
  ]
@@ -7914,6 +7948,39 @@ const operationManifest = [
7914
7948
  },
7915
7949
  "email": { "type": "string" },
7916
7950
  "plan": { "type": "string" },
7951
+ "limits": {
7952
+ "type": "object",
7953
+ "description": "Plan-derived quota limits for an account.",
7954
+ "properties": {
7955
+ "storage_mb": { "type": "number" },
7956
+ "send_per_hour": { "type": "number" },
7957
+ "send_per_day": { "type": "number" },
7958
+ "api_per_minute": { "type": "number" },
7959
+ "webhooks_max_global": { "type": ["number", "null"] },
7960
+ "webhooks_per_domain": { "type": "boolean" },
7961
+ "filters_per_domain": { "type": "boolean" },
7962
+ "spam_thresholds_per_domain": { "type": "boolean" }
7963
+ },
7964
+ "required": [
7965
+ "storage_mb",
7966
+ "send_per_hour",
7967
+ "send_per_day",
7968
+ "api_per_minute",
7969
+ "webhooks_max_global",
7970
+ "webhooks_per_domain",
7971
+ "filters_per_domain",
7972
+ "spam_thresholds_per_domain"
7973
+ ]
7974
+ },
7975
+ "entitlements": {
7976
+ "type": "array",
7977
+ "items": { "type": "string" },
7978
+ "description": "Granted org entitlement keys (sorted). A headless caller reads its\ncapabilities here — e.g. an emailless agent seeing only\n[\"send_mail\", \"send_to_known_addresses\"] knows it is reply-only.\n"
7979
+ },
7980
+ "managed_inbox_address": {
7981
+ "type": ["string", "null"],
7982
+ "description": "The managed inbox FQDN to reply as, or null if the org has no managed inbox."
7983
+ },
7917
7984
  "created_at": {
7918
7985
  "type": "string",
7919
7986
  "format": "date-time"
@@ -7941,6 +8008,9 @@ const operationManifest = [
7941
8008
  "id",
7942
8009
  "email",
7943
8010
  "plan",
8011
+ "limits",
8012
+ "entitlements",
8013
+ "managed_inbox_address",
7944
8014
  "created_at",
7945
8015
  "discard_content_on_webhook_confirmed"
7946
8016
  ]
@@ -10397,6 +10467,22 @@ const operationManifest = [
10397
10467
  "name": "date_to",
10398
10468
  "required": false,
10399
10469
  "type": "string"
10470
+ },
10471
+ {
10472
+ "description": "Forward-tail cursor. Returns rows that became visible AFTER this\ncursor, oldest-first, so a caller can stream new inbound mail by\nre-passing the cursor from each response. Mutually exclusive with\n`cursor` (which pages history newest-first). Pass the `meta.cursor`\nfrom the previous `since` response; an empty page means caught up.\n",
10473
+ "enum": null,
10474
+ "name": "since",
10475
+ "required": false,
10476
+ "type": "string"
10477
+ },
10478
+ {
10479
+ "description": "Long-poll: hold the request up to this many seconds waiting for new\nmail past `since`, returning as soon as any arrives (or an empty\npage when the wait elapses). Requires `since`. Omitted means no wait\n(returns immediately); the server treats an absent value as 0. NOT\ngiven an OpenAPI `default` on purpose: a default makes some\ngenerators (e.g. openapi-python-client) send `wait=0` on every call,\nwhich then fails the `wait` requires `since` check for plain history\nlistings.\n",
10480
+ "enum": null,
10481
+ "maximum": 30,
10482
+ "minimum": 0,
10483
+ "name": "wait",
10484
+ "required": false,
10485
+ "type": "integer"
10400
10486
  }
10401
10487
  ],
10402
10488
  "requestSchema": null,
@@ -11406,7 +11492,7 @@ const operationManifest = [
11406
11492
  "binaryResponse": false,
11407
11493
  "bodyRequired": true,
11408
11494
  "command": "create-function",
11409
- "description": "Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). Primitive signs\neach delivery and forwards the `Primitive-Signature` header to\nthe handler. Verify the raw request body with\n`PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification\nthe request body parses to an `email.received` event (see\n`EmailReceivedEvent` and the Webhook payload section for the full\nschema). Code is bundled before being uploaded; ship a single\nself-contained file rather than relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8, stored with each deployment\nattempt, and sent to the runtime so stack traces can resolve to\noriginal source files.\n\n**Routing.** On successful deploy, the function code is live\nin the runtime, but inbound mail will not reach it until at\nleast one route is bound. Routes are managed from the Primitive\ndashboard. A `deploy_status` of `deployed` means the script is\ninstalled, not that the function is receiving mail. The\ninternal runtime URL is not returned by the API and is not a\ncustomer-facing integration surface.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,\n`PRIMITIVE_API_BASE_URL`) already bound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n",
11495
+ "description": "Creates and deploys a new function. The handler must be a single\nESM module whose default export is an object with an async\n`fetch(request, env)` method (Workers-style). Primitive signs\neach delivery and forwards the `Primitive-Signature` header to\nthe handler. Verify the raw request body with\n`PRIMITIVE_WEBHOOK_SECRET` before parsing JSON; after verification\nthe request body parses to a webhook event whose `event` field is\n`email.received` for normal inbound mail, or a machine-mail type\n(`email.bounced`, `email.tls_report`, `email.dmarc_report`,\n`email.dmarc_failure`) for bounces and reports. Code is bundled\nbefore being uploaded; ship a single self-contained file rather\nthan relying on external imports.\n\n**Code limits.** `code` is capped at 1 MiB UTF-8. `sourceMap`\n(optional) is capped at 5 MiB UTF-8, stored with each deployment\nattempt, and sent to the runtime so stack traces can resolve to\noriginal source files.\n\n**Routing.** On successful deploy, the function code is live\nin the runtime, but inbound mail will not reach it until at\nleast one route is bound. Routes are managed from the Primitive\ndashboard. A `deploy_status` of `deployed` means the script is\ninstalled, not that the function is receiving mail. The\ninternal runtime URL is not returned by the API and is not a\ncustomer-facing integration surface.\n\n**Secrets.** New functions ship with the managed secrets\n(`PRIMITIVE_WEBHOOK_SECRET`, `PRIMITIVE_API_KEY`,\n`PRIMITIVE_API_BASE_URL`) already bound. Add user-set secrets via\n`POST /functions/{id}/secrets`; secret writes only land in the\nrunning handler on the next redeploy.\n",
11410
11496
  "hasJsonBody": true,
11411
11497
  "method": "POST",
11412
11498
  "operationId": "createFunction",
@@ -15344,6 +15430,76 @@ function canonicalizeCliReferences(description) {
15344
15430
  return description.replaceAll("`primitive emails:latest`", "`primitive emails latest`").replaceAll("`primitive describe emails:get-email | jq '.responseSchema.properties'`", "`primitive describe emails:get | jq '.responseSchema.properties'`");
15345
15431
  }
15346
15432
  //#endregion
15433
+ //#region src/oclif/commands/agent-upgrade.ts
15434
+ /**
15435
+ * Interactive upgrade of an emailless agent account to a full developer
15436
+ * account: starts the email claim, prompts for the emailed code, and verifies
15437
+ * it. Combines the generated `agent:claim` (start) and `agent:claim-verify`
15438
+ * into one flow with a prompt, mirroring the `signup` interactive command.
15439
+ * Authenticated by the agent's own API key (the org is taken from the key).
15440
+ */
15441
+ var AgentUpgradeCommand = class AgentUpgradeCommand extends Command {
15442
+ static description = "Upgrade an emailless agent account to a full developer account by confirming an email. Authenticated by the agent's own API key (PRIMITIVE_API_KEY).";
15443
+ static summary = "Upgrade an agent account to developer (email confirmation)";
15444
+ static examples = ["<%= config.bin %> agent upgrade --email you@example.com"];
15445
+ static flags = {
15446
+ email: Flags.string({ description: "Email to confirm. Prompted if omitted." }),
15447
+ code: Flags.string({ description: "Verification code from the email. Prompted if omitted." }),
15448
+ "api-key": Flags.string({
15449
+ env: "PRIMITIVE_API_KEY",
15450
+ description: "Agent API key (defaults to PRIMITIVE_API_KEY or saved credentials)."
15451
+ }),
15452
+ "api-base-url": Flags.string({ description: "Override the API base URL." })
15453
+ };
15454
+ async run() {
15455
+ const { flags } = await this.parse(AgentUpgradeCommand);
15456
+ const { apiClient } = await createAuthenticatedCliApiClient({
15457
+ apiKey: flags["api-key"],
15458
+ apiBaseUrl: flags["api-base-url"],
15459
+ configDir: this.config.configDir
15460
+ });
15461
+ const email = flags.email ?? await promptRequired$1("Email to confirm: ");
15462
+ const started = await startAgentClaim({
15463
+ body: { email },
15464
+ client: apiClient.client,
15465
+ responseStyle: "fields"
15466
+ });
15467
+ if (!started.data) {
15468
+ writeErrorWithHints(extractErrorPayload(started.error));
15469
+ this.exit(1);
15470
+ return;
15471
+ }
15472
+ process$1.stderr.write(`Verification code sent to ${email}.\n`);
15473
+ const verified = await verifyAgentClaim({
15474
+ body: { verification_code: flags.code ?? await promptRequired$1("Verification code: ") },
15475
+ client: apiClient.client,
15476
+ responseStyle: "fields"
15477
+ });
15478
+ const result = verified.data?.data;
15479
+ if (result) {
15480
+ this.log(JSON.stringify(result, null, 2));
15481
+ process$1.stderr.write(`Upgraded to ${result.plan}. Your API key and managed inbox carry over; the send cap is lifted.\n`);
15482
+ return;
15483
+ }
15484
+ writeErrorWithHints(extractErrorPayload(verified.error));
15485
+ this.exit(1);
15486
+ }
15487
+ };
15488
+ async function promptRequired$1(question) {
15489
+ const rl = createInterface({
15490
+ input: process$1.stdin,
15491
+ output: process$1.stderr
15492
+ });
15493
+ try {
15494
+ for (;;) {
15495
+ const answer = (await rl.question(question)).trim();
15496
+ if (answer) return answer;
15497
+ }
15498
+ } finally {
15499
+ rl.close();
15500
+ }
15501
+ }
15502
+ //#endregion
15347
15503
  //#region src/oclif/attachments.ts
15348
15504
  function readAttachmentBytes(path, readFile) {
15349
15505
  try {
@@ -18777,8 +18933,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
18777
18933
  name: "Primitive Team",
18778
18934
  url: "https://primitive.dev"
18779
18935
  };
18780
- const SDK_VERSION_RANGE = "^1.3.0";
18781
- const CLI_VERSION_RANGE = "^1.3.0";
18936
+ const SDK_VERSION_RANGE = "^1.4.0";
18937
+ const CLI_VERSION_RANGE = "^1.4.0";
18782
18938
  const ESBUILD_VERSION_RANGE = "^0.27.0";
18783
18939
  function renderHandler() {
18784
18940
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -21923,6 +22079,251 @@ var LogoutCommand = class LogoutCommand extends Command {
21923
22079
  }
21924
22080
  };
21925
22081
  //#endregion
22082
+ //#region src/oclif/commands/org-secrets-shared.ts
22083
+ function orgSecretsUrl(baseUrl, key) {
22084
+ const base = `${baseUrl.replace(/\/$/, "")}/org/secrets`;
22085
+ return key ? `${base}/${encodeURIComponent(key)}` : base;
22086
+ }
22087
+ function orgSecretsAuthHeaders(requestHeaders, apiKey) {
22088
+ return {
22089
+ ...requestHeaders ?? {},
22090
+ ...apiKey ? { authorization: `Bearer ${apiKey}` } : {}
22091
+ };
22092
+ }
22093
+ async function orgSecretsErrorPayload(response) {
22094
+ if ((response.headers.get("content-type")?.toLowerCase() ?? "").includes("application/json")) return response.json().catch(() => ({
22095
+ code: "http_error",
22096
+ message: `HTTP ${response.status} ${response.statusText}`.trim()
22097
+ }));
22098
+ return {
22099
+ code: "http_error",
22100
+ message: (await response.text().catch(() => "")).trim() || `HTTP ${response.status} ${response.statusText}`.trim()
22101
+ };
22102
+ }
22103
+ async function runOrgSecretsRequest(fetchImpl, baseUrl, headers, op) {
22104
+ const url = op.kind === "remove" ? orgSecretsUrl(baseUrl, op.key) : orgSecretsUrl(baseUrl);
22105
+ const init = op.kind === "set" ? {
22106
+ method: "POST",
22107
+ headers: {
22108
+ ...headers,
22109
+ "content-type": "application/json"
22110
+ },
22111
+ body: JSON.stringify({
22112
+ key: op.key,
22113
+ value: op.value
22114
+ })
22115
+ } : op.kind === "remove" ? {
22116
+ method: "DELETE",
22117
+ headers
22118
+ } : { headers };
22119
+ let response;
22120
+ try {
22121
+ response = await fetchImpl(url, init);
22122
+ } catch (error) {
22123
+ return {
22124
+ kind: "error",
22125
+ payload: extractErrorPayload(error)
22126
+ };
22127
+ }
22128
+ if (!response.ok) return {
22129
+ kind: "error",
22130
+ payload: extractErrorPayload(await orgSecretsErrorPayload(response))
22131
+ };
22132
+ if (op.kind === "remove") return {
22133
+ kind: "ok",
22134
+ data: null
22135
+ };
22136
+ const body = await response.json().catch(() => ({}));
22137
+ if (op.kind === "list") return {
22138
+ kind: "ok",
22139
+ data: body.data?.items ?? []
22140
+ };
22141
+ return {
22142
+ kind: "ok",
22143
+ data: body.data ?? {}
22144
+ };
22145
+ }
22146
+ //#endregion
22147
+ //#region src/oclif/commands/org-secrets-list.ts
22148
+ var OrgSecretsListCommand = class OrgSecretsListCommand extends Command {
22149
+ static description = `List your organization's global secrets.
22150
+
22151
+ Global secrets apply to every function in the org and are read as
22152
+ \`env.<KEY>\` in handlers. Only the keys and timestamps are returned;
22153
+ the values are encrypted at rest and never surfaced.`;
22154
+ static summary = "List global secrets (keys only; values never returned)";
22155
+ static examples = ["<%= config.bin %> org secrets list"];
22156
+ static flags = {
22157
+ "api-key": Flags.string({
22158
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
22159
+ env: "PRIMITIVE_API_KEY"
22160
+ }),
22161
+ "api-base-url": Flags.string({
22162
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
22163
+ env: "PRIMITIVE_API_BASE_URL",
22164
+ hidden: true
22165
+ }),
22166
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
22167
+ };
22168
+ async run() {
22169
+ const { flags } = await this.parse(OrgSecretsListCommand);
22170
+ await runWithTiming(flags.time, async () => {
22171
+ const { auth, baseUrlOverridden, requestConfig } = await createAuthenticatedCliApiClient({
22172
+ apiKey: flags["api-key"],
22173
+ apiBaseUrl: flags["api-base-url"],
22174
+ configDir: this.config.configDir
22175
+ });
22176
+ const outcome = await runOrgSecretsRequest(fetch, requestConfig.resolvedApiBaseUrl, orgSecretsAuthHeaders(requestConfig.headers, auth.apiKey), { kind: "list" });
22177
+ if (outcome.kind === "error") {
22178
+ writeErrorWithHints(outcome.payload);
22179
+ surfaceUnauthorizedHint({
22180
+ auth,
22181
+ baseUrlOverridden,
22182
+ configDir: this.config.configDir,
22183
+ payload: outcome.payload
22184
+ });
22185
+ process.exitCode = 1;
22186
+ return;
22187
+ }
22188
+ this.log(JSON.stringify(outcome.data, null, 2));
22189
+ });
22190
+ }
22191
+ };
22192
+ //#endregion
22193
+ //#region src/oclif/commands/org-secrets-remove.ts
22194
+ var OrgSecretsRemoveCommand = class OrgSecretsRemoveCommand extends Command {
22195
+ static description = `Delete a global secret.
22196
+
22197
+ Deployed functions keep the previous value until each is redeployed. A
22198
+ function that defines its own secret of the same name is unaffected.`;
22199
+ static summary = "Delete a global secret";
22200
+ static aliases = ["org:secrets:delete"];
22201
+ static examples = ["<%= config.bin %> org secrets remove --key STRIPE_KEY"];
22202
+ static flags = {
22203
+ "api-key": Flags.string({
22204
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
22205
+ env: "PRIMITIVE_API_KEY"
22206
+ }),
22207
+ "api-base-url": Flags.string({
22208
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
22209
+ env: "PRIMITIVE_API_BASE_URL",
22210
+ hidden: true
22211
+ }),
22212
+ key: Flags.string({
22213
+ description: "Global secret key to delete.",
22214
+ required: true
22215
+ }),
22216
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
22217
+ };
22218
+ async run() {
22219
+ const { flags } = await this.parse(OrgSecretsRemoveCommand);
22220
+ await runWithTiming(flags.time, async () => {
22221
+ const { auth, baseUrlOverridden, requestConfig } = await createAuthenticatedCliApiClient({
22222
+ apiKey: flags["api-key"],
22223
+ apiBaseUrl: flags["api-base-url"],
22224
+ configDir: this.config.configDir
22225
+ });
22226
+ const outcome = await runOrgSecretsRequest(fetch, requestConfig.resolvedApiBaseUrl, orgSecretsAuthHeaders(requestConfig.headers, auth.apiKey), {
22227
+ kind: "remove",
22228
+ key: flags.key
22229
+ });
22230
+ if (outcome.kind === "error") {
22231
+ writeErrorWithHints(outcome.payload);
22232
+ surfaceUnauthorizedHint({
22233
+ auth,
22234
+ baseUrlOverridden,
22235
+ configDir: this.config.configDir,
22236
+ payload: outcome.payload
22237
+ });
22238
+ process.exitCode = 1;
22239
+ return;
22240
+ }
22241
+ process.stderr.write(`Global secret ${flags.key} deleted. Deployed functions keep the previous value until each is redeployed.\n`);
22242
+ });
22243
+ }
22244
+ };
22245
+ //#endregion
22246
+ //#region src/oclif/commands/org-secrets-set.ts
22247
+ var OrgSecretsSetCommand = class OrgSecretsSetCommand extends Command {
22248
+ static description = `Set a global secret available to every function as \`env.<KEY>\`.
22249
+
22250
+ Global secrets are read into each function at deploy time, so a new or
22251
+ changed value lands in a function only on its next redeploy. A function
22252
+ secret with the same key overrides the global value for that function.
22253
+
22254
+ Keys must match \`^[A-Z_][A-Z0-9_]*$\` (uppercase letters, digits,
22255
+ underscores; first character a letter or underscore). System-managed keys
22256
+ are reserved and rejected. ${SINGLE_SECRET_VALUE_SOURCE_DESCRIPTION}`;
22257
+ static summary = "Set a global secret shared across all functions";
22258
+ static examples = [
22259
+ "<%= config.bin %> org secrets set --key STRIPE_KEY --value sk_live_...",
22260
+ "<%= config.bin %> org secrets set --key OPENAI_KEY --value-from-env OPENAI_KEY",
22261
+ "printf '%s' \"$OPENAI_KEY\" | <%= config.bin %> org secrets set --key OPENAI_KEY --stdin"
22262
+ ];
22263
+ static flags = {
22264
+ "api-key": Flags.string({
22265
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
22266
+ env: "PRIMITIVE_API_KEY"
22267
+ }),
22268
+ "api-base-url": Flags.string({
22269
+ description: "Override the primary API base URL. Internal testing only; not documented to customers.",
22270
+ env: "PRIMITIVE_API_BASE_URL",
22271
+ hidden: true
22272
+ }),
22273
+ key: Flags.string({
22274
+ description: "Secret key. Uppercase letters, digits, underscores; must start with a letter or underscore. System-managed keys are reserved.",
22275
+ required: true
22276
+ }),
22277
+ value: Flags.string({ description: "Secret value (up to 4096 UTF-8 bytes). Encrypted at rest. Visible in shell history and process argv; prefer a non-argv source for sensitive values." }),
22278
+ "value-from-env": Flags.string({ description: "Environment variable to read as the secret value. Example: --value-from-env OPENAI_KEY reads process.env.OPENAI_KEY." }),
22279
+ "value-file": Flags.string({ description: "UTF-8 file to read as the secret value. The full file contents become the value." }),
22280
+ "value-from-env-file": Flags.string({ description: "Dotenv-style file to read as the secret value. Use FILE to read --key from that file, or FILE:KEY to read a different key." }),
22281
+ stdin: Flags.boolean({ description: "Read the secret value from stdin. A single trailing line ending is stripped." }),
22282
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
22283
+ };
22284
+ async run() {
22285
+ const { flags } = await this.parse(OrgSecretsSetCommand);
22286
+ await runWithTiming(flags.time, async () => {
22287
+ const { auth, baseUrlOverridden, requestConfig } = await createAuthenticatedCliApiClient({
22288
+ apiKey: flags["api-key"],
22289
+ apiBaseUrl: flags["api-base-url"],
22290
+ configDir: this.config.configDir
22291
+ });
22292
+ const resolved = resolveSingleSecretValue({
22293
+ key: flags.key,
22294
+ value: flags.value,
22295
+ valueFile: flags["value-file"],
22296
+ valueFromEnv: flags["value-from-env"],
22297
+ valueFromEnvFile: flags["value-from-env-file"],
22298
+ stdin: flags.stdin
22299
+ });
22300
+ if (resolved.kind === "error") {
22301
+ process.stderr.write(`${resolved.message}\n`);
22302
+ process.exitCode = 1;
22303
+ return;
22304
+ }
22305
+ const outcome = await runOrgSecretsRequest(fetch, requestConfig.resolvedApiBaseUrl, orgSecretsAuthHeaders(requestConfig.headers, auth.apiKey), {
22306
+ kind: "set",
22307
+ key: flags.key,
22308
+ value: resolved.value
22309
+ });
22310
+ if (outcome.kind === "error") {
22311
+ writeErrorWithHints(outcome.payload);
22312
+ surfaceUnauthorizedHint({
22313
+ auth,
22314
+ baseUrlOverridden,
22315
+ configDir: this.config.configDir,
22316
+ payload: outcome.payload
22317
+ });
22318
+ process.exitCode = 1;
22319
+ return;
22320
+ }
22321
+ this.log(JSON.stringify(outcome.data, null, 2));
22322
+ process.stderr.write(`Global secret ${flags.key} saved. Deployed functions pick it up on their next redeploy; a function secret of the same name overrides it.\n`);
22323
+ });
22324
+ }
22325
+ };
22326
+ //#endregion
21926
22327
  //#region src/oclif/message-body-sources.ts
21927
22328
  function defaultReadFile(path) {
21928
22329
  return readFileSync(path, "utf8");
@@ -23358,6 +23759,7 @@ const OVERRIDDEN_OPERATION_IDS = new Set([
23358
23759
  const generatedCommands = Object.fromEntries(operationManifest.filter((operation) => !OVERRIDDEN_OPERATION_IDS.has(operationId(operation))).map((operation) => [operationId(operation), createOperationCommand(operation)]));
23359
23760
  const COMMANDS = {
23360
23761
  completion: CompletionCommand,
23762
+ "agent:upgrade": AgentUpgradeCommand,
23361
23763
  "list-operations": ListOperationsCommand,
23362
23764
  config: ConfigCommand,
23363
23765
  "config:list": ConfigListCommand,
@@ -23410,6 +23812,9 @@ const COMMANDS = {
23410
23812
  "functions:deploy": FunctionsDeployCommand,
23411
23813
  "functions:redeploy": FunctionsRedeployCommand,
23412
23814
  "functions:set-secret": FunctionsSetSecretCommand,
23815
+ "org:secrets:list": OrgSecretsListCommand,
23816
+ "org:secrets:set": OrgSecretsSetCommand,
23817
+ "org:secrets:remove": OrgSecretsRemoveCommand,
23413
23818
  "functions:test": FunctionsTestFunctionCommand,
23414
23819
  "functions:test-function": FunctionsTestFunctionCommand,
23415
23820
  "functions:route-set": FunctionsRouteSetCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitivedotdev/cli",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -97,6 +97,12 @@
97
97
  },
98
98
  "functions": {
99
99
  "description": "Deploy JavaScript handlers that run on inbound mail. Prefer `primitive functions templates`, `primitive functions init`, `primitive functions deploy`, `primitive functions redeploy`, `primitive functions list`, `primitive functions get`, `primitive functions logs`, and `primitive functions set-secret`; generated API names remain available for compatibility."
100
+ },
101
+ "org": {
102
+ "description": "Manage org-level (global) resources shared across functions"
103
+ },
104
+ "org:secrets": {
105
+ "description": "Global secrets shared across every function. Use `primitive org secrets list|set|remove`. Changes land in a function on its next redeploy; a function secret of the same name overrides the global."
100
106
  }
101
107
  },
102
108
  "topicSeparator": " "