@alter-ai/cli 0.5.0 → 0.6.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/cli.js +1758 -199
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -28,7 +28,7 @@ import { join } from "path";
28
28
  // package.json
29
29
  var package_default = {
30
30
  name: "@alter-ai/cli",
31
- version: "0.5.0",
31
+ version: "0.6.0",
32
32
  description: "Command-line interface for the Alter Vault dev portal \u2014 scripted dashboard automation.",
33
33
  type: "module",
34
34
  bin: {
@@ -593,9 +593,20 @@ var ReAuthRequiredError = class extends BackendError {
593
593
  }
594
594
  };
595
595
  var GrantExpiredError = class extends ReAuthRequiredError {
596
- constructor(message, details) {
596
+ providerId;
597
+ agentId;
598
+ appUserId;
599
+ // Recovery context appended AFTER `details` — same contract and
600
+ // rationale as GrantNotFoundError: the delegation-walk 403 carries
601
+ // provider/agent/app_user recovery fields; the root-path TTL 403
602
+ // leaves them undefined. Existing `new GrantExpiredError(msg, details)`
603
+ // callers keep compiling unchanged.
604
+ constructor(message, details, providerId, agentId, appUserId) {
597
605
  super(message, details);
598
606
  this.name = "GrantExpiredError";
607
+ this.providerId = providerId;
608
+ this.agentId = agentId;
609
+ this.appUserId = appUserId;
599
610
  }
600
611
  };
601
612
  var GrantRevokedError = class extends ReAuthRequiredError {
@@ -649,6 +660,12 @@ var GrantNotFoundError = class extends BackendError {
649
660
  this.appUserId = appUserId;
650
661
  }
651
662
  };
663
+ var AgentDelegationMissingError = class extends GrantNotFoundError {
664
+ constructor(message, details, providerId, agentId, appUserId) {
665
+ super(message, details, providerId, agentId, appUserId);
666
+ this.name = "AgentDelegationMissingError";
667
+ }
668
+ };
652
669
  var AmbiguousGrantError = class extends BackendError {
653
670
  providerId;
654
671
  accountIdentifiers;
@@ -711,6 +728,20 @@ var PolicyViolationError = class extends BackendError {
711
728
  this.policyError = policyError;
712
729
  }
713
730
  };
731
+ var StepUpRequiredError = class extends PolicyViolationError {
732
+ maxSessionAgeSeconds;
733
+ constructor(message, maxSessionAgeSeconds, details) {
734
+ super(message, "step_up_required", details);
735
+ this.name = "StepUpRequiredError";
736
+ this.maxSessionAgeSeconds = maxSessionAgeSeconds;
737
+ }
738
+ };
739
+ var RedactDischargeFailedError = class extends PolicyViolationError {
740
+ constructor(message, details) {
741
+ super(message, "redact_discharge_failed", details);
742
+ this.name = "RedactDischargeFailedError";
743
+ }
744
+ };
714
745
  var RestrictedGrantRequiresProxyError = class extends BackendError {
715
746
  constructor(message, details) {
716
747
  super(message, details);
@@ -805,6 +836,14 @@ var TokenRefreshInProgressError = class extends BackendError {
805
836
  this.grantId = grantId;
806
837
  }
807
838
  };
839
+ var QuotaExceededError = class extends BackendError {
840
+ retryAfter;
841
+ constructor(message, retryAfter, details) {
842
+ super(message, details);
843
+ this.name = "QuotaExceededError";
844
+ this.retryAfter = retryAfter;
845
+ }
846
+ };
808
847
  var ConnectFlowError = class extends AlterSDKError {
809
848
  constructor(message, details) {
810
849
  super(message, details);
@@ -1391,13 +1430,10 @@ var AuthSession = class {
1391
1430
  _assertString(data.session_token, "session_token", "AuthSession");
1392
1431
  _assertString(data.auth_url, "auth_url", "AuthSession");
1393
1432
  _assertString(data.expires_at, "expires_at", "AuthSession");
1394
- if (typeof data.expires_in !== "number" || !Number.isFinite(data.expires_in)) {
1395
- throw new BackendError(
1396
- "AuthSession: 'expires_in' must be a finite number",
1397
- {
1398
- field: "expires_in"
1399
- }
1400
- );
1433
+ if (!Number.isInteger(data.expires_in)) {
1434
+ throw new BackendError("AuthSession: 'expires_in' must be an integer", {
1435
+ field: "expires_in"
1436
+ });
1401
1437
  }
1402
1438
  this.sessionToken = data.session_token;
1403
1439
  this.authUrl = data.auth_url;
@@ -1490,6 +1526,15 @@ var OAuthGrantItem = class {
1490
1526
  // shape and the Python SDK `needs_reconnect`.
1491
1527
  needsReconnect;
1492
1528
  expiresAt;
1529
+ // The grant's OWN TTL-policy expiry (`grant_policy.expires_at`), first-class.
1530
+ // Two DIFFERENT axes: `expiresAt` above is the provider TOKEN's expiry
1531
+ // (auto-refreshed, never a grant terminal); `grantExpiresAt` is when the
1532
+ // PERMISSION itself lapses (the Connect TTL picker). Null = no TTL
1533
+ // (perpetual until revoked). The backend projects `status = "expired"` the
1534
+ // moment this instant passes even if the DB row hasn't lazily flipped yet,
1535
+ // so filtering `status === "active"` never caches a TTL-dead grant id.
1536
+ // Mirrors the backend Pydantic shape and the Python SDK `grant_expires_at`.
1537
+ grantExpiresAt;
1493
1538
  createdAt;
1494
1539
  lastUsedAt;
1495
1540
  // OAuth grants are ``user`` / ``system`` ROOTS, plus ``agent`` DELEGATION
@@ -1538,7 +1583,8 @@ var OAuthGrantItem = class {
1538
1583
  this.status = data.status;
1539
1584
  this.scopeMismatch = data.scope_mismatch ?? false;
1540
1585
  this.needsReconnect = data.needs_reconnect ?? false;
1541
- this.expiresAt = data.expires_at ?? null;
1586
+ this.expiresAt = typeof data.expires_at === "string" ? data.expires_at : null;
1587
+ this.grantExpiresAt = typeof data.grant_expires_at === "string" ? data.grant_expires_at : null;
1542
1588
  this.createdAt = data.created_at;
1543
1589
  this.lastUsedAt = data.last_used_at ?? null;
1544
1590
  if (data.principal_type !== "user" && data.principal_type !== "system" && data.principal_type !== "agent") {
@@ -1576,6 +1622,7 @@ var OAuthGrantItem = class {
1576
1622
  scope_mismatch: this.scopeMismatch,
1577
1623
  needs_reconnect: this.needsReconnect,
1578
1624
  expires_at: this.expiresAt,
1625
+ grant_expires_at: this.grantExpiresAt,
1579
1626
  created_at: this.createdAt,
1580
1627
  last_used_at: this.lastUsedAt,
1581
1628
  principal_type: this.principalType,
@@ -1604,6 +1651,10 @@ var ManagedSecretGrantItem = class {
1604
1651
  accountIdentifier;
1605
1652
  grantPolicy;
1606
1653
  expiresAt;
1654
+ // The grant's OWN TTL-policy expiry (`grant_policy.expires_at`), first-class —
1655
+ // same semantics as `OAuthGrantItem.grantExpiresAt` (null = perpetual; the
1656
+ // backend projects `status = "expired"` once lapsed).
1657
+ grantExpiresAt;
1607
1658
  createdAt;
1608
1659
  lastUsedAt;
1609
1660
  principalType;
@@ -1630,7 +1681,8 @@ var ManagedSecretGrantItem = class {
1630
1681
  this.status = data.status;
1631
1682
  this.accountIdentifier = data.account_identifier ?? null;
1632
1683
  this.grantPolicy = typeof data.grant_policy === "object" && data.grant_policy !== null && !Array.isArray(data.grant_policy) ? data.grant_policy : null;
1633
- this.expiresAt = data.expires_at ?? null;
1684
+ this.expiresAt = typeof data.expires_at === "string" ? data.expires_at : null;
1685
+ this.grantExpiresAt = typeof data.grant_expires_at === "string" ? data.grant_expires_at : null;
1634
1686
  this.createdAt = data.created_at;
1635
1687
  this.lastUsedAt = data.last_used_at ?? null;
1636
1688
  this.principalType = data.principal_type;
@@ -1660,6 +1712,7 @@ var ManagedSecretGrantItem = class {
1660
1712
  account_identifier: this.accountIdentifier,
1661
1713
  grant_policy: this.grantPolicy,
1662
1714
  expires_at: this.expiresAt,
1715
+ grant_expires_at: this.grantExpiresAt,
1663
1716
  created_at: this.createdAt,
1664
1717
  last_used_at: this.lastUsedAt,
1665
1718
  parent_grant_id: this.parentGrantId,
@@ -2029,8 +2082,40 @@ function _assertApprovalStatusValue(value, context) {
2029
2082
  }
2030
2083
  }
2031
2084
  var APPROVAL_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["denied", "expired", "executed", "failed"]);
2085
+ var ApprovalGate = class {
2086
+ approvalId;
2087
+ expiresAt;
2088
+ expiresIn;
2089
+ approvalUrl;
2090
+ constructor(args) {
2091
+ const ctx = "ApprovalGate";
2092
+ _assertString(args?.approval_id, "approval_id", ctx);
2093
+ if (!Number.isInteger(args?.expires_in)) {
2094
+ throw new BackendError(
2095
+ `${ctx}: 'expires_in' must be an integer, got ${typeof args?.expires_in}`,
2096
+ { field: "expires_in" }
2097
+ );
2098
+ }
2099
+ _assertString(args?.approval_url, "approval_url", ctx);
2100
+ this.approvalId = args.approval_id;
2101
+ this.expiresAt = _parseDateOrThrow(args.expires_at, "expires_at", ctx);
2102
+ this.expiresIn = args.expires_in;
2103
+ this.approvalUrl = args.approval_url;
2104
+ Object.freeze(this);
2105
+ }
2106
+ toJSON() {
2107
+ return {
2108
+ approval_id: this.approvalId,
2109
+ expires_at: this.expiresAt.toISOString(),
2110
+ expires_in: this.expiresIn,
2111
+ approval_url: this.approvalUrl
2112
+ };
2113
+ }
2114
+ };
2032
2115
  var PendingApproval = class {
2033
2116
  approvalId;
2117
+ /** Shared execution unit for the gate group (N-of-N multi-party). */
2118
+ approvalGroupId;
2034
2119
  // The backend's ``ApprovalPendingResponse`` always reports
2035
2120
  // ``status: "pending"`` on the 202 path. Carrying it on the SDK
2036
2121
  // model preserves the wire contract and matches the Python SDK so
@@ -2047,12 +2132,19 @@ var PendingApproval = class {
2047
2132
  * ``EMAIL_PROVIDER`` configured.
2048
2133
  */
2049
2134
  approvalUrl;
2135
+ /**
2136
+ * Every distinct approver gate (>=1). Each must be approved before execution
2137
+ * proceeds; any denial or expiry kills the whole group. The top-level fields
2138
+ * point at the group's executor gate (the id used for polling).
2139
+ */
2140
+ gates;
2050
2141
  constructor(args) {
2051
2142
  const ctx = "PendingApproval";
2052
2143
  _assertString(args?.approval_id, "approval_id", ctx);
2053
- if (typeof args?.expires_in !== "number" || !Number.isFinite(args.expires_in)) {
2144
+ _assertString(args?.approval_group_id, "approval_group_id", ctx);
2145
+ if (!Number.isInteger(args?.expires_in)) {
2054
2146
  throw new BackendError(
2055
- `${ctx}: 'expires_in' must be a finite number, got ${typeof args?.expires_in}`,
2147
+ `${ctx}: 'expires_in' must be an integer, got ${typeof args?.expires_in}`,
2056
2148
  { field: "expires_in" }
2057
2149
  );
2058
2150
  }
@@ -2063,26 +2155,67 @@ var PendingApproval = class {
2063
2155
  );
2064
2156
  }
2065
2157
  _assertString(args?.approval_url, "approval_url", ctx);
2158
+ if (!Array.isArray(args?.gates) || args.gates.length < 1) {
2159
+ throw new BackendError(`${ctx}: 'gates' must be a non-empty array`, {
2160
+ field: "gates"
2161
+ });
2162
+ }
2066
2163
  this.approvalId = args.approval_id;
2164
+ this.approvalGroupId = args.approval_group_id;
2067
2165
  this.status = "pending";
2068
2166
  this.expiresAt = _parseDateOrThrow(args.expires_at, "expires_at", ctx);
2069
2167
  this.expiresIn = args.expires_in;
2070
2168
  this.approvalUrl = args.approval_url;
2169
+ this.gates = Object.freeze(
2170
+ args.gates.map(
2171
+ (g) => new ApprovalGate(g)
2172
+ )
2173
+ );
2071
2174
  Object.freeze(this);
2072
2175
  }
2073
2176
  toJSON() {
2074
2177
  return {
2075
2178
  approval_id: this.approvalId,
2179
+ approval_group_id: this.approvalGroupId,
2076
2180
  status: this.status,
2077
2181
  expires_at: this.expiresAt.toISOString(),
2078
2182
  expires_in: this.expiresIn,
2079
- approval_url: this.approvalUrl
2183
+ approval_url: this.approvalUrl,
2184
+ gates: this.gates.map((g) => g.toJSON())
2185
+ };
2186
+ }
2187
+ };
2188
+ var ApprovalGateStatus = class {
2189
+ approvalId;
2190
+ status;
2191
+ expiresAt;
2192
+ decidedAt;
2193
+ constructor(args) {
2194
+ const ctx = "ApprovalGateStatus";
2195
+ _assertString(args?.approval_id, "approval_id", ctx);
2196
+ _assertApprovalStatusValue(args?.status, ctx);
2197
+ this.approvalId = args.approval_id;
2198
+ this.status = args.status;
2199
+ this.expiresAt = _parseDateOrThrow(args.expires_at, "expires_at", ctx);
2200
+ this.decidedAt = _parseOptionalDate(args.decided_at, "decided_at", ctx);
2201
+ Object.freeze(this);
2202
+ }
2203
+ toJSON() {
2204
+ return {
2205
+ approval_id: this.approvalId,
2206
+ status: this.status,
2207
+ expires_at: this.expiresAt.toISOString(),
2208
+ decided_at: this.decidedAt ? this.decidedAt.toISOString() : null
2080
2209
  };
2081
2210
  }
2082
2211
  };
2083
2212
  var ApprovalStatus = class {
2084
2213
  approvalId;
2214
+ /** Shared execution unit; for a multi-party approval `status` is the DERIVED
2215
+ * group status and `gates` carries the per-gate breakdown. */
2216
+ approvalGroupId;
2085
2217
  status;
2218
+ gates;
2086
2219
  expiresAt;
2087
2220
  decidedAt;
2088
2221
  decisionReason;
@@ -2098,7 +2231,13 @@ var ApprovalStatus = class {
2098
2231
  constructor(args) {
2099
2232
  const ctx = "ApprovalStatus";
2100
2233
  _assertString(args?.approval_id, "approval_id", ctx);
2234
+ _assertString(args?.approval_group_id, "approval_group_id", ctx);
2101
2235
  _assertApprovalStatusValue(args?.status, ctx);
2236
+ if (!Array.isArray(args?.gates) || args.gates.length < 1) {
2237
+ throw new BackendError(`${ctx}: 'gates' must be a non-empty array`, {
2238
+ field: "gates"
2239
+ });
2240
+ }
2102
2241
  if (typeof args?.has_result !== "boolean") {
2103
2242
  throw new BackendError(
2104
2243
  `${ctx}: 'has_result' must be a boolean, got ${typeof args?.has_result}`,
@@ -2112,7 +2251,13 @@ var ApprovalStatus = class {
2112
2251
  );
2113
2252
  }
2114
2253
  this.approvalId = args.approval_id;
2254
+ this.approvalGroupId = args.approval_group_id;
2115
2255
  this.status = args.status;
2256
+ this.gates = Object.freeze(
2257
+ args.gates.map(
2258
+ (g) => new ApprovalGateStatus(g)
2259
+ )
2260
+ );
2116
2261
  this.expiresAt = _parseDateOrThrow(args.expires_at, "expires_at", ctx);
2117
2262
  this.decidedAt = _parseOptionalDate(args.decided_at, "decided_at", ctx);
2118
2263
  this.decisionReason = args.decision_reason ?? null;
@@ -2127,7 +2272,9 @@ var ApprovalStatus = class {
2127
2272
  toJSON() {
2128
2273
  return {
2129
2274
  approval_id: this.approvalId,
2275
+ approval_group_id: this.approvalGroupId,
2130
2276
  status: this.status,
2277
+ gates: this.gates.map((g) => g.toJSON()),
2131
2278
  expires_at: this.expiresAt.toISOString(),
2132
2279
  decided_at: this.decidedAt ? this.decidedAt.toISOString() : null,
2133
2280
  decision_reason: this.decisionReason,
@@ -2145,9 +2292,9 @@ var ApprovalResult = class {
2145
2292
  bodyTruncated;
2146
2293
  constructor(args) {
2147
2294
  const ctx = "ApprovalResult";
2148
- if (typeof args?.status_code !== "number" || !Number.isFinite(args.status_code)) {
2295
+ if (!Number.isInteger(args?.status_code)) {
2149
2296
  throw new BackendError(
2150
- `${ctx}: 'status_code' must be a finite number, got ${typeof args?.status_code}`,
2297
+ `${ctx}: 'status_code' must be an integer, got ${typeof args?.status_code}`,
2151
2298
  { field: "status_code" }
2152
2299
  );
2153
2300
  }
@@ -2790,6 +2937,271 @@ var IdentityAssertion = class {
2790
2937
  return this.toString();
2791
2938
  }
2792
2939
  };
2940
+ var ProviderSpec = class {
2941
+ /** `"oauth"` or `"managed"` (see {@link ProviderSpecKind}). */
2942
+ providerKind;
2943
+ providerId;
2944
+ /** Monotonic ingest version — bumps whenever the stored spec content changes. */
2945
+ version;
2946
+ /** Where the spec came from (e.g. an official published spec vs a curated one). */
2947
+ provenance;
2948
+ sourceUrl;
2949
+ contentHash;
2950
+ operationCount;
2951
+ /**
2952
+ * Parsed to a real `Date` (not a raw string) so callers can compare
2953
+ * freshness against `new Date()` / `Date.now()` without first
2954
+ * remembering to parse. Mirrors Python SDK
2955
+ * ``ProviderSpec.fetched_at: datetime``. Re-serialized to ISO 8601
2956
+ * by `toJSON()`.
2957
+ */
2958
+ fetchedAt;
2959
+ /** When this spec version last changed; unlike fetchedAt, unchanged sweeps do not move it. */
2960
+ changedAt;
2961
+ title;
2962
+ /** The spec document's own declared version string (e.g. an OpenAPI `info.version`). */
2963
+ specVersion;
2964
+ constructor(data) {
2965
+ this.providerKind = _requireNonEmptyString(
2966
+ "ProviderSpec",
2967
+ "provider_kind",
2968
+ data?.provider_kind
2969
+ );
2970
+ this.providerId = _requireNonEmptyString(
2971
+ "ProviderSpec",
2972
+ "provider_id",
2973
+ data.provider_id
2974
+ );
2975
+ this.version = _requireNumber("ProviderSpec", "version", data.version);
2976
+ this.provenance = _requireNonEmptyString(
2977
+ "ProviderSpec",
2978
+ "provenance",
2979
+ data.provenance
2980
+ );
2981
+ this.sourceUrl = _optionalString(
2982
+ "ProviderSpec",
2983
+ "source_url",
2984
+ data.source_url
2985
+ );
2986
+ this.contentHash = _requireNonEmptyString(
2987
+ "ProviderSpec",
2988
+ "content_hash",
2989
+ data.content_hash
2990
+ );
2991
+ this.operationCount = _requireNumber(
2992
+ "ProviderSpec",
2993
+ "operation_count",
2994
+ data.operation_count
2995
+ );
2996
+ this.fetchedAt = _parseDateOrThrow(
2997
+ data.fetched_at,
2998
+ "fetched_at",
2999
+ "ProviderSpec"
3000
+ );
3001
+ this.changedAt = _parseDateOrThrow(
3002
+ data.changed_at,
3003
+ "changed_at",
3004
+ "ProviderSpec"
3005
+ );
3006
+ this.title = _optionalString("ProviderSpec", "title", data.title);
3007
+ this.specVersion = _optionalString(
3008
+ "ProviderSpec",
3009
+ "spec_version",
3010
+ data.spec_version
3011
+ );
3012
+ Object.freeze(this);
3013
+ }
3014
+ toJSON() {
3015
+ return {
3016
+ provider_kind: this.providerKind,
3017
+ provider_id: this.providerId,
3018
+ version: this.version,
3019
+ provenance: this.provenance,
3020
+ source_url: this.sourceUrl,
3021
+ content_hash: this.contentHash,
3022
+ operation_count: this.operationCount,
3023
+ // Serialize Date back to ISO 8601 wire format so toJSON() round-
3024
+ // trips identically to Python ``model_dump(mode="json")``.
3025
+ fetched_at: this.fetchedAt.toISOString(),
3026
+ changed_at: this.changedAt.toISOString(),
3027
+ title: this.title,
3028
+ spec_version: this.specVersion
3029
+ };
3030
+ }
3031
+ };
3032
+ var SpecOperation = class {
3033
+ /**
3034
+ * Spec-native operation id. May contain slashes (GitHub's
3035
+ * `repos/get`) or dots (Google's `gmail.users.messages.list`).
3036
+ */
3037
+ operationId;
3038
+ method;
3039
+ /** URL path template with `{placeholders}` (e.g. `/repos/{owner}/{repo}`). */
3040
+ pathTemplate;
3041
+ summary;
3042
+ constructor(data) {
3043
+ this.operationId = _requireNonEmptyString(
3044
+ "SpecOperation",
3045
+ "operation_id",
3046
+ data?.operation_id
3047
+ );
3048
+ this.method = _requireNonEmptyString(
3049
+ "SpecOperation",
3050
+ "method",
3051
+ data.method
3052
+ );
3053
+ this.pathTemplate = _requireNonEmptyString(
3054
+ "SpecOperation",
3055
+ "path_template",
3056
+ data.path_template
3057
+ );
3058
+ this.summary = _optionalString("SpecOperation", "summary", data.summary);
3059
+ Object.freeze(this);
3060
+ }
3061
+ toJSON() {
3062
+ return {
3063
+ operation_id: this.operationId,
3064
+ method: this.method,
3065
+ path_template: this.pathTemplate,
3066
+ summary: this.summary
3067
+ };
3068
+ }
3069
+ };
3070
+ var SpecOperationsPage = class {
3071
+ items;
3072
+ total;
3073
+ limit;
3074
+ offset;
3075
+ hasMore;
3076
+ spec;
3077
+ constructor(data) {
3078
+ if (!Array.isArray(data?.items)) {
3079
+ throw _wireShapeError(
3080
+ "SpecOperationsPage",
3081
+ "items",
3082
+ "array",
3083
+ data?.items
3084
+ );
3085
+ }
3086
+ this.items = Object.freeze(
3087
+ data.items.map(
3088
+ (item) => item instanceof SpecOperation ? item : new SpecOperation(item)
3089
+ )
3090
+ );
3091
+ this.total = _requireNumber("SpecOperationsPage", "total", data.total);
3092
+ this.limit = _requireNumber("SpecOperationsPage", "limit", data.limit);
3093
+ this.offset = _requireNumber("SpecOperationsPage", "offset", data.offset);
3094
+ this.hasMore = _requireBoolean(
3095
+ "SpecOperationsPage",
3096
+ "has_more",
3097
+ data.has_more
3098
+ );
3099
+ this.spec = data.spec instanceof ProviderSpec ? data.spec : new ProviderSpec(data.spec);
3100
+ Object.freeze(this);
3101
+ }
3102
+ toJSON() {
3103
+ return {
3104
+ items: this.items.map((item) => item.toJSON()),
3105
+ total: this.total,
3106
+ limit: this.limit,
3107
+ offset: this.offset,
3108
+ has_more: this.hasMore,
3109
+ spec: this.spec.toJSON()
3110
+ };
3111
+ }
3112
+ };
3113
+ var SpecOperationDetail = class {
3114
+ operationId;
3115
+ method;
3116
+ pathTemplate;
3117
+ summary;
3118
+ paramsSchema;
3119
+ requestSchema;
3120
+ responseSchema;
3121
+ spec;
3122
+ constructor(data) {
3123
+ this.operationId = _requireNonEmptyString(
3124
+ "SpecOperationDetail",
3125
+ "operation_id",
3126
+ data?.operation_id
3127
+ );
3128
+ this.method = _requireNonEmptyString(
3129
+ "SpecOperationDetail",
3130
+ "method",
3131
+ data.method
3132
+ );
3133
+ this.pathTemplate = _requireNonEmptyString(
3134
+ "SpecOperationDetail",
3135
+ "path_template",
3136
+ data.path_template
3137
+ );
3138
+ this.summary = _optionalString(
3139
+ "SpecOperationDetail",
3140
+ "summary",
3141
+ data.summary
3142
+ );
3143
+ this.paramsSchema = _optionalObjectArray(
3144
+ "SpecOperationDetail",
3145
+ "params_schema",
3146
+ data.params_schema
3147
+ );
3148
+ this.requestSchema = _optionalObject(
3149
+ "SpecOperationDetail",
3150
+ "request_schema",
3151
+ data.request_schema
3152
+ );
3153
+ this.responseSchema = _optionalObject(
3154
+ "SpecOperationDetail",
3155
+ "response_schema",
3156
+ data.response_schema
3157
+ );
3158
+ this.spec = data.spec instanceof ProviderSpec ? data.spec : new ProviderSpec(data.spec);
3159
+ Object.freeze(this);
3160
+ }
3161
+ toJSON() {
3162
+ return {
3163
+ operation_id: this.operationId,
3164
+ method: this.method,
3165
+ path_template: this.pathTemplate,
3166
+ summary: this.summary,
3167
+ params_schema: this.paramsSchema,
3168
+ request_schema: this.requestSchema,
3169
+ response_schema: this.responseSchema,
3170
+ spec: this.spec.toJSON()
3171
+ };
3172
+ }
3173
+ };
3174
+ function _optionalString(modelName, field, value) {
3175
+ if (value === null || value === void 0) return null;
3176
+ if (typeof value !== "string") {
3177
+ throw _wireShapeError(modelName, field, "string or null", value);
3178
+ }
3179
+ return value;
3180
+ }
3181
+ function _optionalObject(modelName, field, value) {
3182
+ if (value === null || value === void 0) return null;
3183
+ if (typeof value !== "object" || Array.isArray(value)) {
3184
+ throw _wireShapeError(modelName, field, "object or null", value);
3185
+ }
3186
+ return value;
3187
+ }
3188
+ function _optionalObjectArray(modelName, field, value) {
3189
+ if (value === null || value === void 0) return null;
3190
+ if (!Array.isArray(value)) {
3191
+ throw _wireShapeError(modelName, field, "array of objects or null", value);
3192
+ }
3193
+ for (const entry of value) {
3194
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
3195
+ throw _wireShapeError(
3196
+ modelName,
3197
+ field,
3198
+ "array of objects or null",
3199
+ entry
3200
+ );
3201
+ }
3202
+ }
3203
+ return value;
3204
+ }
2793
3205
  var ALGORITHM = "AWS4-HMAC-SHA256";
2794
3206
  var AWS_HOST_RE = (
2795
3207
  // eslint-disable-next-line security/detect-unsafe-regex
@@ -3563,7 +3975,7 @@ function _requireStringArray(raw, key, context) {
3563
3975
  }
3564
3976
  return v;
3565
3977
  }
3566
- function _optionalString(raw, key, context) {
3978
+ function _optionalString2(raw, key, context) {
3567
3979
  const v = raw[key];
3568
3980
  if (v === null || v === void 0) return null;
3569
3981
  if (typeof v !== "string") {
@@ -3626,11 +4038,11 @@ function rawToInfo(raw) {
3626
4038
  const createdAt = _requireString(raw, "created_at", ctx);
3627
4039
  const cidrAllowlist = _optionalStringArray(raw, "cidr_allowlist", ctx);
3628
4040
  const rateLimitRpm = _optionalNumber(raw, "rate_limit_rpm", ctx);
3629
- const expiresAt = _optionalString(raw, "expires_at", ctx);
3630
- const deprecatedAt = _optionalString(raw, "deprecated_at", ctx);
3631
- const revokedAt = _optionalString(raw, "revoked_at", ctx);
3632
- const parentKeyId = _optionalString(raw, "parent_key_id", ctx);
3633
- const lastUsedAt = _optionalString(raw, "last_used_at", ctx);
4041
+ const expiresAt = _optionalString2(raw, "expires_at", ctx);
4042
+ const deprecatedAt = _optionalString2(raw, "deprecated_at", ctx);
4043
+ const revokedAt = _optionalString2(raw, "revoked_at", ctx);
4044
+ const parentKeyId = _optionalString2(raw, "parent_key_id", ctx);
4045
+ const lastUsedAt = _optionalString2(raw, "last_used_at", ctx);
3634
4046
  const status = (() => {
3635
4047
  if (revokedAt !== null) return "revoked";
3636
4048
  if (deprecatedAt !== null) return "rotated";
@@ -3956,6 +4368,216 @@ function requireStringArray(fieldName, value) {
3956
4368
  function catalogContractError(message) {
3957
4369
  return new BackendError(message);
3958
4370
  }
4371
+ var LIST_OPERATIONS_LIMIT_MAX = 500;
4372
+ var SEARCH_MAX_LENGTH = 200;
4373
+ function _requireKind(kind) {
4374
+ if (kind !== "oauth" && kind !== "managed") {
4375
+ throw new AlterValueError(
4376
+ `kind must be "oauth" or "managed", got ${JSON.stringify(kind)}`
4377
+ );
4378
+ }
4379
+ return kind;
4380
+ }
4381
+ function _requireProviderId(providerId) {
4382
+ if (typeof providerId !== "string" || providerId.trim().length === 0) {
4383
+ throw new AlterValueError("providerId must be a non-empty string");
4384
+ }
4385
+ return providerId;
4386
+ }
4387
+ function _strictEncodeSegment(segment) {
4388
+ return encodeURIComponent(segment).replace(
4389
+ /[!'()*]/g,
4390
+ (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
4391
+ );
4392
+ }
4393
+ var ProviderSpecsNamespace = class {
4394
+ #vault;
4395
+ constructor(vault) {
4396
+ this.#vault = vault;
4397
+ }
4398
+ /**
4399
+ * Every provider's active spec metadata (freshness + provenance),
4400
+ * optionally filtered to one provider family.
4401
+ *
4402
+ * Requires the `providers:read` key scope.
4403
+ *
4404
+ * @param kind Optional filter: `"oauth"` or `"managed"`.
4405
+ * @throws {AlterValueError} `kind` is not `"oauth"` / `"managed"`.
4406
+ * @throws {InsufficientScopeError} key lacks `providers:read`.
4407
+ * @throws {BackendError} backend reachability or response-shape failure.
4408
+ */
4409
+ async list(kind) {
4410
+ let path = "/sdk/provider-specs";
4411
+ if (kind !== void 0) {
4412
+ const params = new URLSearchParams({ kind: _requireKind(kind) });
4413
+ path += `?${params.toString()}`;
4414
+ }
4415
+ const data = await this.#getJson(path);
4416
+ const obj = _requireResponseObject2("ProviderSpecListResponse", data);
4417
+ const items = obj.items;
4418
+ if (!Array.isArray(items)) {
4419
+ throw new BackendError(
4420
+ "Backend response did not match expected ProviderSpecListResponse shape: missing 'items' array",
4421
+ { model: "ProviderSpecListResponse", received_keys: Object.keys(obj) }
4422
+ );
4423
+ }
4424
+ if (typeof obj.total !== "number") {
4425
+ throw new BackendError(
4426
+ "Backend response did not match expected ProviderSpecListResponse shape: 'total' must be a number",
4427
+ { model: "ProviderSpecListResponse", received_keys: Object.keys(obj) }
4428
+ );
4429
+ }
4430
+ return items.map(
4431
+ (item) => new ProviderSpec(item)
4432
+ );
4433
+ }
4434
+ /**
4435
+ * One provider's active spec metadata.
4436
+ *
4437
+ * @throws {AlterValueError} invalid `kind` / empty `providerId`.
4438
+ * @throws {GrantNotFoundError} no active spec for this provider
4439
+ * (backend 404, `provider_spec_not_found`).
4440
+ * @throws {InsufficientScopeError} key lacks `providers:read`.
4441
+ * @throws {BackendError} backend reachability or response-shape failure.
4442
+ */
4443
+ async get(kind, providerId) {
4444
+ const path = `/sdk/provider-specs/${_requireKind(kind)}/${_strictEncodeSegment(
4445
+ _requireProviderId(providerId)
4446
+ )}`;
4447
+ const data = await this.#getJson(path);
4448
+ const obj = _requireResponseObject2("ProviderSpec", data);
4449
+ return new ProviderSpec(
4450
+ obj
4451
+ );
4452
+ }
4453
+ /**
4454
+ * Page the provider's operations (in-band discovery). Each row is a
4455
+ * summary ({@link SpecOperation}); fetch the full schemas per
4456
+ * operation via {@link getOperation}.
4457
+ *
4458
+ * @param opts `search` (substring filter, <= 200 chars), `limit`
4459
+ * (1–500, default 100), `offset` (>= 0, default 0) — all validated
4460
+ * client-side before any network call, mirroring the backend gates.
4461
+ * @throws {AlterValueError} invalid `kind` / `providerId` / options.
4462
+ * @throws {GrantNotFoundError} no active spec for this provider
4463
+ * (backend 404, `provider_spec_not_found`).
4464
+ * @throws {InsufficientScopeError} key lacks `providers:read`.
4465
+ * @throws {BackendError} backend reachability or response-shape failure.
4466
+ */
4467
+ async listOperations(kind, providerId, opts = {}) {
4468
+ const basePath = `/sdk/provider-specs/${_requireKind(kind)}/${_strictEncodeSegment(
4469
+ _requireProviderId(providerId)
4470
+ )}/operations`;
4471
+ const limit = opts.limit === void 0 ? 100 : opts.limit;
4472
+ const offset = opts.offset === void 0 ? 0 : opts.offset;
4473
+ if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1 || limit > LIST_OPERATIONS_LIMIT_MAX) {
4474
+ throw new AlterValueError(
4475
+ `limit must be an integer between 1 and ${LIST_OPERATIONS_LIMIT_MAX}`
4476
+ );
4477
+ }
4478
+ if (typeof offset !== "number" || !Number.isInteger(offset) || offset < 0) {
4479
+ throw new AlterValueError("offset must be an integer >= 0");
4480
+ }
4481
+ if (opts.search !== void 0) {
4482
+ if (typeof opts.search !== "string" || opts.search.length === 0) {
4483
+ throw new AlterValueError("search must be a non-empty string");
4484
+ }
4485
+ if (opts.search.length > SEARCH_MAX_LENGTH) {
4486
+ throw new AlterValueError(
4487
+ `search is capped at ${SEARCH_MAX_LENGTH} characters`
4488
+ );
4489
+ }
4490
+ }
4491
+ const params = new URLSearchParams();
4492
+ params.set("limit", String(limit));
4493
+ params.set("offset", String(offset));
4494
+ if (opts.search !== void 0) {
4495
+ params.set("search", opts.search);
4496
+ }
4497
+ params.sort();
4498
+ const data = await this.#getJson(`${basePath}?${params.toString()}`);
4499
+ const obj = _requireResponseObject2("SpecOperationsPage", data);
4500
+ return new SpecOperationsPage(
4501
+ obj
4502
+ );
4503
+ }
4504
+ /**
4505
+ * One operation with its full parameter/request/response schemas.
4506
+ *
4507
+ * @param operationId Spec-native operation id. May contain slashes
4508
+ * (GitHub's `repos/get`) — the backend route uses a `:path`
4509
+ * converter, so slashes are preserved as path structure and every
4510
+ * other segment character is strictly percent-encoded (RFC 3986,
4511
+ * byte-identical to the Python SDK's `quote`). Dot-segments (`.` /
4512
+ * `..`, including pre-encoded `%2e` spellings) and empty segments
4513
+ * are rejected (path-traversal guard).
4514
+ * @throws {AlterValueError} invalid `kind` / `providerId` / empty
4515
+ * `operationId` / an `operationId` containing a dot-segment or
4516
+ * empty path segment.
4517
+ * @throws {GrantNotFoundError} no active spec for this provider OR no
4518
+ * such operation (backend 404, `provider_spec_not_found` /
4519
+ * `operation_not_found` — disambiguate via `details.error`).
4520
+ * @throws {InsufficientScopeError} key lacks `providers:read`.
4521
+ * @throws {BackendError} backend reachability or response-shape failure.
4522
+ */
4523
+ async getOperation(kind, providerId, operationId) {
4524
+ if (typeof operationId !== "string" || operationId.trim().length === 0) {
4525
+ throw new AlterValueError("operationId must be a non-empty string");
4526
+ }
4527
+ for (const segment of operationId.split("/")) {
4528
+ if (segment.length === 0) {
4529
+ throw new AlterValueError(
4530
+ "operationId must not contain empty path segments"
4531
+ );
4532
+ }
4533
+ const dotDecoded = segment.replace(/%2e/gi, ".");
4534
+ if (dotDecoded === "." || dotDecoded === "..") {
4535
+ throw new AlterValueError(
4536
+ 'operationId must not contain path traversal segments ("." or "..")'
4537
+ );
4538
+ }
4539
+ }
4540
+ const encodedOperationId = operationId.split("/").map(_strictEncodeSegment).join("/");
4541
+ const path = `/sdk/provider-specs/${_requireKind(kind)}/${_strictEncodeSegment(
4542
+ _requireProviderId(providerId)
4543
+ )}/operations/${encodedOperationId}`;
4544
+ const data = await this.#getJson(path);
4545
+ const obj = _requireResponseObject2("SpecOperationDetail", data);
4546
+ return new SpecOperationDetail(
4547
+ obj
4548
+ );
4549
+ }
4550
+ /**
4551
+ * GET `sdkPath`, route non-2xx through the client's canonical typed
4552
+ * exception mapping, and parse the 2xx body as JSON.
4553
+ */
4554
+ async #getJson(sdkPath) {
4555
+ const response = await this.#vault._authedBackendRequest("GET", sdkPath);
4556
+ if (!response.ok) {
4557
+ await this.#vault._handleErrorResponse(response);
4558
+ }
4559
+ try {
4560
+ return await response.json();
4561
+ } catch (e) {
4562
+ throw new BackendError(
4563
+ `${sdkPath.split("?")[0]} returned non-JSON response: ${e instanceof Error ? e.message : String(e)}`,
4564
+ { status_code: response.status }
4565
+ );
4566
+ }
4567
+ }
4568
+ };
4569
+ function _requireResponseObject2(modelName, data) {
4570
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
4571
+ throw new BackendError(
4572
+ `Backend response did not match expected ${modelName} shape: response is not a JSON object`,
4573
+ {
4574
+ model: modelName,
4575
+ received_type: data === null ? "null" : Array.isArray(data) ? "array" : typeof data
4576
+ }
4577
+ );
4578
+ }
4579
+ return data;
4580
+ }
3959
4581
  var ScopesNamespace = class {
3960
4582
  #vault;
3961
4583
  constructor(vault) {
@@ -4170,6 +4792,136 @@ async function ambientTraceparent(api) {
4170
4792
  return void 0;
4171
4793
  }
4172
4794
  }
4795
+ var _alterContextStore = new AsyncLocalStorage();
4796
+ var RESERVED_CONTEXT_KEYS = /* @__PURE__ */ new Set([
4797
+ "agent",
4798
+ "parent_agent",
4799
+ "run_id",
4800
+ "thread_id",
4801
+ "tool",
4802
+ "tool_call_id",
4803
+ "framework"
4804
+ ]);
4805
+ var MAX_RUN_METADATA_LENGTH = 4096;
4806
+ var MAX_RUN_METADATA_KEYS = 20;
4807
+ var MAX_RUN_METADATA_KEY_LENGTH = 64;
4808
+ var MAX_RUN_METADATA_VALUE_LENGTH = 512;
4809
+ function getCurrentAuditContext() {
4810
+ const value = _alterContextStore.getStore();
4811
+ if (value === void 0) {
4812
+ return null;
4813
+ }
4814
+ return { ...value };
4815
+ }
4816
+ function _stampAgentIdentity(ctx, agentId, options) {
4817
+ const ambientAgent = ctx.agent;
4818
+ if (!options.isAuthoritative && ambientAgent) {
4819
+ return;
4820
+ }
4821
+ if (ambientAgent && ambientAgent !== agentId) {
4822
+ ctx.parent_agent = ambientAgent;
4823
+ }
4824
+ ctx.agent = agentId;
4825
+ }
4826
+ function _validateRunMetadata(metadata) {
4827
+ if (metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) {
4828
+ throw new AlterValueError(
4829
+ "trace() metadata must be an object of string key/value pairs"
4830
+ );
4831
+ }
4832
+ const keys = Object.keys(metadata);
4833
+ if (keys.length === 0) {
4834
+ return;
4835
+ }
4836
+ if (keys.length > MAX_RUN_METADATA_KEYS) {
4837
+ throw new AlterValueError(
4838
+ `trace() metadata has too many keys (${keys.length}); max is ${MAX_RUN_METADATA_KEYS}`
4839
+ );
4840
+ }
4841
+ const reserved = keys.filter((k) => RESERVED_CONTEXT_KEYS.has(k)).sort();
4842
+ if (reserved.length > 0) {
4843
+ throw new AlterValueError(
4844
+ `trace() metadata contains reserved key(s) ${JSON.stringify(reserved)}; these are owned by the SDK / framework bridges and cannot be set via metadata. Use the explicit \`runId\` / \`threadId\` kwargs for those. Reserved keys: ${JSON.stringify([...RESERVED_CONTEXT_KEYS].sort())}.`
4845
+ );
4846
+ }
4847
+ let totalSize = 0;
4848
+ for (const k of keys) {
4849
+ const keyBytes = Buffer.byteLength(k, "utf8");
4850
+ if (keyBytes > MAX_RUN_METADATA_KEY_LENGTH) {
4851
+ throw new AlterValueError(
4852
+ `trace() metadata key "${k.slice(0, 32)}" exceeds max length (${keyBytes} > ${MAX_RUN_METADATA_KEY_LENGTH} bytes)`
4853
+ );
4854
+ }
4855
+ const v = metadata[k];
4856
+ if (typeof v !== "string") {
4857
+ throw new AlterValueError(
4858
+ `trace() metadata value for key "${k}" must be a string, got ${typeof v}`
4859
+ );
4860
+ }
4861
+ const valueBytes = Buffer.byteLength(v, "utf8");
4862
+ if (valueBytes > MAX_RUN_METADATA_VALUE_LENGTH) {
4863
+ throw new AlterValueError(
4864
+ `trace() metadata value for key "${k}" exceeds max length (${valueBytes} > ${MAX_RUN_METADATA_VALUE_LENGTH} bytes)`
4865
+ );
4866
+ }
4867
+ totalSize += keyBytes + valueBytes;
4868
+ }
4869
+ if (totalSize > MAX_RUN_METADATA_LENGTH) {
4870
+ throw new AlterValueError(
4871
+ `trace() metadata total size ${totalSize} bytes exceeds ${MAX_RUN_METADATA_LENGTH} byte limit`
4872
+ );
4873
+ }
4874
+ }
4875
+ async function _withRunScope(options, callback) {
4876
+ const { agentId, runId, threadId, parent, metadata } = options;
4877
+ if (metadata !== void 0) {
4878
+ _validateRunMetadata(metadata);
4879
+ }
4880
+ const outer = _alterContextStore.getStore();
4881
+ const newContext = {};
4882
+ if (outer !== void 0) {
4883
+ for (const [k, v] of Object.entries(outer)) {
4884
+ if (!RESERVED_CONTEXT_KEYS.has(k)) {
4885
+ newContext[k] = v;
4886
+ }
4887
+ }
4888
+ if (outer.agent) {
4889
+ newContext.agent = outer.agent;
4890
+ if (outer.parent_agent) {
4891
+ newContext.parent_agent = outer.parent_agent;
4892
+ }
4893
+ }
4894
+ }
4895
+ _stampAgentIdentity(newContext, agentId, { isAuthoritative: true });
4896
+ if (parent === void 0) {
4897
+ } else if (typeof parent === "string" && parent.length > 0) {
4898
+ newContext.parent_agent = parent;
4899
+ } else {
4900
+ delete newContext.parent_agent;
4901
+ }
4902
+ if (runId !== void 0) {
4903
+ if (typeof runId !== "string") {
4904
+ throw new AlterValueError(`runId must be a string, got ${typeof runId}`);
4905
+ }
4906
+ newContext.run_id = runId;
4907
+ } else if (outer !== void 0 && outer.run_id !== void 0) {
4908
+ newContext.run_id = outer.run_id;
4909
+ }
4910
+ if (threadId !== void 0) {
4911
+ if (typeof threadId !== "string") {
4912
+ throw new AlterValueError(
4913
+ `threadId must be a string, got ${typeof threadId}`
4914
+ );
4915
+ }
4916
+ newContext.thread_id = threadId;
4917
+ } else if (outer !== void 0 && outer.thread_id !== void 0) {
4918
+ newContext.thread_id = outer.thread_id;
4919
+ }
4920
+ if (metadata !== void 0) {
4921
+ Object.assign(newContext, metadata);
4922
+ }
4923
+ return _alterContextStore.run(newContext, callback);
4924
+ }
4173
4925
  function isRetryErrorInfoPayload(x) {
4174
4926
  if (x == null || typeof x !== "object") {
4175
4927
  return false;
@@ -4198,7 +4950,7 @@ function _extractAdditionalCredentials(token) {
4198
4950
  return _additionalCredsStore.get(token);
4199
4951
  }
4200
4952
  var _fetch;
4201
- var SDK_VERSION = "0.20.0";
4953
+ var SDK_VERSION = "0.20.1";
4202
4954
  var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
4203
4955
  var AUTH_POLL_SERVER_WAIT_MS = 25e3;
4204
4956
  var AUTH_POLL_HTTP_BUFFER_MS = 15e3;
@@ -4213,6 +4965,7 @@ var HTTP_UNAUTHORIZED = 401;
4213
4965
  var HTTP_BAD_GATEWAY = 502;
4214
4966
  var HTTP_INTERNAL_SERVER_ERROR = 500;
4215
4967
  var HTTP_SERVICE_UNAVAILABLE = 503;
4968
+ var HTTP_TOO_MANY_REQUESTS = 429;
4216
4969
  var MAX_SCOPE_CONSTRAINT_ATOM_LENGTH = 512;
4217
4970
  var MAX_SCOPE_CONSTRAINT_ATOMS = 100;
4218
4971
  var SCOPE_TOKEN_PATTERN = /^[\x21\x23-\x5B\x5D-\x7E]+$/;
@@ -4984,7 +5737,10 @@ request-rule:${effectiveRequestRule}`;
4984
5737
  if (errorCode === "grant_expired") {
4985
5738
  throw new GrantExpiredError(
4986
5739
  errorData.message ?? "Grant expired per TTL policy",
4987
- errorData.details
5740
+ errorData.details,
5741
+ typeof errorData.provider_id === "string" ? errorData.provider_id : void 0,
5742
+ typeof errorData.agent_id === "string" ? errorData.agent_id : void 0,
5743
+ typeof errorData.app_user_id === "string" ? errorData.app_user_id : void 0
4988
5744
  );
4989
5745
  }
4990
5746
  if (errorCode === "grant_revoked") {
@@ -5029,6 +5785,22 @@ request-rule:${effectiveRequestRule}`;
5029
5785
  details
5030
5786
  );
5031
5787
  }
5788
+ if (errorCode === "step_up_required") {
5789
+ const rawAge = errorData.max_session_age_seconds;
5790
+ throw new StepUpRequiredError(
5791
+ errorData.message ?? "This request requires a fresh (stepped-up) session \u2014 re-authenticate the user and retry.",
5792
+ // Number.isInteger mirrors the Python int-only guard
5793
+ // (fractional/bool/absent → undefined).
5794
+ Number.isInteger(rawAge) ? rawAge : void 0,
5795
+ errorData
5796
+ );
5797
+ }
5798
+ if (errorCode === "redact_discharge_failed") {
5799
+ throw new RedactDischargeFailedError(
5800
+ errorData.message ?? "The redact obligation could not be applied to the request body, so the request was denied.",
5801
+ errorData
5802
+ );
5803
+ }
5032
5804
  throw new PolicyViolationError(
5033
5805
  errorData.message ?? "Access denied by policy",
5034
5806
  errorCode,
@@ -5167,6 +5939,20 @@ request-rule:${effectiveRequestRule}`;
5167
5939
  errorData
5168
5940
  );
5169
5941
  }
5942
+ if (response.status === HTTP_TOO_MANY_REQUESTS) {
5943
+ const errorData = await __VaultClient.#safeParseJson(response);
5944
+ if (errorData.error === "quota_exceeded") {
5945
+ const retryAfterRaw = errorData.retry_after;
5946
+ throw new QuotaExceededError(
5947
+ errorData.message ?? "Request quota exceeded for the current window.",
5948
+ // The backend always emits an int; anything else (bool,
5949
+ // fractional number, string) degrades to undefined — parity with
5950
+ // the Python isinstance(int)-and-not-bool guard.
5951
+ Number.isInteger(retryAfterRaw) ? retryAfterRaw : void 0,
5952
+ errorData
5953
+ );
5954
+ }
5955
+ }
5170
5956
  if (response.status === HTTP_INTERNAL_SERVER_ERROR || response.status === HTTP_SERVICE_UNAVAILABLE) {
5171
5957
  const errorData = await __VaultClient.#safeParseJson(response);
5172
5958
  throw new BackendError(
@@ -5536,7 +6322,7 @@ request-rule:${effectiveRequestRule}`;
5536
6322
  }
5537
6323
  const effectiveGrantId = grantId ?? null;
5538
6324
  let currentUrl = url;
5539
- const context = options?.context;
6325
+ const context = options?.context ?? getCurrentAuditContext() ?? void 0;
5540
6326
  const methodStr = String(method).toUpperCase();
5541
6327
  const urlLower = currentUrl.toLowerCase();
5542
6328
  if (!ALLOWED_URL_SCHEMES.some((scheme) => urlLower.startsWith(scheme))) {
@@ -5989,7 +6775,9 @@ request-rule:${effectiveRequestRule}`;
5989
6775
  */
5990
6776
  async _authedBackendRequest(method, sdkPath, body, extraHeaders) {
5991
6777
  this.#assertNotClosed();
5992
- const actorHeaders = await this.#getActorRequestHeaders();
6778
+ const actorHeaders = await this.#getActorRequestHeaders(
6779
+ getCurrentAuditContext() ?? void 0
6780
+ );
5993
6781
  const bodyStr = body !== void 0 ? JSON.stringify(body) : void 0;
5994
6782
  const hmac = this.#computeHmacHeaders(
5995
6783
  method.toUpperCase(),
@@ -6099,6 +6887,19 @@ request-rule:${effectiveRequestRule}`;
6099
6887
  return this.#oauthProvidersCache;
6100
6888
  }
6101
6889
  #oauthProvidersCache;
6890
+ /**
6891
+ * Provider API spec discovery: `vault.providerSpecs.list()` /
6892
+ * `.get()` / `.listOperations()` / `.getOperation()`.
6893
+ *
6894
+ * See `provider-specs.ts` for the full API surface.
6895
+ */
6896
+ get providerSpecs() {
6897
+ if (this.#providerSpecsCache === void 0) {
6898
+ this.#providerSpecsCache = new ProviderSpecsNamespace(this);
6899
+ }
6900
+ return this.#providerSpecsCache;
6901
+ }
6902
+ #providerSpecsCache;
6102
6903
  /**
6103
6904
  * Scope catalog discovery: `vault.scopes.list()`.
6104
6905
  */
@@ -6142,13 +6943,11 @@ request-rule:${effectiveRequestRule}`;
6142
6943
  * use the proxy surface, which carries the request method. Endpoints cannot
6143
6944
  * be scoped by a rule (the URL is not a matchable attribute).
6144
6945
  *
6145
- * HITL caveat: when a request is frozen for human approval (202), the
6146
- * per-request rule is NOT re-evaluated on the deferred post-approval
6147
- * execution (it is not snapshotted on the approval). This cannot widen
6148
- * access the rule did not match the frozen request at decision time, and
6149
- * the grant plus every STORED policy rule still bound the execution — but
6150
- * callers attenuating an approval-required flow should prefer a stored rule
6151
- * when the constraint must hold at execute time.
6946
+ * HITL behavior: when a request is frozen for human approval (202), the
6947
+ * per-request rule is snapshotted on the approval and RE-APPLIED on the
6948
+ * deferred post-approval execution, so the same attenuation the caller sent
6949
+ * still binds the re-run (a corrupt snapshot fails closed). The rule can
6950
+ * only narrow, never widen.
6152
6951
  *
6153
6952
  * @example
6154
6953
  * ```typescript
@@ -6365,7 +7164,9 @@ request-rule:${effectiveRequestRule}`;
6365
7164
  requestBody.ttl_seconds = ttlSeconds;
6366
7165
  }
6367
7166
  const bodyStr = JSON.stringify(requestBody);
6368
- const actorHeaders = await this.#getActorRequestHeaders();
7167
+ const actorHeaders = await this.#getActorRequestHeaders(
7168
+ getCurrentAuditContext() ?? void 0
7169
+ );
6369
7170
  const hmacHeaders = this.#computeHmacHeaders("POST", sdkPath, bodyStr);
6370
7171
  let response;
6371
7172
  try {
@@ -6667,6 +7468,14 @@ request-rule:${effectiveRequestRule}`;
6667
7468
  if (options.requiredScopes) {
6668
7469
  sessionBody.required_scopes = options.requiredScopes;
6669
7470
  }
7471
+ if (options.switchAccount !== void 0) {
7472
+ if (typeof options.switchAccount !== "boolean") {
7473
+ throw new AlterValueError("switchAccount must be a boolean");
7474
+ }
7475
+ if (options.switchAccount) {
7476
+ sessionBody.switch_account = true;
7477
+ }
7478
+ }
6670
7479
  if (options.requestedGrant !== void 0) {
6671
7480
  const rg = options.requestedGrant;
6672
7481
  if (rg === null || typeof rg !== "object" || Array.isArray(rg)) {
@@ -7778,7 +8587,7 @@ request-rule:${effectiveRequestRule}`;
7778
8587
  );
7779
8588
  }
7780
8589
  const actorHeaders = await this.#getActorRequestHeaders(
7781
- args.context,
8590
+ args.context ?? getCurrentAuditContext() ?? void 0,
7782
8591
  args.caller
7783
8592
  );
7784
8593
  const hmacHeaders = this.#computeHmacHeaders("POST", sdkPath, bodyStr);
@@ -8301,129 +9110,6 @@ async function _assertIdentity(client, body) {
8301
9110
  );
8302
9111
  }
8303
9112
  }
8304
- var _alterContextStore = new AsyncLocalStorage();
8305
- var RESERVED_CONTEXT_KEYS = /* @__PURE__ */ new Set([
8306
- "agent",
8307
- "parent_agent",
8308
- "run_id",
8309
- "thread_id",
8310
- "tool",
8311
- "tool_call_id",
8312
- "framework"
8313
- ]);
8314
- var MAX_RUN_METADATA_LENGTH = 4096;
8315
- var MAX_RUN_METADATA_KEYS = 20;
8316
- var MAX_RUN_METADATA_KEY_LENGTH = 64;
8317
- var MAX_RUN_METADATA_VALUE_LENGTH = 512;
8318
- function _stampAgentIdentity(ctx, agentId, options) {
8319
- const ambientAgent = ctx.agent;
8320
- if (!options.isAuthoritative && ambientAgent) {
8321
- return;
8322
- }
8323
- if (ambientAgent && ambientAgent !== agentId) {
8324
- ctx.parent_agent = ambientAgent;
8325
- }
8326
- ctx.agent = agentId;
8327
- }
8328
- function _validateRunMetadata(metadata) {
8329
- if (metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) {
8330
- throw new AlterValueError(
8331
- "trace() metadata must be an object of string key/value pairs"
8332
- );
8333
- }
8334
- const keys = Object.keys(metadata);
8335
- if (keys.length === 0) {
8336
- return;
8337
- }
8338
- if (keys.length > MAX_RUN_METADATA_KEYS) {
8339
- throw new AlterValueError(
8340
- `trace() metadata has too many keys (${keys.length}); max is ${MAX_RUN_METADATA_KEYS}`
8341
- );
8342
- }
8343
- const reserved = keys.filter((k) => RESERVED_CONTEXT_KEYS.has(k)).sort();
8344
- if (reserved.length > 0) {
8345
- throw new AlterValueError(
8346
- `trace() metadata contains reserved key(s) ${JSON.stringify(reserved)}; these are owned by the SDK / framework bridges and cannot be set via metadata. Use the explicit \`runId\` / \`threadId\` kwargs for those. Reserved keys: ${JSON.stringify([...RESERVED_CONTEXT_KEYS].sort())}.`
8347
- );
8348
- }
8349
- let totalSize = 0;
8350
- for (const k of keys) {
8351
- const keyBytes = Buffer.byteLength(k, "utf8");
8352
- if (keyBytes > MAX_RUN_METADATA_KEY_LENGTH) {
8353
- throw new AlterValueError(
8354
- `trace() metadata key "${k.slice(0, 32)}" exceeds max length (${keyBytes} > ${MAX_RUN_METADATA_KEY_LENGTH} bytes)`
8355
- );
8356
- }
8357
- const v = metadata[k];
8358
- if (typeof v !== "string") {
8359
- throw new AlterValueError(
8360
- `trace() metadata value for key "${k}" must be a string, got ${typeof v}`
8361
- );
8362
- }
8363
- const valueBytes = Buffer.byteLength(v, "utf8");
8364
- if (valueBytes > MAX_RUN_METADATA_VALUE_LENGTH) {
8365
- throw new AlterValueError(
8366
- `trace() metadata value for key "${k}" exceeds max length (${valueBytes} > ${MAX_RUN_METADATA_VALUE_LENGTH} bytes)`
8367
- );
8368
- }
8369
- totalSize += keyBytes + valueBytes;
8370
- }
8371
- if (totalSize > MAX_RUN_METADATA_LENGTH) {
8372
- throw new AlterValueError(
8373
- `trace() metadata total size ${totalSize} bytes exceeds ${MAX_RUN_METADATA_LENGTH} byte limit`
8374
- );
8375
- }
8376
- }
8377
- async function _withRunScope(options, callback) {
8378
- const { agentId, runId, threadId, parent, metadata } = options;
8379
- if (metadata !== void 0) {
8380
- _validateRunMetadata(metadata);
8381
- }
8382
- const outer = _alterContextStore.getStore();
8383
- const newContext = {};
8384
- if (outer !== void 0) {
8385
- for (const [k, v] of Object.entries(outer)) {
8386
- if (!RESERVED_CONTEXT_KEYS.has(k)) {
8387
- newContext[k] = v;
8388
- }
8389
- }
8390
- if (outer.agent) {
8391
- newContext.agent = outer.agent;
8392
- if (outer.parent_agent) {
8393
- newContext.parent_agent = outer.parent_agent;
8394
- }
8395
- }
8396
- }
8397
- _stampAgentIdentity(newContext, agentId, { isAuthoritative: true });
8398
- if (parent === void 0) {
8399
- } else if (typeof parent === "string" && parent.length > 0) {
8400
- newContext.parent_agent = parent;
8401
- } else {
8402
- delete newContext.parent_agent;
8403
- }
8404
- if (runId !== void 0) {
8405
- if (typeof runId !== "string") {
8406
- throw new AlterValueError(`runId must be a string, got ${typeof runId}`);
8407
- }
8408
- newContext.run_id = runId;
8409
- } else if (outer !== void 0 && outer.run_id !== void 0) {
8410
- newContext.run_id = outer.run_id;
8411
- }
8412
- if (threadId !== void 0) {
8413
- if (typeof threadId !== "string") {
8414
- throw new AlterValueError(
8415
- `threadId must be a string, got ${typeof threadId}`
8416
- );
8417
- }
8418
- newContext.thread_id = threadId;
8419
- } else if (outer !== void 0 && outer.thread_id !== void 0) {
8420
- newContext.thread_id = outer.thread_id;
8421
- }
8422
- if (metadata !== void 0) {
8423
- Object.assign(newContext, metadata);
8424
- }
8425
- return _alterContextStore.run(newContext, callback);
8426
- }
8427
9113
  function _generateLocalRunUuid() {
8428
9114
  const cryptoGlobal = globalThis.crypto;
8429
9115
  if (cryptoGlobal?.randomUUID) {
@@ -8545,6 +9231,14 @@ var Agent = class _Agent {
8545
9231
  get oauthProviders() {
8546
9232
  return this.#client.oauthProviders;
8547
9233
  }
9234
+ /**
9235
+ * Provider API spec discovery (in-band operation discovery):
9236
+ * `agent.providerSpecs.list()` / `.get()` / `.listOperations()` /
9237
+ * `.getOperation()`. Requires the `providers:read` key scope.
9238
+ */
9239
+ get providerSpecs() {
9240
+ return this.#client.providerSpecs;
9241
+ }
8548
9242
  /**
8549
9243
  * User-defined trace spans: `agent.spans.emit()`.
8550
9244
  *
@@ -8604,10 +9298,40 @@ var Agent = class _Agent {
8604
9298
  "App.getAgent(...) is the impersonation surface, NOT delegation-scoped access. request({ grantId }) on this Agent uses the operator credentials and CAN reach grants the named agent has no delegation for. Use new Agent({ apiKey: 'alter_ak_...' }) for true delegation-scoped access, or request({ provider, userToken }) here to route through the agent's delegations."
8605
9299
  );
8606
9300
  }
8607
- return this.#client.request(method, url, options);
9301
+ try {
9302
+ return await this.#client.request(method, url, options);
9303
+ } catch (e) {
9304
+ throw this.#toAgentGrantError(e);
9305
+ }
8608
9306
  }
8609
9307
  async proxyRequest(...args) {
8610
- return this.#client.proxyRequest(...args);
9308
+ try {
9309
+ return await this.#client.proxyRequest(...args);
9310
+ } catch (e) {
9311
+ throw this.#toAgentGrantError(e);
9312
+ }
9313
+ }
9314
+ // On the agent path a bare `grant_not_found` 404 is ambiguous — the grant
9315
+ // may not exist, may not be delegated to THIS agent, or a user/base grant_id
9316
+ // was passed where the agent's own delegation id was required (an agent can
9317
+ // only address its own delegation by id). The generic
9318
+ // `GrantNotFoundError` message reads as "stale grant → reconnect", which
9319
+ // sends integrators into a loop. Re-map it to the actionable
9320
+ // `AgentDelegationMissingError` (a `GrantNotFoundError` subclass, so existing
9321
+ // `instanceof GrantNotFoundError` catches still fire). Leave every other
9322
+ // error untouched — including the already-typed `NoDelegatedGrantError`
9323
+ // (a distinct class, not a subclass) and an already-remapped subclass.
9324
+ #toAgentGrantError(e) {
9325
+ if (e instanceof GrantNotFoundError && !(e instanceof AgentDelegationMissingError)) {
9326
+ return new AgentDelegationMissingError(
9327
+ "No grant is resolvable for this agent by the grant_id supplied. The grant may exist but not be delegated to this agent, or a user/base grant_id was passed where the agent's own delegation id is required (get it from agent.listGrants). Run agent.createConnectSession(...) to delegate this agent, or resolve by provider (omit grantId) to route through the agent's existing delegations.",
9328
+ e.details,
9329
+ e.providerId,
9330
+ e.agentId,
9331
+ e.appUserId
9332
+ );
9333
+ }
9334
+ return e;
8611
9335
  }
8612
9336
  // ── trace — audit-context scope for nested calls (TS callback shape) ───
8613
9337
  /**
@@ -8870,6 +9594,8 @@ var App = class _App {
8870
9594
  keys;
8871
9595
  /** OAuth provider catalog discovery. See {@link OAuthProvidersNamespace}. */
8872
9596
  oauthProviders;
9597
+ /** Provider API spec discovery. See {@link ProviderSpecsNamespace}. */
9598
+ providerSpecs;
8873
9599
  /** Scope catalog discovery. See {@link ScopesNamespace}. */
8874
9600
  scopes;
8875
9601
  /** User-defined trace spans. See {@link SpansNamespace}. */
@@ -8890,6 +9616,7 @@ var App = class _App {
8890
9616
  this.agents = this.#client.agents;
8891
9617
  this.keys = this.#client.keys;
8892
9618
  this.oauthProviders = this.#client.oauthProviders;
9619
+ this.providerSpecs = this.#client.providerSpecs;
8893
9620
  this.scopes = this.#client.scopes;
8894
9621
  this.spans = this.#client.spans;
8895
9622
  return;
@@ -8908,6 +9635,7 @@ var App = class _App {
8908
9635
  this.agents = this.#client.agents;
8909
9636
  this.keys = this.#client.keys;
8910
9637
  this.oauthProviders = this.#client.oauthProviders;
9638
+ this.providerSpecs = this.#client.providerSpecs;
8911
9639
  this.scopes = this.#client.scopes;
8912
9640
  this.spans = this.#client.spans;
8913
9641
  _appCredentials.set(this, {
@@ -9717,7 +10445,7 @@ var DEFAULT_BASE_URL = "https://backend.alterauth.com";
9717
10445
  var PAT_API_PREFIX = "/api/v1/dev-portal";
9718
10446
  var HTTP_ERROR_THRESHOLD = 400;
9719
10447
  var DEFAULT_TIMEOUT_MS = 3e4;
9720
- var CLI_VERSION = "0.5.0";
10448
+ var CLI_VERSION = "0.6.0";
9721
10449
  var USER_AGENT = buildUserAgent();
9722
10450
  function buildUserAgent() {
9723
10451
  let osTag = "";
@@ -9885,7 +10613,8 @@ var DashboardClient = class {
9885
10613
  keys;
9886
10614
  /** Managed-agent CRUD (CLI `alter agents` subcommands). */
9887
10615
  agents;
9888
- /** OAuth provider configs (CLI `alter providers` subcommands). */
10616
+ /** OAuth provider configs + the provider API spec store
10617
+ * (CLI `alter providers` subcommands). */
9889
10618
  providers;
9890
10619
  /** Identity-provider create + discover (CLI `alter identity-providers`
9891
10620
  * subcommands). Requires ``dashboard_identity_providers:create``. */
@@ -10569,6 +11298,13 @@ function isProviderCatalogEntry(value) {
10569
11298
  const v = value;
10570
11299
  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") && typeof v.status === "string";
10571
11300
  }
11301
+ function isProviderSpecMeta(value) {
11302
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
11303
+ return false;
11304
+ }
11305
+ const v = value;
11306
+ return typeof v.provider_kind === "string" && typeof v.provider_id === "string" && typeof v.version === "number" && typeof v.provenance === "string" && (v.source_url === null || typeof v.source_url === "string") && typeof v.content_hash === "string" && typeof v.operation_count === "number" && typeof v.fetched_at === "string" && typeof v.changed_at === "string" && (v.title === null || typeof v.title === "string") && (v.spec_version === null || typeof v.spec_version === "string");
11307
+ }
10572
11308
  var ProvidersNamespace = class {
10573
11309
  #client;
10574
11310
  constructor(client) {
@@ -10679,6 +11415,96 @@ var ProvidersNamespace = class {
10679
11415
  query ? { query } : {}
10680
11416
  );
10681
11417
  }
11418
+ // ── Provider API spec store (in-band operation discovery) ──────────
11419
+ //
11420
+ // Read-only catalog data served by ``/provider-specs`` on the
11421
+ // dev-portal surface. Any authenticated PAT — no ``dashboard_*``
11422
+ // scope gate (the specs are public provider documentation, the gate
11423
+ // is just "be a signed-in operator").
11424
+ /**
11425
+ * List every provider's active spec metadata (the freshness /
11426
+ * provenance dashboard). ``GET /provider-specs[?kind=oauth|managed]``.
11427
+ */
11428
+ async listSpecs(options = {}) {
11429
+ const query = {};
11430
+ if (options.kind !== void 0) query.kind = options.kind;
11431
+ const body = await this.#client._call(
11432
+ "GET",
11433
+ "/provider-specs",
11434
+ "providers.list_specs",
11435
+ Object.keys(query).length > 0 ? { query } : {}
11436
+ );
11437
+ const dict = expectDict(body, "providers.list_specs", 200);
11438
+ if (!Array.isArray(dict.items) || typeof dict.total !== "number") {
11439
+ throw makeBackendError(
11440
+ "Dashboard client received an unexpected response shape for providers.list_specs: expected { items, total }",
11441
+ 200,
11442
+ body
11443
+ );
11444
+ }
11445
+ for (const [i, row] of dict.items.entries()) {
11446
+ if (!isProviderSpecMeta(row)) {
11447
+ throw makeBackendError(
11448
+ `Dashboard client received an unexpected provider-spec row at index ${i}: expected { provider_kind, provider_id, version, provenance, source_url, content_hash, operation_count, fetched_at, changed_at, title, spec_version }`,
11449
+ 200,
11450
+ row
11451
+ );
11452
+ }
11453
+ }
11454
+ return dict;
11455
+ }
11456
+ /**
11457
+ * Page one provider's operations from its active spec.
11458
+ * ``GET /provider-specs/{kind}/{provider_id}/operations``.
11459
+ * ``search`` filters server-side; ``limit`` is server-clamped to
11460
+ * 1–500 (default 100).
11461
+ */
11462
+ async listSpecOperations(kind, providerId, options = {}) {
11463
+ const k = encodePathParam(kind, "kind");
11464
+ const provider = encodePathParam(providerId, "providerId");
11465
+ const query = {};
11466
+ if (options.search !== void 0) query.search = options.search;
11467
+ if (options.limit !== void 0) query.limit = String(options.limit);
11468
+ if (options.offset !== void 0) query.offset = String(options.offset);
11469
+ const body = await this.#client._call(
11470
+ "GET",
11471
+ `/provider-specs/${k}/${provider}/operations`,
11472
+ "providers.list_spec_operations",
11473
+ Object.keys(query).length > 0 ? { query } : {}
11474
+ );
11475
+ const page = expectEnvelope(body, "providers.list_spec_operations", 200);
11476
+ const spec = page.spec;
11477
+ if (!isProviderSpecMeta(spec)) {
11478
+ throw makeBackendError(
11479
+ "Dashboard client received an unexpected response shape for providers.list_spec_operations: envelope is missing the spec metadata",
11480
+ 200,
11481
+ body
11482
+ );
11483
+ }
11484
+ return page;
11485
+ }
11486
+ /**
11487
+ * One operation with its full parameter/request/response schemas.
11488
+ * ``GET /provider-specs/{kind}/{provider_id}/operations/{operation_id}``.
11489
+ *
11490
+ * Operation ids may contain slashes (GitHub's ``repos/get``) — the
11491
+ * backend route uses a ``:path`` converter, so each SEGMENT of the id
11492
+ * is encoded individually and the literal ``/`` separators are kept.
11493
+ * ``encodePathParam`` per segment preserves the traversal guard: an
11494
+ * id smuggling ``..`` (or an empty segment) is rejected client-side
11495
+ * instead of URL-normalizing onto a different route.
11496
+ */
11497
+ async getSpecOperation(kind, providerId, operationId) {
11498
+ const k = encodePathParam(kind, "kind");
11499
+ const provider = encodePathParam(providerId, "providerId");
11500
+ const op = operationId.split("/").map((segment) => encodePathParam(segment, "operationId")).join("/");
11501
+ const body = await this.#client._call(
11502
+ "GET",
11503
+ `/provider-specs/${k}/${provider}/operations/${op}`,
11504
+ "providers.get_spec_operation"
11505
+ );
11506
+ return expectDict(body, "providers.get_spec_operation", 200);
11507
+ }
10682
11508
  };
10683
11509
  var IdentityProvidersNamespace = class {
10684
11510
  #client;
@@ -10689,7 +11515,7 @@ var IdentityProvidersNamespace = class {
10689
11515
  * Configure an end-user identity provider for an app. Requires
10690
11516
  * `dashboard_identity_providers:create`. ``create`` and ``webhooks`` are the
10691
11517
  * TWO identity-provider carve-outs on the PAT surface (update / delete /
10692
- * config-reads stay Clerk-only). ``create`` is additive and conflict-guarded
11518
+ * config-reads stay session-only). ``create`` is additive and conflict-guarded
10693
11519
  * (one IDP per app → the backend returns 409 if one already exists, so it can
10694
11520
  * never re-point an already-trusted issuer) and fail-closed on OIDC
10695
11521
  * discovery. BOTH ``create`` and ``webhooks`` (the latter returns the IDP
@@ -10738,7 +11564,7 @@ var IdentityProvidersNamespace = class {
10738
11564
  }
10739
11565
  // ── Webhook lifecycle — requires `dashboard_identity_providers:webhooks`
10740
11566
  // (catalog v7). Per-IDP, recoverable; does NOT touch the issuer/claim
10741
- // trust config (update/delete stay Clerk-only).
11567
+ // trust config (update/delete stay session-only).
10742
11568
  /** Enable webhook integration and return the signing secret (shown once). */
10743
11569
  async enableWebhook(appId, providerId, options = {}) {
10744
11570
  const app = encodePathParam(appId, "appId");
@@ -10970,6 +11796,32 @@ var ApprovalsNamespace = class {
10970
11796
  );
10971
11797
  return expectDict(body, "approvals.defaults", 200);
10972
11798
  }
11799
+ /** List an app's HITL approvals (newest first), group-aware (N-of-N). */
11800
+ async list(appId, opts = {}) {
11801
+ const app = encodePathParam(appId, "appId");
11802
+ const params = new URLSearchParams();
11803
+ if (opts.status) params.set("status", opts.status);
11804
+ if (opts.limit !== void 0) params.set("limit", String(opts.limit));
11805
+ if (opts.offset !== void 0) params.set("offset", String(opts.offset));
11806
+ const qs = params.toString();
11807
+ const body = await this.#client._call(
11808
+ "GET",
11809
+ `/apps/${app}/approvals${qs ? `?${qs}` : ""}`,
11810
+ "approvals.list"
11811
+ );
11812
+ return expectDict(body, "approvals.list", 200);
11813
+ }
11814
+ /** One approval with its full gate breakdown + request-rule snapshot. */
11815
+ async get(appId, approvalId) {
11816
+ const app = encodePathParam(appId, "appId");
11817
+ const id = encodePathParam(approvalId, "approvalId");
11818
+ const body = await this.#client._call(
11819
+ "GET",
11820
+ `/apps/${app}/approvals/${id}`,
11821
+ "approvals.get"
11822
+ );
11823
+ return expectDict(body, "approvals.get", 200);
11824
+ }
10973
11825
  };
10974
11826
  var PolicyNamespace = class {
10975
11827
  #client;
@@ -10978,7 +11830,7 @@ var PolicyNamespace = class {
10978
11830
  }
10979
11831
  // NOTE: ``getOrgKeyPolicy`` + ``updateOrgKeyPolicy`` are NOT exposed.
10980
11832
  // ``GET /organizations/current/key-policy`` AND ``PATCH
10981
- // /organizations/current/key-policy`` are BOTH Clerk-only — per
11833
+ // /organizations/current/key-policy`` are BOTH session-only — per
10982
11834
  // CLAUDE.md "Destructive-Action Policy", org-wide config (reads
10983
11835
  // AND writes) is dashboard-only. The read carve-out that briefly
10984
11836
  // existed was rolled back because the response body is a
@@ -11037,9 +11889,16 @@ var PolicyNamespace = class {
11037
11889
  }
11038
11890
  /**
11039
11891
  * Dry-run the app's custom policy rules for one request shape (grant / method /
11040
- * endpoint / agent / client-ip / time) and return the composed verdict + per-rule
11041
- * trace. A read with no side effects (the route is a GET). Requires
11042
- * `dashboard_app_policy:read`. Foreign/unknown grant or agent ids → 404.
11892
+ * endpoint / agent / client-ip / time / operation / params) and return the
11893
+ * composed verdict + per-rule trace. A read with no side effects (the route
11894
+ * is a GET). Requires `dashboard_app_policy:read`. Foreign/unknown grant or
11895
+ * agent ids → 404.
11896
+ *
11897
+ * ``operationId`` is the explicit attested-operation override the backend
11898
+ * accepts as the ``operation`` query param (service-layer ``operation_id``);
11899
+ * ``simulatedParams`` is the simulated projected parameter bag, JSON-encoded
11900
+ * onto the ``params`` query param (service-layer ``simulated_params``) —
11901
+ * same wire shape the dashboard's policy explorer sends.
11043
11902
  */
11044
11903
  async simulate(appId, options) {
11045
11904
  const query = {
@@ -11052,6 +11911,10 @@ var PolicyNamespace = class {
11052
11911
  if (options.agentId !== void 0) query.agent_id = options.agentId;
11053
11912
  if (options.clientIp !== void 0) query.client_ip = options.clientIp;
11054
11913
  if (options.at !== void 0) query.at = options.at;
11914
+ if (options.operationId !== void 0)
11915
+ query.operation = options.operationId;
11916
+ if (options.simulatedParams !== void 0)
11917
+ query.params = JSON.stringify(options.simulatedParams);
11055
11918
  const out = await this.#client._call(
11056
11919
  "GET",
11057
11920
  `/apps/${encodePathParam(appId, "appId")}/policies/simulate`,
@@ -12007,6 +12870,7 @@ var isBoolean = (v) => typeof v === "boolean";
12007
12870
  var isOptionalString = (v) => v === null || typeof v === "string";
12008
12871
  var isOptionalNumber = (v) => v === null || typeof v === "number";
12009
12872
  var isOptionalBoolean = (v) => v === null || v === void 0 || typeof v === "boolean";
12873
+ var isAbsentOrOptionalString = (v) => v === null || v === void 0 || typeof v === "string";
12010
12874
  var isOptionalStringArray = (v) => v === null || v === void 0 || Array.isArray(v) && v.every((x) => typeof x === "string");
12011
12875
  function extractArrayField(body, field, context) {
12012
12876
  const value = body[field];
@@ -12731,6 +13595,18 @@ function surfaceApproverWarnings(row) {
12731
13595
  );
12732
13596
  }
12733
13597
  }
13598
+ function surfaceCatalogWarnings(row) {
13599
+ if (typeof row !== "object" || row === null) return;
13600
+ const warnings = row.catalog_warnings;
13601
+ if (!Array.isArray(warnings)) return;
13602
+ for (const warning of warnings) {
13603
+ if (typeof warning !== "string" || warning.length === 0) continue;
13604
+ process.stderr.write(
13605
+ `alter: catalog warning \u2014 ${sanitizeStderrText(warning)} (save was allowed)
13606
+ `
13607
+ );
13608
+ }
13609
+ }
12734
13610
 
12735
13611
  // src/commands/agents.ts
12736
13612
  var AGENT_COLUMNS = [
@@ -13646,21 +14522,114 @@ function buildAppsCommand() {
13646
14522
 
13647
14523
  // src/commands/approvals.ts
13648
14524
  import { Command as Command4 } from "commander";
14525
+ var APPROVAL_STATUSES = [
14526
+ "pending",
14527
+ "approved",
14528
+ "executing",
14529
+ "denied",
14530
+ "expired",
14531
+ "executed",
14532
+ "failed"
14533
+ ];
14534
+ var APPROVAL_ROW_CHECKS = {
14535
+ approval_id: isString,
14536
+ group_status: isString,
14537
+ gate_count: (v) => typeof v === "number",
14538
+ grant_id: isString,
14539
+ grant_type: isString,
14540
+ provider_id: isOptionalString,
14541
+ created_at: isString,
14542
+ expires_at: isString
14543
+ };
14544
+ var APPROVAL_LIST_COLUMNS = [
14545
+ { label: "ID", get: (a) => a.approval_id },
14546
+ { label: "GROUP", get: (a) => a.group_status },
14547
+ { label: "GATES", get: (a) => String(a.gate_count) },
14548
+ // provider_id is null for managed-secret grants — fall back to the
14549
+ // grant family so the target column is never blank.
14550
+ {
14551
+ label: "PROVIDER",
14552
+ get: (a) => a.provider_id ?? a.grant_type,
14553
+ maxWidth: 20
14554
+ },
14555
+ { label: "GRANT", get: (a) => a.grant_id, maxWidth: 36 },
14556
+ { label: "REQUESTED", get: (a) => a.created_at, maxWidth: 24 },
14557
+ { label: "EXPIRES", get: (a) => a.expires_at, maxWidth: 24 }
14558
+ ];
13649
14559
  function buildApprovalsCommand() {
13650
14560
  const approvals = new Command4("approvals").description(
13651
- "Inspect HITL approval configuration (read-only)"
14561
+ "Inspect HITL approvals and approval configuration (read-only)"
14562
+ );
14563
+ approvals.command("defaults").description("Show HITL approval deployment defaults and per-grant bounds").option(
14564
+ "--output <format>",
14565
+ "Output format: json|jsonl|table (default: json)",
14566
+ "json"
14567
+ ).action(async (options) => {
14568
+ const format = coerceOutputFormat(options.output);
14569
+ await withClient(async (client) => {
14570
+ const row = await client.approvals.defaults();
14571
+ emit2(format, row);
14572
+ });
14573
+ });
14574
+ approvals.command("list").description(
14575
+ "List an app's HITL approvals (newest first), with the N-of-N group status per row"
14576
+ ).option("--app <id>", "App ID (or ALTER_APP_ID)").option(
14577
+ "--status <status>",
14578
+ `Filter by row status (${APPROVAL_STATUSES.join("|")})`
14579
+ ).option(
14580
+ "--limit <n>",
14581
+ "Page size (1-200, default 50)",
14582
+ parseBoundedInt("--limit", 1, 200)
14583
+ ).option("--offset <n>", "Page offset", parseNonNegativeInt("--offset")).option(
14584
+ "--output <format>",
14585
+ "Output format: json|jsonl|table (default: table)",
14586
+ "table"
14587
+ ).action(
14588
+ async (options) => {
14589
+ const appId = resolveAppIdOrExit(options.app);
14590
+ const format = coerceOutputFormat(options.output);
14591
+ const status = options.status === void 0 ? void 0 : validateChoice("--status", options.status, APPROVAL_STATUSES);
14592
+ await withClient(async (client) => {
14593
+ const page = await client.approvals.list(appId, {
14594
+ ...status ? { status } : {},
14595
+ ...options.limit !== void 0 ? { limit: options.limit } : {},
14596
+ ...options.offset !== void 0 ? { offset: options.offset } : {}
14597
+ });
14598
+ const items = extractArrayField(page, "items", "approvals.list");
14599
+ const rows = validateRows(
14600
+ items,
14601
+ APPROVAL_ROW_CHECKS,
14602
+ "approvals.list"
14603
+ );
14604
+ emit2(format, rows, APPROVAL_LIST_COLUMNS);
14605
+ const total = page.total;
14606
+ const offset = page.offset;
14607
+ if (page.has_more === true && typeof total === "number" && typeof offset === "number") {
14608
+ process.stderr.write(
14609
+ `alter: showing ${rows.length} of ${total} approvals \u2014 pass --offset ${offset + rows.length} for the next page
14610
+ `
14611
+ );
14612
+ }
14613
+ });
14614
+ }
13652
14615
  );
13653
- approvals.command("defaults").description("Show HITL approval deployment defaults and per-grant bounds").option(
14616
+ approvals.command("show").description(
14617
+ "Show one approval with its full N-of-N gate breakdown (per-gate status/approver) and the request-rule snapshot"
14618
+ ).option("--app <id>", "App ID (or ALTER_APP_ID)").requiredOption("--approval <id>", "Approval ID (any gate of the group)").option(
13654
14619
  "--output <format>",
13655
14620
  "Output format: json|jsonl|table (default: json)",
13656
14621
  "json"
13657
- ).action(async (options) => {
13658
- const format = coerceOutputFormat(options.output);
13659
- await withClient(async (client) => {
13660
- const row = await client.approvals.defaults();
13661
- emit2(format, row);
13662
- });
13663
- });
14622
+ ).action(
14623
+ async (options) => {
14624
+ const appId = resolveAppIdOrExit(options.app);
14625
+ const format = coerceOutputFormat(options.output);
14626
+ validateUuidOrExit("--approval", options.approval);
14627
+ await withClient(async (client) => {
14628
+ const row = await client.approvals.get(appId, options.approval);
14629
+ emit2(format, row);
14630
+ });
14631
+ }
14632
+ );
13664
14633
  return approvals;
13665
14634
  }
13666
14635
 
@@ -14717,7 +15686,7 @@ var DASHBOARD_RESOURCE_VERBS = {
14717
15686
  // signing secret. BOTH ``create`` and ``webhooks`` are in the backend's
14718
15687
  // ``WILDCARD_EXCLUDED_VERBS`` — only the literal scope grants either, never
14719
15688
  // ``*`` or ``dashboard_identity_providers:*``. Update, delete, and config
14720
- // reads stay Clerk-only (CLAUDE.md Destructive-Action Policy). Backend source
15689
+ // reads stay session-only (CLAUDE.md Destructive-Action Policy). Backend source
14721
15690
  // of truth: _DASHBOARD_RESOURCES_V7.
14722
15691
  dashboard_identity_providers: ["create", "webhooks"],
14723
15692
  // ``dashboard_analytics`` (catalog v7): read-only org usage observability
@@ -16193,6 +17162,13 @@ var GRANT_COLUMNS = [
16193
17162
  { label: "TYPE", get: (g) => g.grant_type },
16194
17163
  { label: "PROVIDER", get: (g) => g.provider_name, maxWidth: 24 },
16195
17164
  { label: "STATUS", get: (g) => g.status },
17165
+ {
17166
+ // The grant-TTL expiry (access duration chosen at consent) — the axis
17167
+ // STATUS's TTL-aware "expired" derives from. Date-only keeps the table
17168
+ // narrow; "—" = perpetual (no TTL) or a pre-field backend.
17169
+ label: "EXPIRES",
17170
+ get: (g) => g.grant_expires_at ? g.grant_expires_at.slice(0, 10) : "\u2014"
17171
+ },
16196
17172
  {
16197
17173
  label: "USER",
16198
17174
  get: (g) => g.user_identifier ?? g.account_display_name ?? "\u2014",
@@ -16283,6 +17259,7 @@ function buildGrantsCommand() {
16283
17259
  created_at: isString,
16284
17260
  last_used_at: isOptionalString,
16285
17261
  expires_at: isOptionalString,
17262
+ grant_expires_at: isAbsentOrOptionalString,
16286
17263
  parent_grant_id: isOptionalString,
16287
17264
  depth: isOptionalNumber,
16288
17265
  delegable: isOptionalBoolean,
@@ -18911,8 +19888,20 @@ var RULE_TYPES = [
18911
19888
  "ip_allowlist",
18912
19889
  "time_window",
18913
19890
  "require_approval",
18914
- "restriction"
19891
+ "restriction",
19892
+ "quota",
19893
+ "content_match"
19894
+ ];
19895
+ var TIME_WINDOW_DAYS = [
19896
+ "mon",
19897
+ "tue",
19898
+ "wed",
19899
+ "thu",
19900
+ "fri",
19901
+ "sat",
19902
+ "sun"
18915
19903
  ];
19904
+ var QUOTA_PERIODS = ["minute", "hour", "day", "month"];
18916
19905
  var MATCHABLE_ATTRIBUTES = [
18917
19906
  "app_id",
18918
19907
  "environment",
@@ -18921,7 +19910,9 @@ var MATCHABLE_ATTRIBUTES = [
18921
19910
  "provider_id",
18922
19911
  "resource_kind",
18923
19912
  "client_ip",
18924
- "method"
19913
+ "method",
19914
+ "operation",
19915
+ "family"
18925
19916
  ];
18926
19917
  var MAX_WHEN_LIST_VALUES = 100;
18927
19918
  var MAX_WHEN_VALUE_LEN = 512;
@@ -18987,6 +19978,11 @@ function validateWhenMapOrExit(when) {
18987
19978
  `"when.client_ip" matches EXACT IPs only \u2014 CIDR ranges belong in an ip_allowlist rule (--type ip_allowlist)`
18988
19979
  );
18989
19980
  }
19981
+ if (attr === "family" && !OPERATION_FAMILIES.includes(v)) {
19982
+ fail(
19983
+ `when.family names unknown operation family ${JSON.stringify(v)} (expected one of: ${OPERATION_FAMILIES.join(", ")})`
19984
+ );
19985
+ }
18990
19986
  }
18991
19987
  }
18992
19988
  }
@@ -19004,6 +20000,33 @@ function validateJsonMatchBodyOrExit(body) {
19004
20000
  }
19005
20001
  validateWhenMapOrExit(body.when);
19006
20002
  }
20003
+ function validateRequireApprovalWhenOrExit(when) {
20004
+ const fail = failRuleBody;
20005
+ if (typeof when !== "object" || when === null || Array.isArray(when)) {
20006
+ fail(`"when" must be an object of condition keys`);
20007
+ }
20008
+ const record = when;
20009
+ const metadata = {};
20010
+ let hasContent = false;
20011
+ for (const [key, value] of Object.entries(record)) {
20012
+ if (key === "match" || key === "params") {
20013
+ hasContent = true;
20014
+ } else {
20015
+ metadata[key] = value;
20016
+ }
20017
+ }
20018
+ if (Object.keys(metadata).length > 0) validateWhenMapOrExit(metadata);
20019
+ if (hasContent) {
20020
+ validateContentMatchBodyOrExit({
20021
+ match: record.match,
20022
+ ...record.params !== void 0 ? { params: record.params } : {},
20023
+ effect: "deny"
20024
+ });
20025
+ }
20026
+ if (Object.keys(metadata).length === 0 && !hasContent) {
20027
+ fail(`"when" must contain at least one condition`);
20028
+ }
20029
+ }
19007
20030
  var HITL_MAX_APPROVERS = 10;
19008
20031
  var HITL_MIN_EXPIRY_SECONDS = 60;
19009
20032
  var HITL_MAX_EXPIRY_SECONDS = 86400;
@@ -19048,7 +20071,7 @@ function validateRequireApprovalBodyOrExit(body) {
19048
20071
  if (body.effect !== "require_approval") {
19049
20072
  fail(`effect must be "require_approval"`);
19050
20073
  }
19051
- if (body.when !== void 0) validateWhenMapOrExit(body.when);
20074
+ if (body.when !== void 0) validateRequireApprovalWhenOrExit(body.when);
19052
20075
  const approval = body.approval;
19053
20076
  if (typeof approval !== "object" || approval === null || Array.isArray(approval)) {
19054
20077
  fail(
@@ -19190,37 +20213,95 @@ function validateIpAllowlistBodyOrExit(body) {
19190
20213
  );
19191
20214
  }
19192
20215
  }
19193
- var TIME_WINDOW_KEYS = [
19194
- "business_hours_only",
19195
- "weekdays_only",
19196
- "timezone"
19197
- ];
20216
+ var TIME_WINDOW_KEYS = ["windows", "timezone"];
20217
+ var TIME_WINDOW_WINDOW_KEYS = ["days", "start", "end"];
20218
+ var HHMM_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
19198
20219
  function validateTimeWindowBodyOrExit(body) {
19199
20220
  const fail = failRuleBody;
19200
20221
  for (const k of Object.keys(body)) {
19201
20222
  if (!TIME_WINDOW_KEYS.includes(k)) {
19202
20223
  fail(
19203
- `unknown key ${JSON.stringify(k)} (expected: ${TIME_WINDOW_KEYS.join(", ")})`
20224
+ `unknown key ${JSON.stringify(k)} (expected: ${TIME_WINDOW_KEYS.join(", ")}). The legacy business_hours_only/weekdays_only grammar was removed \u2014 use {"windows": [{"days": [...], "start": "HH:MM", "end": "HH:MM"}], "timezone": "<IANA>"}`
19204
20225
  );
19205
20226
  }
19206
20227
  }
19207
- for (const k of ["business_hours_only", "weekdays_only"]) {
19208
- if (body[k] !== void 0 && typeof body[k] !== "boolean") {
19209
- fail(`"${k}" must be a boolean`);
20228
+ const windows = body.windows;
20229
+ if (!Array.isArray(windows) || windows.length === 0) {
20230
+ fail(`"windows" must be a non-empty array`);
20231
+ return;
20232
+ }
20233
+ if (windows.length > 20) {
20234
+ fail(`"windows" may not exceed 20 entries`);
20235
+ }
20236
+ windows.forEach((w, i) => {
20237
+ if (typeof w !== "object" || w === null || Array.isArray(w)) {
20238
+ fail(`windows[${i}] must be an object`);
20239
+ return;
20240
+ }
20241
+ const win = w;
20242
+ for (const k of Object.keys(win)) {
20243
+ if (!TIME_WINDOW_WINDOW_KEYS.includes(k)) {
20244
+ fail(`windows[${i}] has unknown key ${JSON.stringify(k)}`);
20245
+ }
20246
+ }
20247
+ const days = win.days;
20248
+ if (!Array.isArray(days) || days.length === 0) {
20249
+ fail(`windows[${i}].days must be a non-empty array`);
20250
+ } else {
20251
+ for (const d of days) {
20252
+ if (typeof d !== "string" || !TIME_WINDOW_DAYS.includes(d)) {
20253
+ fail(
20254
+ `windows[${i}].days has invalid day ${JSON.stringify(d)} (use ${TIME_WINDOW_DAYS.join("/")})`
20255
+ );
20256
+ }
20257
+ }
20258
+ if (new Set(days).size !== days.length) {
20259
+ fail(`windows[${i}].days has duplicate day(s)`);
20260
+ }
20261
+ }
20262
+ const start = win.start;
20263
+ const end = win.end;
20264
+ if (typeof start !== "string" || !HHMM_RE.test(start)) {
20265
+ fail(`windows[${i}].start must be "HH:MM" (00:00\u201323:59)`);
20266
+ }
20267
+ if (typeof end !== "string" || !(HHMM_RE.test(end) || end === "24:00")) {
20268
+ fail(`windows[${i}].end must be "HH:MM" (00:00\u201323:59) or "24:00"`);
20269
+ }
20270
+ if (typeof start === "string" && start === end) {
20271
+ fail(`windows[${i}] start == end (a zero-length window can never match)`);
19210
20272
  }
20273
+ });
20274
+ if (typeof body.timezone !== "string" || body.timezone.trim().length === 0) {
20275
+ fail(
20276
+ `"timezone" is required \u2014 a non-empty IANA timezone string (e.g. "America/New_York")`
20277
+ );
20278
+ return;
19211
20279
  }
19212
- if (body.business_hours_only !== true && body.weekdays_only !== true) {
20280
+ try {
20281
+ new Intl.DateTimeFormat(void 0, { timeZone: body.timezone });
20282
+ } catch {
19213
20283
  fail(
19214
- `at least one of "business_hours_only" / "weekdays_only" must be true`
20284
+ `"timezone" ${JSON.stringify(body.timezone)} is not a valid IANA timezone name (e.g. "America/New_York")`
19215
20285
  );
19216
20286
  }
19217
- if (body.timezone !== void 0) {
19218
- if (typeof body.timezone !== "string" || body.timezone.trim().length === 0) {
20287
+ }
20288
+ var QUOTA_KEYS = ["limit", "period"];
20289
+ function validateQuotaBodyOrExit(body) {
20290
+ const fail = failRuleBody;
20291
+ for (const k of Object.keys(body)) {
20292
+ if (!QUOTA_KEYS.includes(k)) {
19219
20293
  fail(
19220
- `"timezone" must be a non-empty IANA timezone string (e.g. "America/New_York")`
20294
+ `unknown key ${JSON.stringify(k)} (expected: ${QUOTA_KEYS.join(", ")})`
19221
20295
  );
19222
20296
  }
19223
20297
  }
20298
+ const limit = body.limit;
20299
+ if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1 || limit > 1e6) {
20300
+ fail(`"limit" must be an integer between 1 and 1000000`);
20301
+ }
20302
+ if (typeof body.period !== "string" || !QUOTA_PERIODS.includes(body.period)) {
20303
+ fail(`"period" must be one of ${QUOTA_PERIODS.join("/")}`);
20304
+ }
19224
20305
  }
19225
20306
  var RESTRICTION_KEYS = ["allowed_methods", "allowed_endpoints"];
19226
20307
  var MAX_RESTRICTION_METHODS = 7;
@@ -19281,6 +20362,195 @@ function validateRestrictionBodyOrExit(body) {
19281
20362
  );
19282
20363
  }
19283
20364
  }
20365
+ var CONTENT_MATCH_KEYS = [
20366
+ "match",
20367
+ "params",
20368
+ "effect",
20369
+ "redact",
20370
+ "step_up"
20371
+ ];
20372
+ var CONTENT_MATCH_MATCH_KEYS = ["operations", "families"];
20373
+ var CONTENT_MATCH_EFFECTS = ["deny", "redact", "step_up"];
20374
+ var CONTENT_NUMERIC_OPS = ["gt", "gte", "lt", "lte"];
20375
+ var CONTENT_PARAM_OPS = [
20376
+ "equals",
20377
+ "any_in",
20378
+ "not_subset_of",
20379
+ "gt",
20380
+ "gte",
20381
+ "lt",
20382
+ "lte"
20383
+ ];
20384
+ var OPERATION_FAMILIES = [
20385
+ "send",
20386
+ "read",
20387
+ "write",
20388
+ "delete",
20389
+ "admin",
20390
+ "payment"
20391
+ ];
20392
+ var MAX_CONTENT_MATCH_ENTRIES = 100;
20393
+ var MAX_CONTENT_OPERATION_ID_LEN = 255;
20394
+ var MAX_CONTENT_PARAM_CONDITIONS = 20;
20395
+ var MAX_CONTENT_PARAM_NAME_LEN = 120;
20396
+ var MAX_CONTENT_VALUE_ENTRIES = 200;
20397
+ var MAX_CONTENT_VALUE_LEN = 512;
20398
+ var MAX_CONTENT_REDACT_FIELDS = 50;
20399
+ var MAX_CONTENT_REDACT_FIELD_LEN = 120;
20400
+ var CONTENT_STEP_UP_MIN_SECONDS = 1;
20401
+ var CONTENT_STEP_UP_MAX_SECONDS = 86400;
20402
+ function validateContentMatchBodyOrExit(body) {
20403
+ const fail = failRuleBody;
20404
+ for (const k of Object.keys(body)) {
20405
+ if (!CONTENT_MATCH_KEYS.includes(k)) {
20406
+ fail(
20407
+ `unknown key ${JSON.stringify(k)} (expected: ${CONTENT_MATCH_KEYS.join(", ")})`
20408
+ );
20409
+ }
20410
+ }
20411
+ const match = body.match;
20412
+ if (typeof match !== "object" || match === null || Array.isArray(match)) {
20413
+ fail(
20414
+ `"match" object is required ({"operations"?: [...], "families"?: [...]})`
20415
+ );
20416
+ }
20417
+ const m = match;
20418
+ for (const k of Object.keys(m)) {
20419
+ if (!CONTENT_MATCH_MATCH_KEYS.includes(k)) {
20420
+ fail(
20421
+ `unknown match key ${JSON.stringify(k)} (expected: ${CONTENT_MATCH_MATCH_KEYS.join(", ")})`
20422
+ );
20423
+ }
20424
+ }
20425
+ const operations = m.operations ?? [];
20426
+ const families = m.families ?? [];
20427
+ if (!Array.isArray(operations) || !Array.isArray(families)) {
20428
+ fail(`"match.operations" and "match.families" must be lists`);
20429
+ }
20430
+ const ops = operations;
20431
+ const fams = families;
20432
+ if (ops.length === 0 && fams.length === 0) {
20433
+ fail(`"match" must name at least one operation or family`);
20434
+ }
20435
+ if (ops.length + fams.length > MAX_CONTENT_MATCH_ENTRIES) {
20436
+ fail(
20437
+ `"match" exceeds ${MAX_CONTENT_MATCH_ENTRIES} combined operations/families entries`
20438
+ );
20439
+ }
20440
+ for (const opId of ops) {
20441
+ if (typeof opId !== "string" || opId.length === 0 || opId.length > MAX_CONTENT_OPERATION_ID_LEN) {
20442
+ fail(
20443
+ `"match.operations" entries must be non-empty strings of at most ${MAX_CONTENT_OPERATION_ID_LEN} characters`
20444
+ );
20445
+ }
20446
+ }
20447
+ for (const fam of fams) {
20448
+ if (typeof fam !== "string" || !OPERATION_FAMILIES.includes(fam)) {
20449
+ fail(
20450
+ `"match.families" entry ${JSON.stringify(fam)} is not a known operation family (expected one of: ${OPERATION_FAMILIES.join(", ")})`
20451
+ );
20452
+ }
20453
+ }
20454
+ const effect = body.effect;
20455
+ if (typeof effect !== "string" || !CONTENT_MATCH_EFFECTS.includes(effect)) {
20456
+ fail(`"effect" must be one of ${CONTENT_MATCH_EFFECTS.join("/")}`);
20457
+ }
20458
+ if (effect === "redact") {
20459
+ const redact = body.redact;
20460
+ if (typeof redact !== "object" || redact === null || Array.isArray(redact)) {
20461
+ fail(`effect=redact requires a "redact" object ({"fields": [...]})`);
20462
+ }
20463
+ const r = redact;
20464
+ for (const k of Object.keys(r)) {
20465
+ if (k !== "fields") {
20466
+ fail(`unknown redact key ${JSON.stringify(k)} (expected: fields)`);
20467
+ }
20468
+ }
20469
+ const fields2 = r.fields;
20470
+ if (!Array.isArray(fields2) || fields2.length === 0 || fields2.length > MAX_CONTENT_REDACT_FIELDS) {
20471
+ fail(`"redact.fields" must name 1-${MAX_CONTENT_REDACT_FIELDS} fields`);
20472
+ }
20473
+ for (const f of fields2) {
20474
+ if (typeof f !== "string" || f.length === 0 || f.length > MAX_CONTENT_REDACT_FIELD_LEN) {
20475
+ fail(
20476
+ `"redact.fields" entries must be non-empty strings of at most ${MAX_CONTENT_REDACT_FIELD_LEN} characters`
20477
+ );
20478
+ }
20479
+ }
20480
+ } else if (body.redact !== void 0) {
20481
+ fail(`"redact" only applies to effect=redact`);
20482
+ }
20483
+ if (effect === "step_up") {
20484
+ const stepUp = body.step_up;
20485
+ if (typeof stepUp !== "object" || stepUp === null || Array.isArray(stepUp)) {
20486
+ fail(
20487
+ `effect=step_up requires a "step_up" object ({"max_session_age_seconds": <seconds>})`
20488
+ );
20489
+ }
20490
+ const s = stepUp;
20491
+ for (const k of Object.keys(s)) {
20492
+ if (k !== "max_session_age_seconds") {
20493
+ fail(
20494
+ `unknown step_up key ${JSON.stringify(k)} (expected: max_session_age_seconds)`
20495
+ );
20496
+ }
20497
+ }
20498
+ const maxAge = s.max_session_age_seconds;
20499
+ if (typeof maxAge !== "number" || !Number.isInteger(maxAge) || maxAge < CONTENT_STEP_UP_MIN_SECONDS || maxAge > CONTENT_STEP_UP_MAX_SECONDS) {
20500
+ fail(
20501
+ `"step_up.max_session_age_seconds" must be an integer between ${CONTENT_STEP_UP_MIN_SECONDS} and ${CONTENT_STEP_UP_MAX_SECONDS}`
20502
+ );
20503
+ }
20504
+ } else if (body.step_up !== void 0) {
20505
+ fail(`"step_up" only applies to effect=step_up`);
20506
+ }
20507
+ const params = body.params ?? [];
20508
+ if (!Array.isArray(params) || params.length > MAX_CONTENT_PARAM_CONDITIONS) {
20509
+ fail(
20510
+ `"params" must be a list of at most ${MAX_CONTENT_PARAM_CONDITIONS} conditions`
20511
+ );
20512
+ }
20513
+ params.forEach((cond, i) => {
20514
+ if (typeof cond !== "object" || cond === null || Array.isArray(cond)) {
20515
+ fail(`params[${i}] must be an object ({"name", "op", "value"})`);
20516
+ }
20517
+ const c = cond;
20518
+ const condKeys = Object.keys(c);
20519
+ if (condKeys.length !== 3 || condKeys.some((k) => k !== "name" && k !== "op" && k !== "value")) {
20520
+ fail(`params[${i}] needs exactly the keys name/op/value`);
20521
+ }
20522
+ const name = c.name;
20523
+ if (typeof name !== "string" || name.length === 0 || name.length > MAX_CONTENT_PARAM_NAME_LEN) {
20524
+ fail(
20525
+ `params[${i}].name must be a non-empty string of at most ${MAX_CONTENT_PARAM_NAME_LEN} characters`
20526
+ );
20527
+ }
20528
+ const op = c.op;
20529
+ if (typeof op !== "string" || !CONTENT_PARAM_OPS.includes(op)) {
20530
+ fail(`params[${i}].op must be one of ${CONTENT_PARAM_OPS.join("/")}`);
20531
+ }
20532
+ const value = c.value;
20533
+ if (CONTENT_NUMERIC_OPS.includes(op)) {
20534
+ if (typeof value !== "number") {
20535
+ fail(`params[${i}].op ${op} requires a numeric value`);
20536
+ }
20537
+ } else if (op === "equals") {
20538
+ if (typeof value !== "string" && typeof value !== "number") {
20539
+ fail(
20540
+ `params[${i}].op equals requires a scalar value (string or number)`
20541
+ );
20542
+ }
20543
+ } else {
20544
+ if (!Array.isArray(value) || value.length === 0 || value.length > MAX_CONTENT_VALUE_ENTRIES || value.some(
20545
+ (v) => typeof v !== "string" || v.length === 0 || v.length > MAX_CONTENT_VALUE_LEN
20546
+ )) {
20547
+ fail(
20548
+ `params[${i}].op ${op} requires a non-empty list of strings (max ${MAX_CONTENT_VALUE_ENTRIES} entries, ${MAX_CONTENT_VALUE_LEN} characters each)`
20549
+ );
20550
+ }
20551
+ }
20552
+ });
20553
+ }
19284
20554
  function validateRuleBodyForTypeOrExit(ruleType, body) {
19285
20555
  if (pythonCompactJsonLength(body) > MAX_RULE_BODY_BYTES) {
19286
20556
  failRuleBody(
@@ -19303,14 +20573,23 @@ function validateRuleBodyForTypeOrExit(ruleType, body) {
19303
20573
  case "restriction":
19304
20574
  validateRestrictionBodyOrExit(body);
19305
20575
  return;
20576
+ case "quota":
20577
+ validateQuotaBodyOrExit(body);
20578
+ return;
20579
+ case "content_match":
20580
+ validateContentMatchBodyOrExit(body);
20581
+ return;
19306
20582
  }
19307
20583
  }
19308
20584
  function detectRuleBodyType(body) {
19309
20585
  if (body.effect === "require_approval" || "approval" in body)
19310
20586
  return "require_approval";
20587
+ if ("match" in body || body.effect === "redact" || body.effect === "step_up" || "redact" in body || "step_up" in body)
20588
+ return "content_match";
19311
20589
  if ("when" in body || "effect" in body) return "json_match";
19312
20590
  if ("allow" in body) return "ip_allowlist";
19313
20591
  if (TIME_WINDOW_KEYS.some((k) => k in body)) return "time_window";
20592
+ if (QUOTA_KEYS.some((k) => k in body)) return "quota";
19314
20593
  if (RESTRICTION_KEYS.some((k) => k in body)) return "restriction";
19315
20594
  return null;
19316
20595
  }
@@ -19396,6 +20675,9 @@ var SIMULATE_TRACE_CHECKS = {
19396
20675
  matched: isBoolean,
19397
20676
  would_deny: isBoolean
19398
20677
  };
20678
+ var MAX_SIMULATE_OPERATION_ID_LEN = 255;
20679
+ var MAX_SIMULATE_PARAMS_ENTRIES = 20;
20680
+ var MAX_SIMULATE_PARAMS_LEN = 8192;
19399
20681
  var SIMULATE_TRACE_COLUMNS = [
19400
20682
  { label: "LEVEL", get: (r) => r.level },
19401
20683
  { label: "TYPE", get: (r) => r.rule_type },
@@ -19495,6 +20777,7 @@ function buildRulesSubcommand() {
19495
20777
  const row = await client.policy.createRule(appId, target, payload);
19496
20778
  emit2(format, row);
19497
20779
  surfaceApproverWarnings(row);
20780
+ surfaceCatalogWarnings(row);
19498
20781
  });
19499
20782
  }
19500
20783
  );
@@ -19584,7 +20867,7 @@ function buildRulesSubcommand() {
19584
20867
  const ruleBody = loadInputJsonBody(options.body, "--body");
19585
20868
  if (detectRuleBodyType(ruleBody) === null) {
19586
20869
  failRuleBody(
19587
- `unrecognized rule body shape (expected json_match {"when", "effect"}, require_approval {"effect": "require_approval", "approval"}, ip_allowlist {"allow"}, time_window {${TIME_WINDOW_KEYS.map((k) => `"${k}"`).join(", ")}}, or restriction {${RESTRICTION_KEYS.map((k) => `"${k}"`).join(" / ")}})`
20870
+ `unrecognized rule body shape (expected json_match {"when", "effect"}, require_approval {"effect": "require_approval", "approval"}, content_match {"match", "effect"}, ip_allowlist {"allow"}, time_window {${TIME_WINDOW_KEYS.map((k) => `"${k}"`).join(", ")}}, quota {${QUOTA_KEYS.map((k) => `"${k}"`).join(", ")}}, or restriction {${RESTRICTION_KEYS.map((k) => `"${k}"`).join(" / ")}})`
19588
20871
  );
19589
20872
  }
19590
20873
  pendingRuleBody = ruleBody;
@@ -19646,6 +20929,7 @@ function buildRulesSubcommand() {
19646
20929
  );
19647
20930
  emit2(format, row);
19648
20931
  surfaceApproverWarnings(row);
20932
+ surfaceCatalogWarnings(row);
19649
20933
  });
19650
20934
  }
19651
20935
  );
@@ -19720,7 +21004,7 @@ function buildPolicyCommand() {
19720
21004
  });
19721
21005
  });
19722
21006
  policy.command("simulate").description(
19723
- "Dry-run the app's policy rules for one request shape (grant / method / endpoint / agent / client-ip / time) and print the verdict + per-rule trace. Read-only, no side effects (requires dashboard_app_policy:read)"
21007
+ "Dry-run the app's policy rules for one request shape (grant / method / endpoint / agent / client-ip / time / operation / params) and print the verdict + per-rule trace. Read-only, no side effects (requires dashboard_app_policy:read)"
19724
21008
  ).option(
19725
21009
  "--app <app-id>",
19726
21010
  "App ID. Falls back to ALTER_APP_ID env or .alter/config.yaml"
@@ -19739,6 +21023,12 @@ function buildPolicyCommand() {
19739
21023
  ).option(
19740
21024
  "--at <iso8601>",
19741
21025
  "Instant for time-window rules (ISO 8601; e.g. 2026-05-21T09:00:00Z)"
21026
+ ).option(
21027
+ "--operation <id>",
21028
+ "Explicit attested-operation id override for content_match rules (e.g. gmail.users.messages.send); omit to classify from --endpoint like the live gate"
21029
+ ).option(
21030
+ "--params <json>",
21031
+ `Simulated projected parameter bag for content_match rules, as a JSON object (e.g. '{"recipients": ["bob@other.io"]}'). Values are used only for the dry-run evaluation and never stored`
19742
21032
  ).option(
19743
21033
  "--output <format>",
19744
21034
  "Output format: json|jsonl|table (default: table)",
@@ -19771,6 +21061,49 @@ function buildPolicyCommand() {
19771
21061
  process.exit(EXIT_USAGE);
19772
21062
  }
19773
21063
  const at = options.at !== void 0 ? parseIsoDateArgument("--at")(options.at) : void 0;
21064
+ let operationId;
21065
+ if (options.operation !== void 0) {
21066
+ operationId = options.operation.trim();
21067
+ if (operationId.length === 0 || operationId.length > MAX_SIMULATE_OPERATION_ID_LEN) {
21068
+ process.stderr.write(
21069
+ `alter: --operation must be a non-empty operation id of at most ${MAX_SIMULATE_OPERATION_ID_LEN} characters
21070
+ `
21071
+ );
21072
+ process.exit(EXIT_USAGE);
21073
+ }
21074
+ }
21075
+ let simulatedParams;
21076
+ if (options.params !== void 0) {
21077
+ let parsed;
21078
+ try {
21079
+ parsed = JSON.parse(options.params);
21080
+ } catch {
21081
+ process.stderr.write("alter: --params must be valid JSON\n");
21082
+ process.exit(EXIT_USAGE);
21083
+ }
21084
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
21085
+ process.stderr.write(
21086
+ `alter: --params must be a JSON object (not an array or scalar), e.g. '{"recipients": ["bob@other.io"]}'
21087
+ `
21088
+ );
21089
+ process.exit(EXIT_USAGE);
21090
+ }
21091
+ simulatedParams = parsed;
21092
+ if (Object.keys(simulatedParams).length > MAX_SIMULATE_PARAMS_ENTRIES) {
21093
+ process.stderr.write(
21094
+ `alter: --params must be a JSON object of at most ${MAX_SIMULATE_PARAMS_ENTRIES} entries
21095
+ `
21096
+ );
21097
+ process.exit(EXIT_USAGE);
21098
+ }
21099
+ if (JSON.stringify(simulatedParams).length > MAX_SIMULATE_PARAMS_LEN) {
21100
+ process.stderr.write(
21101
+ `alter: --params exceeds ${MAX_SIMULATE_PARAMS_LEN} characters when JSON-encoded
21102
+ `
21103
+ );
21104
+ process.exit(EXIT_USAGE);
21105
+ }
21106
+ }
19774
21107
  await withClient(async (client) => {
19775
21108
  const verdict = await client.policy.simulate(appId, {
19776
21109
  grantId: options.grant,
@@ -19778,7 +21111,9 @@ function buildPolicyCommand() {
19778
21111
  endpoint: options.endpoint,
19779
21112
  agentId: options.agent,
19780
21113
  clientIp: options.clientIp?.trim(),
19781
- at
21114
+ at,
21115
+ operationId,
21116
+ simulatedParams
19782
21117
  });
19783
21118
  if (format === "json" || format === "jsonl") {
19784
21119
  emit2(format, verdict);
@@ -19796,6 +21131,21 @@ function buildPolicyCommand() {
19796
21131
  "alter: node liveness NOT simulated \u2014 a revoked/expired ancestor in this delegation chain could still deny the live request\n"
19797
21132
  );
19798
21133
  }
21134
+ const classificationReason = typeof verdict.classification_reason === "string" ? verdict.classification_reason : null;
21135
+ const classifiedOperation = typeof verdict.classified_operation === "string" ? verdict.classified_operation : null;
21136
+ if (classificationReason !== null && classificationReason !== "not_simulated") {
21137
+ if (classifiedOperation !== null) {
21138
+ process.stderr.write(
21139
+ `alter: classified operation: ${classifiedOperation}
21140
+ `
21141
+ );
21142
+ } else {
21143
+ process.stderr.write(
21144
+ `alter: unclassified request (${classificationReason}) \u2014 content rules targeting this provider DENY unclassified traffic (endpoints outside the policy catalog are refused for ruled traffic)
21145
+ `
21146
+ );
21147
+ }
21148
+ }
19799
21149
  const items = extractArrayField(verdict, "rules", "policy.simulate");
19800
21150
  const rows = validateRows(
19801
21151
  items,
@@ -19828,6 +21178,51 @@ var PROVIDER_COLUMNS = [
19828
21178
  { label: "GRANTS", get: (p) => String(p.grants_count) },
19829
21179
  { label: "STATUS", get: (p) => p.status }
19830
21180
  ];
21181
+ var PROVIDER_SPEC_KINDS = ["oauth", "managed"];
21182
+ var SPEC_COLUMNS = [
21183
+ { label: "KIND", get: (s) => s.provider_kind },
21184
+ { label: "PROVIDER", get: (s) => s.provider_id },
21185
+ { label: "VER", get: (s) => String(s.version) },
21186
+ { label: "OPS", get: (s) => String(s.operation_count) },
21187
+ { label: "PROVENANCE", get: (s) => s.provenance, maxWidth: 16 },
21188
+ { label: "SPEC", get: (s) => s.spec_version ?? "\u2014", maxWidth: 16 },
21189
+ { label: "CHANGED", get: (s) => s.changed_at, maxWidth: 24 },
21190
+ { label: "FETCHED", get: (s) => s.fetched_at, maxWidth: 24 }
21191
+ ];
21192
+ var SPEC_OPERATION_COLUMNS = [
21193
+ { label: "OPERATION", get: (o) => o.operation_id, maxWidth: 40 },
21194
+ { label: "METHOD", get: (o) => o.method },
21195
+ { label: "PATH", get: (o) => o.path_template, maxWidth: 48 },
21196
+ { label: "SUMMARY", get: (o) => o.summary ?? "\u2014", maxWidth: 48 }
21197
+ ];
21198
+ function resolveSpecForProvider(items, providerId) {
21199
+ const matches = items.filter((s) => s.provider_id === providerId);
21200
+ if (new Set(matches.map((s) => s.provider_kind)).size > 1) {
21201
+ return { outcome: "ambiguous" };
21202
+ }
21203
+ const spec = matches[0];
21204
+ if (spec === void 0 || spec.provider_kind !== "oauth" && spec.provider_kind !== "managed") {
21205
+ return { outcome: "not_found" };
21206
+ }
21207
+ return { outcome: "resolved", kind: spec.provider_kind, spec };
21208
+ }
21209
+ function unwrapSpecResolutionOrExit(resolution, providerId, kind) {
21210
+ if (resolution.outcome === "ambiguous") {
21211
+ process.stderr.write(
21212
+ `alter: provider "${sanitizeStderrText(providerId)}" has API specs in BOTH the oauth and managed families \u2014 pass --kind oauth or --kind managed to pick one
21213
+ `
21214
+ );
21215
+ process.exit(EXIT_USAGE);
21216
+ }
21217
+ if (resolution.outcome === "not_found") {
21218
+ process.stderr.write(
21219
+ `alter: no ${kind !== void 0 ? `${kind} ` : ""}provider API spec found for "${sanitizeStderrText(providerId)}" \u2014 run \`alter providers spec list\` to see every provider with an active spec
21220
+ `
21221
+ );
21222
+ process.exit(EXIT_NOT_FOUND);
21223
+ }
21224
+ return { kind: resolution.kind, spec: resolution.spec };
21225
+ }
19831
21226
  function resolveClientSecret(raw) {
19832
21227
  return resolveSecretArg(raw, "--client-secret");
19833
21228
  }
@@ -19879,7 +21274,7 @@ function surfaceProviderResponse(row) {
19879
21274
  }
19880
21275
  function buildProvidersCommand() {
19881
21276
  const providers = new Command20("providers").description(
19882
- "Manage OAuth provider configs"
21277
+ "Manage OAuth provider configs and browse provider API specs"
19883
21278
  );
19884
21279
  providers.command("list").description("List OAuth provider configs for an app").option(
19885
21280
  "--app <app-id>",
@@ -20200,6 +21595,170 @@ function buildProvidersCommand() {
20200
21595
  });
20201
21596
  }
20202
21597
  );
21598
+ const spec = new Command20("spec").description(
21599
+ "Inspect provider API spec freshness, provenance, and versions"
21600
+ );
21601
+ spec.command("list").description(
21602
+ "List every provider's active API spec metadata \u2014 version, provenance, operation count, fetch freshness. The spec store's freshness/provenance dashboard, from the CLI."
21603
+ ).option("--kind <kind>", "Filter by provider family: oauth | managed").option(
21604
+ "--output <format>",
21605
+ "Output format: json|jsonl|table (default: table)",
21606
+ "table"
21607
+ ).action(async (options) => {
21608
+ const format = coerceOutputFormat(options.output);
21609
+ const kind = validateChoice("--kind", options.kind, PROVIDER_SPEC_KINDS);
21610
+ await withClient(async (client) => {
21611
+ const res = await client.providers.listSpecs(
21612
+ kind !== void 0 ? { kind } : {}
21613
+ );
21614
+ emit2(format, res.items, SPEC_COLUMNS);
21615
+ });
21616
+ });
21617
+ spec.command("status").description(
21618
+ "Show one provider's active spec metadata \u2014 version, provenance, operation count, fetch freshness"
21619
+ ).argument("<provider>", "Provider ID (e.g. google, github)").option(
21620
+ "--kind <kind>",
21621
+ "Provider family: oauth | managed. Required only when the provider id exists in both families (e.g. github); otherwise auto-resolved from the spec store."
21622
+ ).option(
21623
+ "--output <format>",
21624
+ "Output format: json|jsonl|table (default: json)",
21625
+ "json"
21626
+ ).action(
21627
+ async (provider, options) => {
21628
+ const format = coerceOutputFormat(options.output);
21629
+ const kind = validateChoice(
21630
+ "--kind",
21631
+ options.kind,
21632
+ PROVIDER_SPEC_KINDS
21633
+ );
21634
+ const resolution = await withClient(async (client) => {
21635
+ const res = await client.providers.listSpecs(
21636
+ kind !== void 0 ? { kind } : {}
21637
+ );
21638
+ return resolveSpecForProvider(res.items, provider);
21639
+ });
21640
+ const { spec: row } = unwrapSpecResolutionOrExit(
21641
+ resolution,
21642
+ provider,
21643
+ kind
21644
+ );
21645
+ emit2(format, row);
21646
+ }
21647
+ );
21648
+ providers.addCommand(spec);
21649
+ const operations = new Command20("operations").description(
21650
+ "Discover a provider's API operations from its active spec"
21651
+ );
21652
+ operations.command("list").description(
21653
+ "Page one provider's API operations from its active spec (in-band operation discovery)"
21654
+ ).argument("<provider>", "Provider ID (e.g. google, github)").option(
21655
+ "--kind <kind>",
21656
+ "Provider family: oauth | managed. Required only when the provider id exists in both families (e.g. github); otherwise auto-resolved from the spec store."
21657
+ ).option(
21658
+ "--search <q>",
21659
+ "Server-side filter over operation id, method, path, and summary"
21660
+ ).option(
21661
+ "--limit <n>",
21662
+ "Page size (1-500, default 100)",
21663
+ parseBoundedInt("--limit", 1, 500)
21664
+ ).option("--offset <n>", "Page offset", parseNonNegativeInt("--offset")).option(
21665
+ "--output <format>",
21666
+ "Output format: json|jsonl|table (default: table)",
21667
+ "table"
21668
+ ).action(
21669
+ async (provider, options) => {
21670
+ const format = coerceOutputFormat(options.output);
21671
+ const kindFlag = validateChoice(
21672
+ "--kind",
21673
+ options.kind,
21674
+ PROVIDER_SPEC_KINDS
21675
+ );
21676
+ const outcome = await withClient(async (client) => {
21677
+ let kind = kindFlag;
21678
+ if (kind === void 0) {
21679
+ const res = await client.providers.listSpecs();
21680
+ const resolution = resolveSpecForProvider(res.items, provider);
21681
+ if (resolution.outcome !== "resolved") return resolution;
21682
+ kind = resolution.kind;
21683
+ }
21684
+ const page2 = await client.providers.listSpecOperations(
21685
+ kind,
21686
+ provider,
21687
+ {
21688
+ ...options.search !== void 0 ? { search: options.search } : {},
21689
+ ...options.limit !== void 0 ? { limit: options.limit } : {},
21690
+ ...options.offset !== void 0 ? { offset: options.offset } : {}
21691
+ }
21692
+ );
21693
+ return { outcome: "ok", page: page2 };
21694
+ });
21695
+ if (outcome.outcome !== "ok") {
21696
+ unwrapSpecResolutionOrExit(outcome, provider, kindFlag);
21697
+ return;
21698
+ }
21699
+ const page = outcome.page;
21700
+ const rows = validateRows(
21701
+ page.items,
21702
+ {
21703
+ operation_id: isString,
21704
+ method: isString,
21705
+ path_template: isString,
21706
+ summary: isOptionalString
21707
+ },
21708
+ "providers.list_spec_operations"
21709
+ );
21710
+ emit2(format, rows, SPEC_OPERATION_COLUMNS);
21711
+ if (page.has_more) {
21712
+ process.stderr.write(
21713
+ `alter: showing ${rows.length} of ${page.total} operations \u2014 pass --offset ${page.offset + rows.length} for the next page
21714
+ `
21715
+ );
21716
+ }
21717
+ }
21718
+ );
21719
+ operations.command("get").description(
21720
+ "Show one API operation with its full parameter/request/response schemas"
21721
+ ).argument("<provider>", "Provider ID (e.g. google, github)").argument(
21722
+ "<operation-id>",
21723
+ "Operation ID from `alter providers operations list` (may contain slashes, e.g. repos/get)"
21724
+ ).option(
21725
+ "--kind <kind>",
21726
+ "Provider family: oauth | managed. Required only when the provider id exists in both families (e.g. github); otherwise auto-resolved from the spec store."
21727
+ ).option(
21728
+ "--output <format>",
21729
+ "Output format: json|jsonl|table (default: json)",
21730
+ "json"
21731
+ ).action(
21732
+ async (provider, operationId, options) => {
21733
+ const format = coerceOutputFormat(options.output);
21734
+ const kindFlag = validateChoice(
21735
+ "--kind",
21736
+ options.kind,
21737
+ PROVIDER_SPEC_KINDS
21738
+ );
21739
+ const outcome = await withClient(async (client) => {
21740
+ let kind = kindFlag;
21741
+ if (kind === void 0) {
21742
+ const res = await client.providers.listSpecs();
21743
+ const resolution = resolveSpecForProvider(res.items, provider);
21744
+ if (resolution.outcome !== "resolved") return resolution;
21745
+ kind = resolution.kind;
21746
+ }
21747
+ const row = await client.providers.getSpecOperation(
21748
+ kind,
21749
+ provider,
21750
+ operationId
21751
+ );
21752
+ return { outcome: "ok", row };
21753
+ });
21754
+ if (outcome.outcome !== "ok") {
21755
+ unwrapSpecResolutionOrExit(outcome, provider, kindFlag);
21756
+ return;
21757
+ }
21758
+ emit2(format, outcome.row);
21759
+ }
21760
+ );
21761
+ providers.addCommand(operations);
20203
21762
  return providers;
20204
21763
  }
20205
21764