@alter-ai/cli 0.9.1 → 0.9.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.
package/dist/cli.js CHANGED
@@ -33,7 +33,7 @@ import { join } from "path";
33
33
  // package.json
34
34
  var package_default = {
35
35
  name: "@alter-ai/cli",
36
- version: "0.9.1",
36
+ version: "0.9.2",
37
37
  description: "Command-line interface for the Alter Vault dev portal \u2014 scripted dashboard automation.",
38
38
  type: "module",
39
39
  bin: {
@@ -80,7 +80,7 @@ var package_default = {
80
80
  esbuild: "0.28.1"
81
81
  },
82
82
  devDependencies: {
83
- "@alter-ai/alter-sdk": "workspace:0.24.2",
83
+ "@alter-ai/alter-sdk": "workspace:0.25.0",
84
84
  "@alter-vault/shared-types": "workspace:0.0.1",
85
85
  "@alter-vault/shared-utils": "workspace:0.0.1",
86
86
  "@eslint/js": "9.39.4",
@@ -4743,7 +4743,14 @@ var AmbiguousGrantError = class extends BackendError {
4743
4743
  // scoped to the caller's own accessible set. Retry with the chosen
4744
4744
  // `grantId`.
4745
4745
  candidates;
4746
- constructor(message, providerId, accountIdentifiers, accountWasProvided, appUserIds, details, grantIds, candidates) {
4746
+ // True when more grants matched than the error body can carry. It bounds
4747
+ // EVERY disambiguator on this error — `candidates`, `accountIdentifiers`,
4748
+ // `appUserIds` and `grantIds` all come from the same capped set — so a
4749
+ // grant, account or user missing from any of them is not proof it does not
4750
+ // exist. Narrow the request or call `listGrants()` for the authoritative
4751
+ // set.
4752
+ candidatesTruncated;
4753
+ constructor(message, providerId, accountIdentifiers, accountWasProvided, appUserIds, details, grantIds, candidates, candidatesTruncated) {
4747
4754
  super(message, details);
4748
4755
  this.name = "AmbiguousGrantError";
4749
4756
  this.providerId = providerId;
@@ -4752,6 +4759,7 @@ var AmbiguousGrantError = class extends BackendError {
4752
4759
  this.appUserIds = appUserIds ?? [];
4753
4760
  this.grantIds = grantIds ?? [];
4754
4761
  this.candidates = candidates ?? [];
4762
+ this.candidatesTruncated = candidatesTruncated ?? false;
4755
4763
  }
4756
4764
  };
4757
4765
  var NoDelegatedGrantError = class extends BackendError {
@@ -5724,6 +5732,14 @@ var ConnectSession = class {
5724
5732
  * Python SDK's scope_constraint_warnings coercion (parity contract).
5725
5733
  */
5726
5734
  scopeConstraintWarnings;
5735
+ /**
5736
+ * True when the session names exactly one provider: the hosted UI shows no
5737
+ * picker, confirmation screen or Alter-branded page, and the browser widget
5738
+ * can open the provider window straight from the application's click when
5739
+ * the value is passed through as `open({ direct })`. False on a malformed
5740
+ * wire value (informational, not load-bearing).
5741
+ */
5742
+ direct;
5727
5743
  constructor(data) {
5728
5744
  _assertString(data.session_token, "session_token", "ConnectSession");
5729
5745
  _assertString(data.connect_url, "connect_url", "ConnectSession");
@@ -5745,10 +5761,12 @@ var ConnectSession = class {
5745
5761
  (w) => typeof w === "string"
5746
5762
  ) : []
5747
5763
  );
5764
+ this.direct = data.direct === true;
5748
5765
  Object.freeze(this);
5749
5766
  }
5750
5767
  toJSON() {
5751
5768
  return {
5769
+ direct: this.direct,
5752
5770
  session_token: this.sessionToken,
5753
5771
  connect_url: this.connectUrl,
5754
5772
  expires_in: this.expiresIn,
@@ -10180,7 +10198,7 @@ function _extractAdditionalCredentials(token) {
10180
10198
  return _additionalCredsStore.get(token);
10181
10199
  }
10182
10200
  var _fetch;
10183
- var SDK_VERSION = "0.24.2";
10201
+ var SDK_VERSION = "0.25.0";
10184
10202
  var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
10185
10203
  function pyUnquote(s) {
10186
10204
  if (!s.includes("%")) return s;
@@ -11682,7 +11700,11 @@ ${label}:${value}`;
11682
11700
  appUserIds,
11683
11701
  errorData,
11684
11702
  grantIds,
11685
- candidates
11703
+ candidates,
11704
+ // Absent on the wire means "complete" — only a real cap emits the
11705
+ // key, so a strict `=== true` keeps a stray falsy value from
11706
+ // reading as truncation.
11707
+ errorData.candidates_truncated === true
11686
11708
  );
11687
11709
  }
11688
11710
  if (errorData.error === "sibling_label_conflict") {
@@ -16755,7 +16777,7 @@ var DEFAULT_BASE_URL = "https://backend.alterauth.com";
16755
16777
  var PAT_API_PREFIX = "/api/v1/dev-portal";
16756
16778
  var HTTP_ERROR_THRESHOLD = 400;
16757
16779
  var DEFAULT_TIMEOUT_MS2 = 3e4;
16758
- var CLI_VERSION = "0.9.1";
16780
+ var CLI_VERSION = "0.9.2";
16759
16781
  var USER_AGENT = buildUserAgent();
16760
16782
  function buildUserAgent() {
16761
16783
  let osTag = "";
@@ -17740,6 +17762,13 @@ var AgentsNamespace2 = class {
17740
17762
  return expectDict(body, "agents.undeprecate_key", 200);
17741
17763
  }
17742
17764
  };
17765
+ function isRedirectUriAliasSpec(value) {
17766
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
17767
+ return false;
17768
+ }
17769
+ const spec = value;
17770
+ return typeof spec.label === "string" && typeof spec.description === "string";
17771
+ }
17743
17772
  function isSharedDevProviderEntry(value) {
17744
17773
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
17745
17774
  return false;
@@ -17761,7 +17790,7 @@ function isProviderCatalogEntry(value) {
17761
17790
  const v = value;
17762
17791
  return typeof v.id === "string" && typeof v.name === "string" && typeof v.display_name === "string" && (v.category === null || typeof v.category === "string") && (v.supports_refresh === null || typeof v.supports_refresh === "boolean") && (v.supports_pkce === null || typeof v.supports_pkce === "boolean") && (v.available_environments === void 0 || Array.isArray(v.available_environments) && v.available_environments.every(
17763
17792
  (environment) => environment === "production" || environment === "sandbox"
17764
- )) && (v.scopes_configured_on_provider === void 0 || typeof v.scopes_configured_on_provider === "boolean") && v.available_scopes !== null && typeof v.available_scopes === "object" && !Array.isArray(v.available_scopes) && Object.values(v.available_scopes).every(
17793
+ )) && (v.scopes_configured_on_provider === void 0 || typeof v.scopes_configured_on_provider === "boolean") && (v.redirect_uri_alias === void 0 || v.redirect_uri_alias === null || isRedirectUriAliasSpec(v.redirect_uri_alias)) && v.available_scopes !== null && typeof v.available_scopes === "object" && !Array.isArray(v.available_scopes) && Object.values(v.available_scopes).every(
17765
17794
  isProviderScopeCatalogEntry
17766
17795
  ) && (v.managed_credentials_available === void 0 || typeof v.managed_credentials_available === "boolean") && (v.managed_approved_scopes === void 0 || v.managed_approved_scopes === null || Array.isArray(v.managed_approved_scopes) && v.managed_approved_scopes.every(
17767
17796
  (scope) => typeof scope === "string"
@@ -17856,7 +17885,8 @@ var ProvidersNamespace = class {
17856
17885
  client_secret: options.client_secret,
17857
17886
  environment: options.environment,
17858
17887
  scopes: options.scopes,
17859
- redirect_uris: options.redirect_uris
17888
+ redirect_uris: options.redirect_uris,
17889
+ redirect_uri_alias: options.redirect_uri_alias
17860
17890
  });
17861
17891
  const body = await this.#client._call(
17862
17892
  "POST",
@@ -17893,6 +17923,7 @@ var ProvidersNamespace = class {
17893
17923
  environment: options.environment,
17894
17924
  scopes: options.scopes,
17895
17925
  redirect_uris: options.redirect_uris,
17926
+ redirect_uri_alias: options.redirect_uri_alias,
17896
17927
  status: options.status
17897
17928
  });
17898
17929
  const body = await this.#client._call(
@@ -20794,7 +20825,9 @@ function buildAgentsCommand() {
20794
20825
  });
20795
20826
  }
20796
20827
  );
20797
- agents.command("create").description("Create a managed agent (returns one-shot API key)").option(APP_FLAG, APP_TARGET_DESC).option(
20828
+ agents.command("create").description(
20829
+ "Create a managed agent (returns its first API key ONCE, in the api_key field)"
20830
+ ).option(APP_FLAG, APP_TARGET_DESC).option(
20798
20831
  "--name <name>",
20799
20832
  "Agent name (stable identifier). Required unless --input is supplied."
20800
20833
  ).option("--display-name <name>", "Human-readable display name").option("--type <type>", "Type: agent|service (default: agent)", "agent").option(
@@ -21199,7 +21232,9 @@ function buildAgentsCommand() {
21199
21232
  `);
21200
21233
  });
21201
21234
  });
21202
- agents.command("mint-key").description("Mint a fresh API key for the agent (plaintext returned ONCE)").option(APP_FLAG, APP_TARGET_DESC).requiredOption(
21235
+ agents.command("mint-key").description(
21236
+ "Mint a fresh API key for the agent (plaintext returned ONCE, in the api_key field)"
21237
+ ).option(APP_FLAG, APP_TARGET_DESC).requiredOption(
21203
21238
  "--agent <agent-id>",
21204
21239
  "Agent ID",
21205
21240
  parseUuidArgument("--agent")
@@ -38281,7 +38316,25 @@ import { hostname as hostname3, platform as platform2 } from "os";
38281
38316
  import { spawn } from "child_process";
38282
38317
  var MIN_EPHEMERAL_PORT = 49152;
38283
38318
  var MAX_EPHEMERAL_PORT = 65535;
38284
- var LISTENER_TIMEOUT_MS = 12e4;
38319
+ var LISTENER_TIMEOUT_MS = 6e5;
38320
+ function describeDeadline(ms) {
38321
+ if (ms >= 6e4 && ms % 6e4 === 0) {
38322
+ const minutes = ms / 6e4;
38323
+ return `${minutes} minute${minutes === 1 ? "" : "s"}`;
38324
+ }
38325
+ if (ms >= 1e3) {
38326
+ const seconds = Math.round(ms / 1e3);
38327
+ return `${seconds} second${seconds === 1 ? "" : "s"}`;
38328
+ }
38329
+ return `${ms} ms`;
38330
+ }
38331
+ var SELF_INSPECTION_SCOPE = "dashboard_pats:read";
38332
+ function withSelfInspectionScope(scopes) {
38333
+ if (scopes.includes(SELF_INSPECTION_SCOPE)) {
38334
+ return { scopes: [...scopes], added: false };
38335
+ }
38336
+ return { scopes: [...scopes, SELF_INSPECTION_SCOPE], added: true };
38337
+ }
38285
38338
  var CALLBACK_STATE_PATTERN = /^[0-9a-f]{64}$/;
38286
38339
  var DEFAULT_SCOPES = [
38287
38340
  "dashboard_apps:read",
@@ -38359,7 +38412,9 @@ function pickEphemeralPort() {
38359
38412
  }
38360
38413
  async function runBrowserDance(options) {
38361
38414
  const state = randomBytes2(32).toString("hex");
38362
- const scopes = options.scopes ?? DEFAULT_SCOPES;
38415
+ const { scopes, added: addedSelfInspectionScope } = withSelfInspectionScope(
38416
+ options.scopes ?? DEFAULT_SCOPES
38417
+ );
38363
38418
  const dashboardUrl = options.dashboardUrl ?? deriveDashboardUrl(options.baseUrl);
38364
38419
  const openBrowser = options.openBrowser ?? defaultOpenBrowser;
38365
38420
  const timeoutMs = options.timeoutMs ?? LISTENER_TIMEOUT_MS;
@@ -38414,7 +38469,7 @@ async function runBrowserDance(options) {
38414
38469
  cleanupListener();
38415
38470
  reject(
38416
38471
  new Error(
38417
- "browser-dance timed out \u2014 operator did not complete the consent flow within 2 minutes"
38472
+ `browser-dance timed out \u2014 operator did not complete the consent flow within ${describeDeadline(timeoutMs)}`
38418
38473
  )
38419
38474
  );
38420
38475
  }, timeoutMs);
@@ -38572,9 +38627,15 @@ async function runBrowserDance(options) {
38572
38627
  bindQuery = `&bind=cidr&cidr=${encodeURIComponent(options.cidrValue)}`;
38573
38628
  }
38574
38629
  const url2 = `${dashboardUrl}/cli-auth?port=${port}&state=${state}&scopes=${scopeQuery}&hostname=${encodeURIComponent(hostname3())}${bindQuery}`;
38630
+ if (addedSelfInspectionScope) {
38631
+ process.stderr.write(
38632
+ `alter: added ${SELF_INSPECTION_SCOPE} to the requested scopes \u2014 the CLI verifies the sign-in (and \`alter auth status\`) with it.
38633
+ `
38634
+ );
38635
+ }
38575
38636
  process.stdout.write(
38576
38637
  `alter: opening browser at ${dashboardUrl}/cli-auth
38577
- (waiting up to 2 minutes for you to approve)\u2026
38638
+ (waiting up to ${describeDeadline(timeoutMs)} for you to approve)\u2026
38578
38639
  `
38579
38640
  );
38580
38641
  openBrowser(url2);
@@ -39220,7 +39281,7 @@ function buildAuthCommand() {
39220
39281
  "Override the dashboard URL used by the browser-dance flow. Defaults to the backend URL with the leftmost `backend.` subdomain swapped for `portal.`. Use this when the dashboard isn't at the regex-derivable host (e.g. staging / self-hosted setups). Must use https://."
39221
39282
  ).option(
39222
39283
  "--scopes <list>",
39223
- "Comma-separated scope list to request on the browser-dance consent page (e.g. 'dashboard_apps:read,dashboard_keys:write'). Defaults to the full read+write set across the CLI surface \u2014 enough for every non-destructive day-to-day command. Opt in to admin / delete tiers explicitly: 'dashboard_keys:admin' for runtime-key admin ops, 'dashboard_apps:admin' to archive/unarchive an app, and 'dashboard_apps:delete' for cascade-delete an app (note the Destructive-Action Policy keeps :delete as its own verb, separate from :admin \u2014 :admin does not grant the cascade). Each entry is validated client-side against the dashboard scope catalog \u2014 typos fail fast. Mutually exclusive with --token / --token-file / --token-stdin \u2014 pre-minted PATs have their scope set baked at mint time on the dashboard's Settings page; combining the flags is a usage error."
39284
+ "Comma-separated scope list to request on the browser-dance consent page (e.g. 'dashboard_apps:read,dashboard_keys:write'). Defaults to the full read+write set across the CLI surface \u2014 enough for every non-destructive day-to-day command. Opt in to admin / delete tiers explicitly: 'dashboard_keys:admin' for runtime-key admin ops, 'dashboard_apps:admin' to archive/unarchive an app, and 'dashboard_apps:delete' for cascade-delete an app (note the Destructive-Action Policy keeps :delete as its own verb, separate from :admin \u2014 :admin does not grant the cascade). The list REPLACES the default set, except that dashboard_pats:read is always added: the CLI verifies every sign-in (and `alter auth status`) with it, so a PAT without it cannot complete the login. Each entry is validated client-side against the dashboard scope catalog \u2014 typos fail fast. Mutually exclusive with --token / --token-file / --token-stdin \u2014 pre-minted PATs have their scope set baked at mint time on the dashboard's Settings page; combining the flags is a usage error."
39224
39285
  ).option(
39225
39286
  "--no-bind-ip",
39226
39287
  "Explicit opt-out of IP binding. This is now the DEFAULT for the browser-dance flow, so the flag is a no-op alias retained for scripts that pass it explicitly. The resulting PAT works from any source IP. Browser-dance flow only."
@@ -40965,7 +41026,9 @@ function buildKeysCommand() {
40965
41026
  emit2(format, row);
40966
41027
  });
40967
41028
  });
40968
- keys.command("mint").description("Mint a new scoped key (plaintext returned ONCE)").option(APP_FLAG, APP_TARGET_DESC).option(
41029
+ keys.command("mint").description(
41030
+ "Mint a new scoped key (plaintext returned ONCE, in the plain_key field \u2014 api_key is the key's metadata; capture it with --output json --fields plain_key or jq -r .plain_key)"
41031
+ ).option(APP_FLAG, APP_TARGET_DESC).option(
40969
41032
  "--name <name>",
40970
41033
  "Display name. Required unless --input is supplied."
40971
41034
  ).option(
@@ -41027,7 +41090,7 @@ function buildKeysCommand() {
41027
41090
  }
41028
41091
  emit2(format, outcome2.result);
41029
41092
  process.stderr.write(
41030
- "alter: the plaintext key above is shown ONCE and cannot be retrieved later.\n"
41093
+ "alter: the plain_key field above is the secret (api_key is metadata); it is shown ONCE and cannot be retrieved later.\n"
41031
41094
  );
41032
41095
  return;
41033
41096
  }
@@ -41120,7 +41183,7 @@ function buildKeysCommand() {
41120
41183
  }
41121
41184
  emit2(format, outcome.result);
41122
41185
  process.stderr.write(
41123
- "alter: the plaintext key above is shown ONCE and cannot be retrieved later.\n"
41186
+ "alter: the plain_key field above is the secret (api_key is metadata); it is shown ONCE and cannot be retrieved later.\n"
41124
41187
  );
41125
41188
  }
41126
41189
  );
@@ -41137,7 +41200,7 @@ function buildKeysCommand() {
41137
41200
  });
41138
41201
  });
41139
41202
  keys.command("rotate").description(
41140
- "Rotate a key (new plaintext returned ONCE; old key enters grace until revoke). The app-key successor receives the organization's current finite default TTL. Requires dashboard_keys:write scope."
41203
+ "Rotate a key (new plaintext returned ONCE, in the new_plain_key field; old key enters grace until revoke). The app-key successor receives the organization's current finite default TTL. Requires dashboard_keys:write scope."
41141
41204
  ).option(APP_FLAG, APP_TARGET_DESC).requiredOption("--key <key-id>", "Key ID", parseUuidArgument("--key")).option(
41142
41205
  "--scopes <list>",
41143
41206
  "Optional new scope set (defaults to the key's current scopes)"
@@ -41162,7 +41225,7 @@ function buildKeysCommand() {
41162
41225
  });
41163
41226
  emit2(format, result);
41164
41227
  process.stderr.write(
41165
- "alter: the new plaintext key above is shown ONCE and cannot be retrieved later.\n"
41228
+ "alter: the new_plain_key field above is the new secret (new_api_key is metadata); it is shown ONCE and cannot be retrieved later.\n"
41166
41229
  );
41167
41230
  });
41168
41231
  }
@@ -41171,7 +41234,7 @@ function buildKeysCommand() {
41171
41234
  "Revoke a key immediately (cascades to derived keys). Requires dashboard_keys:admin scope."
41172
41235
  ).option(APP_FLAG, APP_TARGET_DESC).requiredOption("--key <key-id>", "Key ID", parseUuidArgument("--key")).option(
41173
41236
  "--force",
41174
- "Override the last-active-key guard (revoking an agent's only active key bricks it until a fresh key is minted)"
41237
+ "Override the last-active-key guard (revoking the last active key on an app or an agent leaves whatever authenticates with it broken until a fresh key is minted)"
41175
41238
  ).option("--yes", "Skip the interactive y/N prompt").action(
41176
41239
  async (options) => {
41177
41240
  const resolvedAppId = await resolveAppOrExit(options.app);
@@ -42099,10 +42162,20 @@ var SECRET_COLUMNS = [
42099
42162
  { label: "CREATED", get: (s) => s.created_at },
42100
42163
  { label: "UPDATED", get: (s) => s.updated_at ?? "\u2014" }
42101
42164
  ];
42165
+ var DEPTH_COLUMN = {
42166
+ label: "DEPTH",
42167
+ get: (g) => g.depth == null ? "?" : String(g.depth)
42168
+ };
42169
+ var PARENT_COLUMN = {
42170
+ label: "PARENT",
42171
+ get: (g) => g.parent_grant_id === void 0 ? "?" : g.parent_grant_id ?? "\u2014",
42172
+ maxWidth: 36
42173
+ };
42102
42174
  var GRANT_COLUMNS2 = [
42103
42175
  { label: "GRANT_ID", get: (g) => g.grant_id, maxWidth: 36 },
42104
42176
  { label: "PRINCIPAL", get: (g) => g.principal_type },
42105
42177
  { label: "LABEL", get: (g) => g.label ?? "\u2014", maxWidth: 24 },
42178
+ DEPTH_COLUMN,
42106
42179
  { label: "STATUS", get: (g) => g.status },
42107
42180
  { label: "CREATED", get: (g) => g.created_at },
42108
42181
  {
@@ -42114,6 +42187,9 @@ var GRANT_COLUMNS2 = [
42114
42187
  get: (g) => g.grant_expires_at ?? "\u2014"
42115
42188
  }
42116
42189
  ];
42190
+ var AGENT_GRANT_COLUMNS = GRANT_COLUMNS2.flatMap(
42191
+ (col) => col === DEPTH_COLUMN ? [col, PARENT_COLUMN] : [col]
42192
+ );
42117
42193
  var USER_COLUMNS = [
42118
42194
  { label: "ID", get: (u) => u.id, maxWidth: 36 },
42119
42195
  { label: "EMAIL", get: (u) => u.email ?? "\u2014" },
@@ -42190,7 +42266,9 @@ function buildGrantsSubcommand() {
42190
42266
  label: isOptionalString,
42191
42267
  created_at: isString,
42192
42268
  expires_at: isOptionalString,
42193
- grant_expires_at: isAbsentOrOptionalString
42269
+ grant_expires_at: isAbsentOrOptionalString,
42270
+ parent_grant_id: isAbsentOrOptionalString,
42271
+ depth: isAbsentOrOptionalNumber
42194
42272
  },
42195
42273
  "managed-secrets.grants.list"
42196
42274
  );
@@ -42238,11 +42316,14 @@ function buildGrantsSubcommand() {
42238
42316
  label: isOptionalString,
42239
42317
  created_at: isString,
42240
42318
  expires_at: isOptionalString,
42241
- grant_expires_at: isAbsentOrOptionalString
42319
+ grant_expires_at: isAbsentOrOptionalString,
42320
+ // Rendered through ``AGENT_GRANT_COLUMNS`` (adds PARENT).
42321
+ parent_grant_id: isAbsentOrOptionalString,
42322
+ depth: isAbsentOrOptionalNumber
42242
42323
  },
42243
42324
  "managed-secrets.grants.list-for-agent"
42244
42325
  );
42245
- emit2(format, rows, GRANT_COLUMNS2);
42326
+ emit2(format, rows, AGENT_GRANT_COLUMNS);
42246
42327
  });
42247
42328
  }
42248
42329
  );
@@ -43726,7 +43807,9 @@ function buildIdentityProvidersCommand() {
43726
43807
  const webhook = idp.command("webhook").description(
43727
43808
  "Manage an identity provider's webhook integration (requires dashboard_identity_providers:webhooks)"
43728
43809
  );
43729
- webhook.command("enable").description("Enable webhook integration (returns the signing secret ONCE)").option(APP_FLAG, APP_TARGET_DESC).requiredOption(
43810
+ webhook.command("enable").description(
43811
+ "Enable webhook integration (returns the signing secret ONCE, in the webhook_secret field)"
43812
+ ).option(APP_FLAG, APP_TARGET_DESC).requiredOption(
43730
43813
  "--provider <provider-id>",
43731
43814
  "Identity provider ID",
43732
43815
  parseUuidArgument("--provider")
@@ -43758,7 +43841,7 @@ function buildIdentityProvidersCommand() {
43758
43841
  emit2(format, row);
43759
43842
  if (typeof row["webhook_secret"] === "string") {
43760
43843
  process.stderr.write(
43761
- "alter: the webhook_secret above is shown ONCE and cannot be retrieved later.\n"
43844
+ "alter: the webhook_secret field above is shown ONCE and cannot be retrieved later.\n"
43762
43845
  );
43763
43846
  }
43764
43847
  return null;
@@ -43878,7 +43961,7 @@ alter: re-run with --force --confirm <issuer-host> to revoke those grants and di
43878
43961
  }
43879
43962
  );
43880
43963
  webhook.command("rotate").description(
43881
- "Rotate the webhook signing secret (invalidates the previous; returns the new one ONCE)"
43964
+ "Rotate the webhook signing secret (invalidates the previous; returns the new one ONCE, in the webhook_secret field)"
43882
43965
  ).option(APP_FLAG, APP_TARGET_DESC).requiredOption(
43883
43966
  "--provider <provider-id>",
43884
43967
  "Identity provider ID",
@@ -43926,7 +44009,7 @@ alter: re-run with --force --confirm <issuer-host> to revoke those grants and di
43926
44009
  emit2(format, row);
43927
44010
  if (typeof row["webhook_secret"] === "string") {
43928
44011
  process.stderr.write(
43929
- "alter: the webhook_secret above is shown ONCE and cannot be retrieved later.\n"
44012
+ "alter: the webhook_secret field above is shown ONCE and cannot be retrieved later.\n"
43930
44013
  );
43931
44014
  }
43932
44015
  return null;
@@ -45327,8 +45410,12 @@ function buildPolicyCommand() {
45327
45410
  import { Command as Command20 } from "commander";
45328
45411
  var CREDENTIAL_SOURCES = ["custom", "shared_dev"];
45329
45412
  var PROVIDER_ENVIRONMENTS = ["production", "sandbox"];
45330
- async function providerCatalogError(client, providerId, environment, credentialSource, scopes, scopesRequired = false) {
45331
- if (environment === void 0 && scopes === void 0 && !scopesRequired) {
45413
+ async function providerCatalogError(client, providerId, environment, credentialSource, scopes, options = {}) {
45414
+ const create = options.create === true;
45415
+ const redirectUriAlias = options.redirectUriAlias;
45416
+ const readStoredProvider = options.readStoredProvider;
45417
+ const switchesToCustom = !create && credentialSource === "custom" && readStoredProvider !== void 0;
45418
+ if (environment === void 0 && scopes === void 0 && redirectUriAlias === void 0 && !create && !switchesToCustom) {
45332
45419
  return null;
45333
45420
  }
45334
45421
  if (!providerId) {
@@ -45350,6 +45437,23 @@ async function providerCatalogError(client, providerId, environment, credentialS
45350
45437
  if (environment !== void 0 && credentialSource === "shared_dev" && environment !== "production") {
45351
45438
  return "alter: --credential-source shared_dev only supports --environment production; use custom sandbox credentials\n";
45352
45439
  }
45440
+ const aliasSpec = provider.redirect_uri_alias ?? null;
45441
+ const aliasError = redirectUriAliasError(
45442
+ providerId,
45443
+ aliasSpec,
45444
+ credentialSource,
45445
+ redirectUriAlias,
45446
+ create
45447
+ );
45448
+ if (aliasError !== null) return aliasError;
45449
+ if (switchesToCustom && readStoredProvider !== void 0 && aliasSpec !== null && redirectUriAlias === void 0) {
45450
+ const storedAliasError = await redirectUriAliasStoredStateError(
45451
+ readStoredProvider,
45452
+ providerId,
45453
+ aliasSpec
45454
+ );
45455
+ if (storedAliasError !== null) return storedAliasError;
45456
+ }
45353
45457
  const availableScopes = provider.available_scopes ?? {};
45354
45458
  if (Object.keys(availableScopes).length === 0) {
45355
45459
  if (scopes !== void 0 && scopes.length > 0) {
@@ -45359,7 +45463,7 @@ async function providerCatalogError(client, providerId, environment, credentialS
45359
45463
  }
45360
45464
  return null;
45361
45465
  }
45362
- if (scopesRequired && scopes === void 0) {
45466
+ if (create && scopes === void 0) {
45363
45467
  return `alter: provider '${providerId}' requires at least one scope (pass --scopes, or "scopes" in --input)
45364
45468
  `;
45365
45469
  }
@@ -45390,6 +45494,41 @@ async function providerCatalogError(client, providerId, environment, credentialS
45390
45494
  }
45391
45495
  return null;
45392
45496
  }
45497
+ function redirectUriAliasError(providerId, spec, credentialSource, redirectUriAlias, create) {
45498
+ if (spec === null) {
45499
+ return redirectUriAlias === void 0 ? null : `alter: --redirect-uri-alias is not used by ${providerId}
45500
+ `;
45501
+ }
45502
+ if (redirectUriAlias !== void 0 && credentialSource === "shared_dev") {
45503
+ return `alter: --redirect-uri-alias is only accepted with --credential-source custom (Alter-managed credentials carry their own ${spec.label})
45504
+ `;
45505
+ }
45506
+ const custom3 = credentialSource === void 0 || credentialSource === "custom";
45507
+ if (create && custom3 && redirectUriAlias === void 0) {
45508
+ return redirectUriAliasRequiredError(providerId, spec);
45509
+ }
45510
+ return null;
45511
+ }
45512
+ function redirectUriAliasRequiredError(providerId, spec) {
45513
+ return `alter: ${providerId} requires --redirect-uri-alias (${spec.label}): ${spec.description} (pass --redirect-uri-alias, or "redirect_uri_alias" in --input)
45514
+ `;
45515
+ }
45516
+ async function redirectUriAliasStoredStateError(readStoredProvider, providerId, spec) {
45517
+ let current;
45518
+ try {
45519
+ const row = await readStoredProvider();
45520
+ if (row === null || typeof row !== "object") return null;
45521
+ current = row;
45522
+ } catch {
45523
+ return null;
45524
+ }
45525
+ const storedSource = typeof current.credential_source === "string" ? current.credential_source : "custom";
45526
+ const storedAlias = typeof current.redirect_uri_alias === "string" ? current.redirect_uri_alias.trim() : "";
45527
+ if (storedSource === "shared_dev" || storedAlias.length === 0) {
45528
+ return redirectUriAliasRequiredError(providerId, spec);
45529
+ }
45530
+ return null;
45531
+ }
45393
45532
  function storedProviderReader(client, appId, providerId) {
45394
45533
  let pending2 = null;
45395
45534
  return () => pending2 ??= client.providers.get(appId, providerId);
@@ -45475,6 +45614,16 @@ var PROVIDER_COLUMNS = [
45475
45614
  // between test money and real money.
45476
45615
  { label: "ENV", get: (p) => p.environment ?? "production" },
45477
45616
  { label: "SCOPES", get: (p) => p.scopes.join(",") || "\u2014", maxWidth: 40 },
45617
+ // The provider-issued redirect name (eBay's RuName). Its provider-side
45618
+ // label is on ``list-catalog``'s REDIRECT ALIAS column. Wide enough for a
45619
+ // real RuName (``<user>-<app>-<title>-<token>``, ~40–60 chars) to print
45620
+ // untruncated — an operator copies this column back into a provider
45621
+ // console, so a clipped value is worse than none.
45622
+ {
45623
+ label: "REDIRECT ALIAS",
45624
+ get: (p) => p.redirect_uri_alias ?? "\u2014",
45625
+ maxWidth: 60
45626
+ },
45478
45627
  { label: "GRANTS", get: (p) => String(p.grants_count) },
45479
45628
  { label: "STATUS", get: (p) => p.status },
45480
45629
  {
@@ -45558,6 +45707,22 @@ function collectRedirectUris(value, previous = []) {
45558
45707
  const next = value.split(",").map((s) => s.trim()).filter(Boolean);
45559
45708
  return [...previous, ...next];
45560
45709
  }
45710
+ function validateRedirectUriAliasOrExit(raw, source = "--redirect-uri-alias") {
45711
+ if (raw === void 0) return void 0;
45712
+ const value = raw.trim();
45713
+ if (value.length === 0) {
45714
+ process.stderr.write(`alter: ${source} requires a non-empty value
45715
+ `);
45716
+ process.exit(EXIT_USAGE);
45717
+ }
45718
+ if (value.length > 255) {
45719
+ process.stderr.write(`alter: ${source} must be at most 255 characters
45720
+ `);
45721
+ process.exit(EXIT_USAGE);
45722
+ }
45723
+ return value;
45724
+ }
45725
+ var INPUT_ALIAS_SOURCE = '"redirect_uri_alias" in --input';
45561
45726
  function surfaceProviderResponse(row) {
45562
45727
  if (typeof row !== "object" || row === null) return;
45563
45728
  const r = row;
@@ -45627,6 +45792,7 @@ function buildProvidersCommand() {
45627
45792
  // "production" — defeating the tolerance this line exists for.
45628
45793
  environment: isAbsentOrOptionalString,
45629
45794
  scopes: (v) => Array.isArray(v),
45795
+ redirect_uri_alias: isAbsentOrOptionalString,
45630
45796
  status: isString,
45631
45797
  grants_count: (v) => typeof v === "number"
45632
45798
  },
@@ -45675,6 +45841,13 @@ function buildProvidersCommand() {
45675
45841
  get: (p) => p.managed_approved_scopes === void 0 ? "?" : p.managed_approved_scopes === null ? "\u2014" : p.managed_approved_scopes.join(",") || "(none)",
45676
45842
  maxWidth: 36
45677
45843
  },
45844
+ // Providers that take a provider-issued NAME as redirect_uri
45845
+ // instead of the callback URL show its label (eBay: RuName) —
45846
+ // ``create`` then requires ``--redirect-uri-alias``.
45847
+ {
45848
+ label: "REDIRECT ALIAS",
45849
+ get: (p) => p.redirect_uri_alias?.label ?? "\u2014"
45850
+ },
45678
45851
  { label: "STATUS", get: (p) => p.status }
45679
45852
  ]);
45680
45853
  });
@@ -45703,9 +45876,12 @@ function buildProvidersCommand() {
45703
45876
  "--redirect-uri <uri>",
45704
45877
  "Add a redirect URI. Repeat the flag (``--redirect-uri A --redirect-uri B``) or pass a comma-separated list (``--redirect-uri A,B``); both forms accumulate.",
45705
45878
  collectRedirectUris
45879
+ ).option(
45880
+ "--redirect-uri-alias <name>",
45881
+ "The provider-issued redirect name sent as redirect_uri in place of the callback URL \u2014 required for providers that use one (eBay's RuName); see `alter providers list-catalog`."
45706
45882
  ).option(
45707
45883
  "--input <path>",
45708
- "JSON wire body from file (@/path/to/body.json) or stdin (-). Mirrors ``gh api --input``. Replaces the per-field flags (``--provider``, ``--credential-source``, ``--client-id``, ``--client-secret``, ``--environment``, ``--scopes``, ``--redirect-uri``); the body is sent to ``POST /apps/<app>/providers`` verbatim. If both --input and per-field flags are supplied, --input wins and the per-field flags are ignored (with a warning)."
45884
+ "JSON wire body from file (@/path/to/body.json) or stdin (-). Mirrors ``gh api --input``. Replaces the per-field flags (``--provider``, ``--credential-source``, ``--client-id``, ``--client-secret``, ``--environment``, ``--scopes``, ``--redirect-uri``, ``--redirect-uri-alias``); the body is sent to ``POST /apps/<app>/providers`` verbatim. If both --input and per-field flags are supplied, --input wins and the per-field flags are ignored (with a warning)."
45709
45885
  ).option(
45710
45886
  "--output <format>",
45711
45887
  "Output format: json|jsonl|table (default: json)",
@@ -45723,7 +45899,8 @@ function buildProvidersCommand() {
45723
45899
  "clientSecret",
45724
45900
  "environment",
45725
45901
  "scopes",
45726
- "redirectUri"
45902
+ "redirectUri",
45903
+ "redirectUriAlias"
45727
45904
  ].filter((k) => {
45728
45905
  if (k === "credentialSource")
45729
45906
  return options.credentialSource !== "custom";
@@ -45732,6 +45909,10 @@ function buildProvidersCommand() {
45732
45909
  return options[k] !== void 0;
45733
45910
  });
45734
45911
  warnInputOverridesPerFieldFlags(perField.map(optionKeyToFlag));
45912
+ const bodyRedirectUriAlias = validateRedirectUriAliasOrExit(
45913
+ inputBodyString(body, "redirect_uri_alias"),
45914
+ INPUT_ALIAS_SOURCE
45915
+ );
45735
45916
  const inputPreflightError = await withClient(
45736
45917
  async (client) => {
45737
45918
  const bodyEnvironment = inputBodyString(body, "environment");
@@ -45741,7 +45922,7 @@ function buildProvidersCommand() {
45741
45922
  bodyEnvironment,
45742
45923
  inputBodyString(body, "credential_source"),
45743
45924
  inputBodyStringArray(body, "scopes"),
45744
- true
45925
+ { create: true, redirectUriAlias: bodyRedirectUriAlias }
45745
45926
  );
45746
45927
  if (catalogError !== null) return catalogError;
45747
45928
  const availabilityError = await sharedDevAvailabilityError(
@@ -45810,6 +45991,9 @@ function buildProvidersCommand() {
45810
45991
  validateUrlOrExit("--redirect-uri", uri);
45811
45992
  }
45812
45993
  const redirects = options.redirectUri && options.redirectUri.length > 0 ? options.redirectUri : void 0;
45994
+ const redirectUriAlias = validateRedirectUriAliasOrExit(
45995
+ options.redirectUriAlias
45996
+ );
45813
45997
  const environmentPreflightError = await withClient(
45814
45998
  async (client) => {
45815
45999
  const catalogError = await providerCatalogError(
@@ -45818,7 +46002,7 @@ function buildProvidersCommand() {
45818
46002
  environment,
45819
46003
  credentialSource,
45820
46004
  scopes,
45821
- true
46005
+ { create: true, redirectUriAlias }
45822
46006
  );
45823
46007
  if (catalogError !== null) return catalogError;
45824
46008
  const availabilityError = await sharedDevAvailabilityError(
@@ -45837,7 +46021,8 @@ function buildProvidersCommand() {
45837
46021
  // Scopeless provider with --scopes omitted: the dashboard's
45838
46022
  // exact wire body for these providers is ``scopes: []``.
45839
46023
  scopes: scopes ?? [],
45840
- redirect_uris: redirects
46024
+ redirect_uris: redirects,
46025
+ redirect_uri_alias: redirectUriAlias
45841
46026
  });
45842
46027
  surfaceProviderResponse(row);
45843
46028
  emit2(format, row);
@@ -45871,6 +46056,7 @@ function buildProvidersCommand() {
45871
46056
  credential_source: isOptionalString,
45872
46057
  environment: isAbsentOrOptionalString,
45873
46058
  scopes: (v) => Array.isArray(v),
46059
+ redirect_uri_alias: isAbsentOrOptionalString,
45874
46060
  status: isString,
45875
46061
  grants_count: (v) => typeof v === "number"
45876
46062
  },
@@ -45898,6 +46084,9 @@ function buildProvidersCommand() {
45898
46084
  "--redirect-uri <uri>",
45899
46085
  "Replace redirect URIs. Repeat the flag or pass a comma-separated list; both forms accumulate.",
45900
46086
  collectRedirectUris
46087
+ ).option(
46088
+ "--redirect-uri-alias <name>",
46089
+ "Replace the provider-issued redirect name sent as redirect_uri in place of the callback URL (eBay's RuName). Accepted only for providers that use one; see `alter providers list-catalog`."
45901
46090
  ).option(
45902
46091
  "--status <status>",
45903
46092
  "New status: active | disabled (rejected at the CLI if not exactly one of the two values)"
@@ -45927,24 +46116,33 @@ function buildProvidersCommand() {
45927
46116
  "environment",
45928
46117
  "scopes",
45929
46118
  "redirectUri",
46119
+ "redirectUriAlias",
45930
46120
  "status"
45931
46121
  ].filter((k) => options[k] !== void 0);
45932
46122
  warnInputOverridesPerFieldFlags(perField.map(optionKeyToFlag));
46123
+ const bodyRedirectUriAlias = validateRedirectUriAliasOrExit(
46124
+ inputBodyString(body, "redirect_uri_alias"),
46125
+ INPUT_ALIAS_SOURCE
46126
+ );
45933
46127
  const inputPreflightError = await withClient(
45934
46128
  async (client) => {
46129
+ const readStoredProvider = storedProviderReader(
46130
+ client,
46131
+ appId,
46132
+ options.provider
46133
+ );
45935
46134
  const catalogError = await providerCatalogError(
45936
46135
  client,
45937
46136
  options.provider,
45938
46137
  inputBodyString(body, "environment"),
45939
46138
  inputBodyString(body, "credential_source"),
45940
- inputBodyStringArray(body, "scopes")
46139
+ inputBodyStringArray(body, "scopes"),
46140
+ {
46141
+ redirectUriAlias: bodyRedirectUriAlias,
46142
+ readStoredProvider
46143
+ }
45941
46144
  );
45942
46145
  if (catalogError !== null) return catalogError;
45943
- const readStoredProvider = storedProviderReader(
45944
- client,
45945
- appId,
45946
- options.provider
45947
- );
45948
46146
  const storedStateError = await sharedDevStoredStateError(
45949
46147
  readStoredProvider,
45950
46148
  options.provider,
@@ -46006,21 +46204,25 @@ function buildProvidersCommand() {
46006
46204
  validateUrlOrExit("--redirect-uri", uri);
46007
46205
  }
46008
46206
  const redirects = options.redirectUri && options.redirectUri.length > 0 ? options.redirectUri : void 0;
46207
+ const redirectUriAlias = validateRedirectUriAliasOrExit(
46208
+ options.redirectUriAlias
46209
+ );
46009
46210
  const updatePreflightError = await withClient(
46010
46211
  async (client) => {
46212
+ const readStoredProvider = storedProviderReader(
46213
+ client,
46214
+ appId,
46215
+ options.provider
46216
+ );
46011
46217
  const catalogError = await providerCatalogError(
46012
46218
  client,
46013
46219
  options.provider,
46014
46220
  environment,
46015
46221
  credentialSource,
46016
- scopes
46222
+ scopes,
46223
+ { redirectUriAlias, readStoredProvider }
46017
46224
  );
46018
46225
  if (catalogError !== null) return catalogError;
46019
- const readStoredProvider = storedProviderReader(
46020
- client,
46021
- appId,
46022
- options.provider
46023
- );
46024
46226
  const storedStateError = await sharedDevStoredStateError(
46025
46227
  readStoredProvider,
46026
46228
  options.provider,
@@ -46044,6 +46246,7 @@ function buildProvidersCommand() {
46044
46246
  ...options.skipPreflight ? { skip_preflight: true } : {},
46045
46247
  scopes,
46046
46248
  redirect_uris: redirects,
46249
+ redirect_uri_alias: redirectUriAlias,
46047
46250
  status: providerStatus
46048
46251
  });
46049
46252
  surfaceProviderResponse(row);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@alter-ai/cli",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@alter-ai/cli",
9
- "version": "0.9.1",
9
+ "version": "0.9.2",
10
10
  "dependencies": {
11
11
  "commander": "12.1.0",
12
12
  "posthog-node": "4.18.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alter-ai/cli",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Command-line interface for the Alter Vault dev portal — scripted dashboard automation.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -47,7 +47,7 @@
47
47
  "esbuild": "0.28.1"
48
48
  },
49
49
  "devDependencies": {
50
- "@alter-ai/alter-sdk": "workspace:0.24.2",
50
+ "@alter-ai/alter-sdk": "workspace:0.25.0",
51
51
  "@alter-vault/shared-types": "workspace:0.0.1",
52
52
  "@alter-vault/shared-utils": "workspace:0.0.1",
53
53
  "@eslint/js": "9.39.4",