primcli 1.14.1 → 1.14.3

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.
package/README.md CHANGED
@@ -73,6 +73,26 @@ primitive deliveries replay --id <delivery-id>
73
73
 
74
74
  Generated API commands remain available for compatibility and full schema parity, for example `primitive emails:list-emails` and `primitive sending:reply-to-email`.
75
75
 
76
+ ## Recipient routing
77
+
78
+ Bind a recipient address to a destination so inbound mail resolves to a single
79
+ endpoint. Pass `--function` to route an address to a function (its route-target
80
+ endpoint is created in the same call, enabling per-address routing like
81
+ `alice@acme.com -> functionA`), or `--endpoint` for an existing endpoint.
82
+
83
+ ```bash
84
+ primitive routes add alice@acme.com --function <function-id>
85
+ primitive routes add 'support+*@acme.com' --match wildcard --endpoint <endpoint-id>
86
+ primitive routes list
87
+ primitive routes test alice@acme.com # preview where an address resolves, with the rule trace
88
+ primitive routes update <route-id> --priority 5
89
+ primitive routes reorder --set <route-id>=10 --set <other-id>=20
90
+ primitive routes remove <route-id>
91
+ ```
92
+
93
+ Recipient routing is gated by an organization entitlement; routes are inert
94
+ until it is enabled.
95
+
76
96
  ## x402 payments
77
97
 
78
98
  The `primitive payments` command group drives non-custodial x402 USDC payments. One agent registers a payout address and requests a payment; the paying agent signs locally with its own wallet key and settles. The key never leaves your machine. Networks are `base` and `base-sepolia`. Amounts take a human USDC value (`--amount-usdc 0.01`) or token base units (`--amount 10000`, since USDC has 6 decimals). Your org is resolved automatically from your API key, so payout registration takes no org flag.
@@ -1124,6 +1124,21 @@ function detectPrimitiveKeyEnvMisname(env = process.env) {
1124
1124
  if ((primitiveKey?.length ?? 0) > 0 && (primitiveApiKey?.length ?? 0) === 0) return "PRIMITIVE_KEY is set but the CLI reads PRIMITIVE_API_KEY. Rename your env var, or re-run with PRIMITIVE_API_KEY=$PRIMITIVE_KEY.";
1125
1125
  return null;
1126
1126
  }
1127
+ /**
1128
+ * True when a usable OAuth login session is saved on disk. Used by `pay-email`
1129
+ * to decide whether it can safely ignore an env-only `PRIMITIVE_API_KEY` in
1130
+ * favor of the payer's logged-in account (which owns the inbound challenge
1131
+ * inbox). Returns false when nothing is stored or the stored credential is
1132
+ * unreadable/legacy, so the caller falls back to whatever key did resolve
1133
+ * rather than silently dropping the user's only auth.
1134
+ */
1135
+ function hasStoredCliLogin(configDir) {
1136
+ try {
1137
+ return loadCliCredentials(configDir) !== null;
1138
+ } catch {
1139
+ return false;
1140
+ }
1141
+ }
1127
1142
  function resolveCliAuth(params) {
1128
1143
  const apiKey = params.apiKey?.trim();
1129
1144
  const apiBaseUrl = normalizeApiBaseUrl(params.apiBaseUrl);
@@ -1327,4 +1342,4 @@ function redactCliEnvironment(environment) {
1327
1342
  };
1328
1343
  }
1329
1344
  //#endregion
1330
- export { PrimitiveApiClient as A, saveCliCredentials as C, loadActiveChatState as D, deleteChatState as E, createConfig as M, loadChatConversationByLocalId as O, resolveCliAuth as S, chatStatePath as T, deleteCliCredentials as _, normalizeCliEnvironmentName as a, loadCliCredentials as b, resolveConfigEnvironment as c, validateCliHeaderName as d, validateCliHeaderValue as f, credentialsPath as g, credentialsLockPath as h, loadCliConfig as i, createClient as j, saveActiveChatState as k, saveCliConfig as l, cliAccessTokenExpiresAt as m, deleteCliConfig as n, redactCliEnvironment as o, acquireCliCredentialsLock as p, emptyCliConfig as r, removeCliEnvironment as s, DEFAULT_ENVIRONMENT as t, upsertCliEnvironment as u, deleteCliCredentialsLock as v, saveSignupCredentials as w, normalizeApiBaseUrl as x, detectPrimitiveKeyEnvMisname as y };
1345
+ export { saveActiveChatState as A, resolveCliAuth as C, deleteChatState as D, chatStatePath as E, createClient as M, createConfig as N, loadActiveChatState as O, normalizeApiBaseUrl as S, saveSignupCredentials as T, deleteCliCredentials as _, normalizeCliEnvironmentName as a, hasStoredCliLogin as b, resolveConfigEnvironment as c, validateCliHeaderName as d, validateCliHeaderValue as f, credentialsPath as g, credentialsLockPath as h, loadCliConfig as i, PrimitiveApiClient as j, loadChatConversationByLocalId as k, saveCliConfig as l, cliAccessTokenExpiresAt as m, deleteCliConfig as n, redactCliEnvironment as o, acquireCliCredentialsLock as p, emptyCliConfig as r, removeCliEnvironment as s, DEFAULT_ENVIRONMENT as t, upsertCliEnvironment as u, deleteCliCredentialsLock as v, saveCliCredentials as w, loadCliCredentials as x, detectPrimitiveKeyEnvMisname as y };
@@ -1,4 +1,4 @@
1
- import { A as PrimitiveApiClient, C as saveCliCredentials, D as loadActiveChatState, E as deleteChatState, M as createConfig, O as loadChatConversationByLocalId, S as resolveCliAuth, T as chatStatePath, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as loadCliCredentials, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as createClient, k as saveActiveChatState, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as saveSignupCredentials, x as normalizeApiBaseUrl, y as detectPrimitiveKeyEnvMisname } from "../cli-config-B5hrwe8q.js";
1
+ import { A as saveActiveChatState, C as resolveCliAuth, D as deleteChatState, E as chatStatePath, M as createClient, N as createConfig, O as loadActiveChatState, S as normalizeApiBaseUrl, T as saveSignupCredentials, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as hasStoredCliLogin, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as PrimitiveApiClient, k as loadChatConversationByLocalId, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as saveCliCredentials, x as loadCliCredentials, y as detectPrimitiveKeyEnvMisname } from "../cli-config-C-7V83Qd.js";
2
2
  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";
@@ -3607,6 +3607,7 @@ const openapiDocument = {
3607
3607
  },
3608
3608
  "400": { "$ref": "#/components/responses/ValidationError" },
3609
3609
  "401": { "$ref": "#/components/responses/Unauthorized" },
3610
+ "403": { "$ref": "#/components/responses/Forbidden" },
3610
3611
  "409": { "$ref": "#/components/responses/Conflict" }
3611
3612
  }
3612
3613
  }
@@ -3676,6 +3677,7 @@ const openapiDocument = {
3676
3677
  },
3677
3678
  "400": { "$ref": "#/components/responses/ValidationError" },
3678
3679
  "401": { "$ref": "#/components/responses/Unauthorized" },
3680
+ "403": { "$ref": "#/components/responses/Forbidden" },
3679
3681
  "404": { "$ref": "#/components/responses/NotFound" },
3680
3682
  "409": { "$ref": "#/components/responses/Conflict" }
3681
3683
  }
@@ -28619,6 +28621,23 @@ async function pollForSettlementInteraction(params) {
28619
28621
  function shouldDeriveChallenge(flags) {
28620
28622
  return flags.challenge === void 0 && flags["challenge-file"] === void 0;
28621
28623
  }
28624
+ /**
28625
+ * Decide the api-key `pay-email` should authenticate its inbox reads with, given
28626
+ * the parsed flag value, whether `--api-key` was passed explicitly on the
28627
+ * command line (vs sourced from `PRIMITIVE_API_KEY`), and whether a logged-in
28628
+ * session exists. Returns the key to use, or `undefined` to fall through to the
28629
+ * stored login. See the block comment above for the precedence rationale.
28630
+ */
28631
+ function resolvePayerInboxApiKey(params) {
28632
+ if (params.apiKeyFlag !== void 0 && params.apiKeyFromEnvOnly && params.hasStoredLogin) return {
28633
+ apiKey: void 0,
28634
+ usedStoredLoginOverEnvKey: true
28635
+ };
28636
+ return {
28637
+ apiKey: params.apiKeyFlag,
28638
+ usedStoredLoginOverEnvKey: false
28639
+ };
28640
+ }
28622
28641
  /** The exact part name + content type the inbound matcher requires. */
28623
28642
  const INTERACTION_PART_FILENAME = "interaction.json";
28624
28643
  const INTERACTION_PART_CONTENT_TYPE = "application/json";
@@ -28744,9 +28763,17 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28744
28763
  time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
28745
28764
  };
28746
28765
  async run() {
28747
- const { flags } = await this.parse(PaymentsPayEmailCommand);
28766
+ const { flags, raw } = await this.parse(PaymentsPayEmailCommand);
28767
+ const apiKeyFlagExplicit = raw.some((token) => token.type === "flag" && token.flag === "api-key");
28768
+ const apiKeyFromEnvOnly = flags["api-key"] !== void 0 && !apiKeyFlagExplicit;
28769
+ const { apiKey: effectiveApiKey, usedStoredLoginOverEnvKey } = resolvePayerInboxApiKey({
28770
+ apiKeyFlag: flags["api-key"],
28771
+ apiKeyFromEnvOnly,
28772
+ hasStoredLogin: hasStoredCliLogin(this.config.configDir)
28773
+ });
28774
+ if (usedStoredLoginOverEnvKey) process.stderr.write("PRIMITIVE_API_KEY is set, but pay-email reads the inbound challenge from your logged-in account's inbox, so it is using your saved login for this command. Pass --api-key explicitly to override.\n");
28748
28775
  const { apiClient, auth, baseUrlOverridden, requestConfig } = await createAuthenticatedCliApiClient({
28749
- apiKey: flags["api-key"],
28776
+ apiKey: effectiveApiKey,
28750
28777
  apiBaseUrl: flags["api-base-url"],
28751
28778
  configDir: this.config.configDir
28752
28779
  });
@@ -28768,6 +28795,7 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28768
28795
  ...authFailureContext,
28769
28796
  payload: errorPayload
28770
28797
  });
28798
+ if (extractErrorCode(errorPayload) === API_ERROR_CODES.notFound && auth.source === "flag-or-env") process.stderr.write("The inbound challenge email was not found for the org this API key belongs to. pay-email reads the challenge from the PAYER account's inbox: unset PRIMITIVE_API_KEY (or pass --api-key for the payer's org, or run `primitive signin` as the payer) and retry.\n");
28771
28799
  process.exitCode = 1;
28772
28800
  return;
28773
28801
  }
@@ -28795,7 +28823,7 @@ var PaymentsPayEmailCommand = class PaymentsPayEmailCommand extends Command {
28795
28823
  challenge,
28796
28824
  privateKey: flags["private-key"] ?? "",
28797
28825
  resolvedApiBaseUrl: auth.apiBaseUrl,
28798
- apiKey: flags["api-key"]
28826
+ apiKey: effectiveApiKey
28799
28827
  });
28800
28828
  } catch (error) {
28801
28829
  reportX402Error(error, authFailureContext);
@@ -29163,6 +29191,434 @@ var ReplyCommand = class ReplyCommand extends Command {
29163
29191
  }
29164
29192
  };
29165
29193
  //#endregion
29194
+ //#region src/oclif/commands/routes-add.ts
29195
+ function resolveCreateTarget(flags) {
29196
+ if (flags.function) return { function_id: flags.function };
29197
+ if (flags.endpoint) return { endpoint_id: flags.endpoint };
29198
+ return { error: "Provide exactly one of --function (route to a function) or --endpoint (an existing endpoint)." };
29199
+ }
29200
+ var RoutesAddCommand = class RoutesAddCommand extends Command {
29201
+ static description = `Create a recipient route binding an address pattern to a destination.
29202
+
29203
+ Provide exactly one of --function (route to a function; its route-target
29204
+ endpoint is created in the same transaction) or --endpoint (an existing
29205
+ endpoint). The match type defaults to exact; use --match wildcard for
29206
+ patterns like 'support+*@acme.com'.`;
29207
+ static summary = "Add a recipient route";
29208
+ static examples = [
29209
+ "<%= config.bin %> routes add alice@acme.com --function <fn-id>",
29210
+ "<%= config.bin %> routes add 'support+*@acme.com' --match wildcard --function <fn-id>",
29211
+ "<%= config.bin %> routes add billing@acme.com --endpoint <endpoint-id> --priority 10"
29212
+ ];
29213
+ static args = { pattern: Args.string({
29214
+ description: "Recipient address or wildcard pattern (e.g. alice@acme.com).",
29215
+ required: true
29216
+ }) };
29217
+ static flags = {
29218
+ "api-key": Flags.string({
29219
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
29220
+ env: "PRIMITIVE_API_KEY"
29221
+ }),
29222
+ "api-base-url": Flags.string({
29223
+ description: API_BASE_URL_FLAG_DESCRIPTION,
29224
+ env: "PRIMITIVE_API_BASE_URL",
29225
+ hidden: true
29226
+ }),
29227
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION }),
29228
+ function: Flags.string({
29229
+ description: "Function id to route this address to (its route-target endpoint is minted if needed). Mutually exclusive with --endpoint.",
29230
+ exclusive: ["endpoint"]
29231
+ }),
29232
+ endpoint: Flags.string({
29233
+ description: "Existing endpoint id to route to. Mutually exclusive with --function.",
29234
+ exclusive: ["function"]
29235
+ }),
29236
+ match: Flags.string({
29237
+ description: "Match type for the pattern. regex requires the Power plan and may be rejected if not enabled.",
29238
+ options: [
29239
+ "exact",
29240
+ "wildcard",
29241
+ "regex"
29242
+ ],
29243
+ default: "exact"
29244
+ }),
29245
+ domain: Flags.string({ description: "Scope the route to this domain id (defaults to the pattern's domain)." }),
29246
+ priority: Flags.integer({ description: "Evaluation order within a scope; lower is checked first." }),
29247
+ disabled: Flags.boolean({ description: "Create the route disabled." })
29248
+ };
29249
+ async run() {
29250
+ const { args, flags } = await this.parse(RoutesAddCommand);
29251
+ const target = resolveCreateTarget(flags);
29252
+ if ("error" in target) {
29253
+ process.stderr.write(`${target.error}\n`);
29254
+ process.exitCode = 1;
29255
+ return;
29256
+ }
29257
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
29258
+ apiKey: flags["api-key"],
29259
+ apiBaseUrl: flags["api-base-url"],
29260
+ configDir: this.config.configDir
29261
+ });
29262
+ await runWithTiming(flags.time, async () => {
29263
+ const result = await createRoute({
29264
+ client: apiClient.client,
29265
+ body: {
29266
+ match_type: flags.match,
29267
+ pattern: args.pattern,
29268
+ ...target,
29269
+ ...flags.domain ? { domain_id: flags.domain } : {},
29270
+ ...flags.priority != null ? { priority: flags.priority } : {},
29271
+ ...flags.disabled ? { enabled: false } : {}
29272
+ },
29273
+ responseStyle: "fields"
29274
+ });
29275
+ if (result.error) {
29276
+ const payload = extractErrorPayload(result.error);
29277
+ writeErrorWithHints(payload);
29278
+ surfaceUnauthorizedHint({
29279
+ auth,
29280
+ baseUrlOverridden,
29281
+ configDir: this.config.configDir,
29282
+ payload
29283
+ });
29284
+ process.exitCode = 1;
29285
+ return;
29286
+ }
29287
+ this.log(JSON.stringify(result.data.data, null, 2));
29288
+ });
29289
+ }
29290
+ };
29291
+ //#endregion
29292
+ //#region src/oclif/commands/routes-list.ts
29293
+ var RoutesListCommand = class RoutesListCommand extends Command {
29294
+ static description = "List the org's recipient routes in evaluation order.";
29295
+ static summary = "List recipient routes";
29296
+ static examples = ["<%= config.bin %> routes list"];
29297
+ static flags = {
29298
+ "api-key": Flags.string({
29299
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
29300
+ env: "PRIMITIVE_API_KEY"
29301
+ }),
29302
+ "api-base-url": Flags.string({
29303
+ description: API_BASE_URL_FLAG_DESCRIPTION,
29304
+ env: "PRIMITIVE_API_BASE_URL",
29305
+ hidden: true
29306
+ }),
29307
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
29308
+ };
29309
+ async run() {
29310
+ const { flags } = await this.parse(RoutesListCommand);
29311
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
29312
+ apiKey: flags["api-key"],
29313
+ apiBaseUrl: flags["api-base-url"],
29314
+ configDir: this.config.configDir
29315
+ });
29316
+ await runWithTiming(flags.time, async () => {
29317
+ const result = await listRoutes({
29318
+ client: apiClient.client,
29319
+ responseStyle: "fields"
29320
+ });
29321
+ if (result.error) {
29322
+ const payload = extractErrorPayload(result.error);
29323
+ writeErrorWithHints(payload);
29324
+ surfaceUnauthorizedHint({
29325
+ auth,
29326
+ baseUrlOverridden,
29327
+ configDir: this.config.configDir,
29328
+ payload
29329
+ });
29330
+ process.exitCode = 1;
29331
+ return;
29332
+ }
29333
+ this.log(JSON.stringify(result.data.data, null, 2));
29334
+ });
29335
+ }
29336
+ };
29337
+ //#endregion
29338
+ //#region src/oclif/commands/routes-remove.ts
29339
+ var RoutesRemoveCommand = class RoutesRemoveCommand extends Command {
29340
+ static description = "Delete a recipient route by id. Mail matching its pattern then falls back to the domain or organization default.";
29341
+ static summary = "Remove a recipient route";
29342
+ static examples = ["<%= config.bin %> routes remove <route-id>"];
29343
+ static args = { id: Args.string({
29344
+ description: "Route id (UUID) to delete.",
29345
+ required: true
29346
+ }) };
29347
+ static flags = {
29348
+ "api-key": Flags.string({
29349
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
29350
+ env: "PRIMITIVE_API_KEY"
29351
+ }),
29352
+ "api-base-url": Flags.string({
29353
+ description: API_BASE_URL_FLAG_DESCRIPTION,
29354
+ env: "PRIMITIVE_API_BASE_URL",
29355
+ hidden: true
29356
+ }),
29357
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
29358
+ };
29359
+ async run() {
29360
+ const { args, flags } = await this.parse(RoutesRemoveCommand);
29361
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
29362
+ apiKey: flags["api-key"],
29363
+ apiBaseUrl: flags["api-base-url"],
29364
+ configDir: this.config.configDir
29365
+ });
29366
+ await runWithTiming(flags.time, async () => {
29367
+ const result = await deleteRoute({
29368
+ client: apiClient.client,
29369
+ path: { id: args.id },
29370
+ responseStyle: "fields"
29371
+ });
29372
+ if (result.error) {
29373
+ const payload = extractErrorPayload(result.error);
29374
+ writeErrorWithHints(payload);
29375
+ surfaceUnauthorizedHint({
29376
+ auth,
29377
+ baseUrlOverridden,
29378
+ configDir: this.config.configDir,
29379
+ payload
29380
+ });
29381
+ process.exitCode = 1;
29382
+ return;
29383
+ }
29384
+ this.log(JSON.stringify(result.data.data, null, 2));
29385
+ });
29386
+ }
29387
+ };
29388
+ //#endregion
29389
+ //#region src/oclif/commands/routes-reorder.ts
29390
+ function parseReorderUpdates(set) {
29391
+ const updates = [];
29392
+ const seen = /* @__PURE__ */ new Set();
29393
+ for (const pair of set) {
29394
+ const eq = pair.lastIndexOf("=");
29395
+ const id = (eq >= 0 ? pair.slice(0, eq).trim() : "").toLowerCase();
29396
+ const priority = eq >= 0 ? Number(pair.slice(eq + 1).trim()) : NaN;
29397
+ if (!id || !Number.isInteger(priority) || priority < 0) return { error: `Invalid --set value "${pair}"; expected <route-id>=<priority> with a non-negative integer priority.` };
29398
+ if (seen.has(id)) return { error: `Route ${id} appears more than once in --set; specify each route at most once.` };
29399
+ seen.add(id);
29400
+ updates.push({
29401
+ id,
29402
+ priority
29403
+ });
29404
+ }
29405
+ return { updates };
29406
+ }
29407
+ var RoutesReorderCommand = class RoutesReorderCommand extends Command {
29408
+ static description = `Set the priority of one or more recipient routes in a single call.
29409
+
29410
+ Priority controls evaluation order within a scope; lower is checked first.
29411
+ Pass --set <route-id>=<priority> once per route you want to move.`;
29412
+ static summary = "Reorder recipient routes";
29413
+ static examples = ["<%= config.bin %> routes reorder --set <route-id>=10", "<%= config.bin %> routes reorder --set <id-a>=10 --set <id-b>=20"];
29414
+ static flags = {
29415
+ "api-key": Flags.string({
29416
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
29417
+ env: "PRIMITIVE_API_KEY"
29418
+ }),
29419
+ "api-base-url": Flags.string({
29420
+ description: API_BASE_URL_FLAG_DESCRIPTION,
29421
+ env: "PRIMITIVE_API_BASE_URL",
29422
+ hidden: true
29423
+ }),
29424
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION }),
29425
+ set: Flags.string({
29426
+ description: "A route and its new priority as <route-id>=<priority>. Repeatable.",
29427
+ multiple: true,
29428
+ required: true
29429
+ })
29430
+ };
29431
+ async run() {
29432
+ const { flags } = await this.parse(RoutesReorderCommand);
29433
+ const parsed = parseReorderUpdates(flags.set);
29434
+ if ("error" in parsed) {
29435
+ process.stderr.write(`${parsed.error}\n`);
29436
+ process.exitCode = 1;
29437
+ return;
29438
+ }
29439
+ const { updates } = parsed;
29440
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
29441
+ apiKey: flags["api-key"],
29442
+ apiBaseUrl: flags["api-base-url"],
29443
+ configDir: this.config.configDir
29444
+ });
29445
+ await runWithTiming(flags.time, async () => {
29446
+ const result = await reorderRoutes({
29447
+ client: apiClient.client,
29448
+ body: { updates },
29449
+ responseStyle: "fields"
29450
+ });
29451
+ if (result.error) {
29452
+ const payload = extractErrorPayload(result.error);
29453
+ writeErrorWithHints(payload);
29454
+ surfaceUnauthorizedHint({
29455
+ auth,
29456
+ baseUrlOverridden,
29457
+ configDir: this.config.configDir,
29458
+ payload
29459
+ });
29460
+ process.exitCode = 1;
29461
+ return;
29462
+ }
29463
+ this.log(JSON.stringify(result.data.data, null, 2));
29464
+ });
29465
+ }
29466
+ };
29467
+ //#endregion
29468
+ //#region src/oclif/commands/routes-test.ts
29469
+ var RoutesTestCommand = class RoutesTestCommand extends Command {
29470
+ static description = `Simulate routing for a recipient address.
29471
+
29472
+ Shows the resolved destination and a trace of every rule evaluated (which
29473
+ matched or missed, and why). Does not create or change anything.`;
29474
+ static summary = "Simulate routing for a recipient";
29475
+ static examples = ["<%= config.bin %> routes test alice@acme.com", "<%= config.bin %> routes test bounce@acme.com --event-type email.bounced"];
29476
+ static args = { recipient: Args.string({
29477
+ description: "Recipient address to simulate (e.g. alice@acme.com).",
29478
+ required: true
29479
+ }) };
29480
+ static flags = {
29481
+ "api-key": Flags.string({
29482
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
29483
+ env: "PRIMITIVE_API_KEY"
29484
+ }),
29485
+ "api-base-url": Flags.string({
29486
+ description: API_BASE_URL_FLAG_DESCRIPTION,
29487
+ env: "PRIMITIVE_API_BASE_URL",
29488
+ hidden: true
29489
+ }),
29490
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION }),
29491
+ "event-type": Flags.string({ description: "Event type to model (defaults to email.received)." })
29492
+ };
29493
+ async run() {
29494
+ const { args, flags } = await this.parse(RoutesTestCommand);
29495
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
29496
+ apiKey: flags["api-key"],
29497
+ apiBaseUrl: flags["api-base-url"],
29498
+ configDir: this.config.configDir
29499
+ });
29500
+ await runWithTiming(flags.time, async () => {
29501
+ const result = await simulateRoute({
29502
+ client: apiClient.client,
29503
+ body: {
29504
+ recipient: args.recipient,
29505
+ ...flags["event-type"] ? { event_type: flags["event-type"] } : {}
29506
+ },
29507
+ responseStyle: "fields"
29508
+ });
29509
+ if (result.error) {
29510
+ const payload = extractErrorPayload(result.error);
29511
+ writeErrorWithHints(payload);
29512
+ surfaceUnauthorizedHint({
29513
+ auth,
29514
+ baseUrlOverridden,
29515
+ configDir: this.config.configDir,
29516
+ payload
29517
+ });
29518
+ process.exitCode = 1;
29519
+ return;
29520
+ }
29521
+ this.log(JSON.stringify(result.data.data, null, 2));
29522
+ });
29523
+ }
29524
+ };
29525
+ //#endregion
29526
+ //#region src/oclif/commands/routes-update.ts
29527
+ function buildUpdateBody(flags) {
29528
+ return {
29529
+ ...flags.match ? { match_type: flags.match } : {},
29530
+ ...flags.pattern ? { pattern: flags.pattern } : {},
29531
+ ...flags.endpoint ? { endpoint_id: flags.endpoint } : {},
29532
+ ...flags.domain ? { domain_id: flags.domain } : {},
29533
+ ...flags.priority != null ? { priority: flags.priority } : {},
29534
+ ...flags.enable ? { enabled: true } : {},
29535
+ ...flags.disable ? { enabled: false } : {}
29536
+ };
29537
+ }
29538
+ var RoutesUpdateCommand = class RoutesUpdateCommand extends Command {
29539
+ static description = `Update an existing recipient route.
29540
+
29541
+ Change the pattern, match type, target endpoint, domain scope, priority, or
29542
+ enabled state. Provide at least one field; anything omitted is left as-is.`;
29543
+ static summary = "Update a recipient route";
29544
+ static examples = [
29545
+ "<%= config.bin %> routes update <route-id> --priority 5",
29546
+ "<%= config.bin %> routes update <route-id> --pattern alice@acme.com",
29547
+ "<%= config.bin %> routes update <route-id> --disable"
29548
+ ];
29549
+ static args = { id: Args.string({
29550
+ description: "Route id (UUID) to update.",
29551
+ required: true
29552
+ }) };
29553
+ static flags = {
29554
+ "api-key": Flags.string({
29555
+ description: "Primitive API key override (defaults to PRIMITIVE_API_KEY or saved OAuth login credentials)",
29556
+ env: "PRIMITIVE_API_KEY"
29557
+ }),
29558
+ "api-base-url": Flags.string({
29559
+ description: API_BASE_URL_FLAG_DESCRIPTION,
29560
+ env: "PRIMITIVE_API_BASE_URL",
29561
+ hidden: true
29562
+ }),
29563
+ time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION }),
29564
+ match: Flags.string({
29565
+ description: "New match type for the pattern.",
29566
+ options: [
29567
+ "exact",
29568
+ "wildcard",
29569
+ "regex"
29570
+ ]
29571
+ }),
29572
+ pattern: Flags.string({ description: "New recipient address or wildcard pattern." }),
29573
+ endpoint: Flags.string({ description: "New target endpoint id (UUID)." }),
29574
+ domain: Flags.string({ description: "New domain scope id (UUID)." }),
29575
+ priority: Flags.integer({ description: "New evaluation priority within a scope; lower is checked first." }),
29576
+ enable: Flags.boolean({
29577
+ description: "Enable the route.",
29578
+ exclusive: ["disable"]
29579
+ }),
29580
+ disable: Flags.boolean({
29581
+ description: "Disable the route.",
29582
+ exclusive: ["enable"]
29583
+ })
29584
+ };
29585
+ async run() {
29586
+ const { args, flags } = await this.parse(RoutesUpdateCommand);
29587
+ const body = buildUpdateBody(flags);
29588
+ if (Object.keys(body).length === 0) {
29589
+ process.stderr.write("Provide at least one field to update (--match, --pattern, --endpoint, --domain, --priority, --enable/--disable).\n");
29590
+ process.exitCode = 1;
29591
+ return;
29592
+ }
29593
+ const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
29594
+ apiKey: flags["api-key"],
29595
+ apiBaseUrl: flags["api-base-url"],
29596
+ configDir: this.config.configDir
29597
+ });
29598
+ await runWithTiming(flags.time, async () => {
29599
+ const result = await updateRoute({
29600
+ client: apiClient.client,
29601
+ path: { id: args.id },
29602
+ body,
29603
+ responseStyle: "fields"
29604
+ });
29605
+ if (result.error) {
29606
+ const payload = extractErrorPayload(result.error);
29607
+ writeErrorWithHints(payload);
29608
+ surfaceUnauthorizedHint({
29609
+ auth,
29610
+ baseUrlOverridden,
29611
+ configDir: this.config.configDir,
29612
+ payload
29613
+ });
29614
+ process.exitCode = 1;
29615
+ return;
29616
+ }
29617
+ this.log(JSON.stringify(result.data.data, null, 2));
29618
+ });
29619
+ }
29620
+ };
29621
+ //#endregion
29166
29622
  //#region src/oclif/commands/semantic-search.ts
29167
29623
  const DEFAULT_LIMIT$1 = 10;
29168
29624
  const MAX_LIMIT$1 = 100;
@@ -30481,6 +30937,12 @@ const COMMANDS = {
30481
30937
  "functions:route-unset": FunctionsRouteUnsetCommand,
30482
30938
  "functions:route-get": FunctionsRouteGetCommand,
30483
30939
  "functions:routing-topology": FunctionsRoutingTopologyCommand,
30940
+ "routes:add": RoutesAddCommand,
30941
+ "routes:list": RoutesListCommand,
30942
+ "routes:test": RoutesTestCommand,
30943
+ "routes:update": RoutesUpdateCommand,
30944
+ "routes:reorder": RoutesReorderCommand,
30945
+ "routes:remove": RoutesRemoveCommand,
30484
30946
  ...Object.fromEntries(Object.entries(CANONICAL_OPERATION_ALIASES).map(([alias, target]) => {
30485
30947
  const command = generatedCommands[target];
30486
30948
  if (!command) throw new Error(`Missing generated command target for alias ${alias}`);
@@ -1,4 +1,4 @@
1
- import { c as resolveConfigEnvironment, i as loadCliConfig, x as normalizeApiBaseUrl } from "../cli-config-B5hrwe8q.js";
1
+ import { S as normalizeApiBaseUrl, c as resolveConfigEnvironment, i as loadCliConfig } from "../cli-config-C-7V83Qd.js";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { homedir } from "node:os";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primcli",
3
- "version": "1.14.1",
3
+ "version": "1.14.3",
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,