@alter-ai/cli 0.8.0 → 0.9.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.
- package/dist/cli.js +366 -81
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -33,7 +33,7 @@ import { join } from "path";
|
|
|
33
33
|
// package.json
|
|
34
34
|
var package_default = {
|
|
35
35
|
name: "@alter-ai/cli",
|
|
36
|
-
version: "0.
|
|
36
|
+
version: "0.9.0",
|
|
37
37
|
description: "Command-line interface for the Alter Vault dev portal \u2014 scripted dashboard automation.",
|
|
38
38
|
type: "module",
|
|
39
39
|
bin: {
|
|
@@ -76,7 +76,7 @@ var package_default = {
|
|
|
76
76
|
keytar: "^7.9.0"
|
|
77
77
|
},
|
|
78
78
|
devDependencies: {
|
|
79
|
-
"@alter-ai/alter-sdk": "workspace:0.
|
|
79
|
+
"@alter-ai/alter-sdk": "workspace:0.24.0",
|
|
80
80
|
"@alter-vault/shared-types": "workspace:0.0.1",
|
|
81
81
|
"@alter-vault/shared-utils": "workspace:0.0.1",
|
|
82
82
|
"@eslint/js": "9.39.4",
|
|
@@ -4618,7 +4618,7 @@ var AlterSDKError = class extends Error {
|
|
|
4618
4618
|
constructor(message, details) {
|
|
4619
4619
|
super(message);
|
|
4620
4620
|
this.name = "AlterSDKError";
|
|
4621
|
-
this.details = details
|
|
4621
|
+
this.details = details !== null && typeof details === "object" && !Array.isArray(details) ? details : {};
|
|
4622
4622
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
4623
4623
|
}
|
|
4624
4624
|
toString() {
|
|
@@ -4836,9 +4836,9 @@ var InsufficientScopeError = class _InsufficientScopeError extends BackendError
|
|
|
4836
4836
|
constructor(message = "Insufficient scope", options = {}) {
|
|
4837
4837
|
super(message, options.details);
|
|
4838
4838
|
this.name = "InsufficientScopeError";
|
|
4839
|
-
this.required = options.required
|
|
4840
|
-
this.granted = options.granted
|
|
4841
|
-
this.missing = options.missing
|
|
4839
|
+
this.required = options.required ? [...options.required] : [];
|
|
4840
|
+
this.granted = options.granted ? [...options.granted] : [];
|
|
4841
|
+
this.missing = options.missing ? [...options.missing] : [];
|
|
4842
4842
|
this.scopeVersion = options.scopeVersion ?? null;
|
|
4843
4843
|
this.currentScopeVersion = options.currentScopeVersion ?? null;
|
|
4844
4844
|
this.scopeVersionMismatch = options.scopeVersionMismatch ?? false;
|
|
@@ -4871,7 +4871,9 @@ var InsufficientScopeError = class _InsufficientScopeError extends BackendError
|
|
|
4871
4871
|
*/
|
|
4872
4872
|
static fromErrorBody(errorData) {
|
|
4873
4873
|
return new _InsufficientScopeError(
|
|
4874
|
-
|
|
4874
|
+
// Non-empty string only (parity with `_messageOr` / Python
|
|
4875
|
+
// `from_error_body`): an empty wire message is as uninformative as none.
|
|
4876
|
+
typeof errorData.message === "string" && errorData.message ? errorData.message : "Insufficient scope",
|
|
4875
4877
|
{
|
|
4876
4878
|
// Element-level filtering: a non-array coerces to [], and non-string
|
|
4877
4879
|
// elements inside an array are dropped (a bare cast would smuggle
|
|
@@ -5056,7 +5058,7 @@ var AgentError = class extends BackendError {
|
|
|
5056
5058
|
hint;
|
|
5057
5059
|
constructor(opts = {}) {
|
|
5058
5060
|
const subclassCode = new.target.code;
|
|
5059
|
-
super(opts.message
|
|
5061
|
+
super(opts.message || subclassCode, opts.details);
|
|
5060
5062
|
this.name = "AgentError";
|
|
5061
5063
|
this.code = subclassCode;
|
|
5062
5064
|
this.hint = opts.hint;
|
|
@@ -6796,12 +6798,13 @@ var ApprovalResult = class {
|
|
|
6796
6798
|
headers: this.headers,
|
|
6797
6799
|
body_b64: this.bodyB64,
|
|
6798
6800
|
body_truncated: this.bodyTruncated,
|
|
6799
|
-
//
|
|
6800
|
-
//
|
|
6801
|
-
//
|
|
6802
|
-
//
|
|
6803
|
-
//
|
|
6804
|
-
//
|
|
6801
|
+
// Include duration_ms so JSON round-trips carry the timing metadata.
|
|
6802
|
+
// The Python twin's ApprovalResult is a plain Pydantic model whose
|
|
6803
|
+
// model_dump() includes EVERY public field — duration_ms and
|
|
6804
|
+
// credential_hint alike — so omitting either here would make a TS
|
|
6805
|
+
// JSON round-trip lossy where the Python one is not (wire-shape
|
|
6806
|
+
// parity: toJSON() must serialize the same field set as model_dump()).
|
|
6807
|
+
duration_ms: this.durationMs,
|
|
6805
6808
|
credential_hint: this.credentialHint
|
|
6806
6809
|
};
|
|
6807
6810
|
}
|
|
@@ -8112,7 +8115,7 @@ async function _raiseForStatus(response) {
|
|
|
8112
8115
|
const rawCode = body.error;
|
|
8113
8116
|
const code = typeof rawCode === "string" ? rawCode : "";
|
|
8114
8117
|
const rawMessage = body.message;
|
|
8115
|
-
const message = typeof rawMessage === "string" ? rawMessage : `Backend error ${response.status}`;
|
|
8118
|
+
const message = typeof rawMessage === "string" && rawMessage ? rawMessage : `Backend error ${response.status}`;
|
|
8116
8119
|
const rawHint = body.hint;
|
|
8117
8120
|
const hint = typeof rawHint === "string" ? rawHint : void 0;
|
|
8118
8121
|
if (code === "insufficient_scope") {
|
|
@@ -8120,7 +8123,7 @@ async function _raiseForStatus(response) {
|
|
|
8120
8123
|
}
|
|
8121
8124
|
if (code === "rate_limit_exceeded") {
|
|
8122
8125
|
throw new RateLimitError(
|
|
8123
|
-
typeof body.message === "string" ? body.message : "Rate limit exceeded \u2014 retry after the window resets.",
|
|
8126
|
+
typeof body.message === "string" && body.message ? body.message : "Rate limit exceeded \u2014 retry after the window resets.",
|
|
8124
8127
|
_parseRetryAfter(response.headers.get("Retry-After"), body.retry_after),
|
|
8125
8128
|
body,
|
|
8126
8129
|
typeof body.scope === "string" ? body.scope : void 0,
|
|
@@ -8318,9 +8321,15 @@ var AgentsNamespace = class {
|
|
|
8318
8321
|
const limit = options.limit ?? 100;
|
|
8319
8322
|
const offset = options.offset ?? 0;
|
|
8320
8323
|
const includeRevoked = options.includeRevoked ?? false;
|
|
8324
|
+
if (!Number.isInteger(limit)) {
|
|
8325
|
+
throw new AlterValueError("limit must be an integer");
|
|
8326
|
+
}
|
|
8321
8327
|
if (limit < 1 || limit > LIST_LIMIT_MAX) {
|
|
8322
8328
|
throw new AlterValueError("limit must be between 1 and 1000");
|
|
8323
8329
|
}
|
|
8330
|
+
if (!Number.isInteger(offset)) {
|
|
8331
|
+
throw new AlterValueError("offset must be an integer");
|
|
8332
|
+
}
|
|
8324
8333
|
if (offset < 0) {
|
|
8325
8334
|
throw new AlterValueError("offset must be >= 0");
|
|
8326
8335
|
}
|
|
@@ -8766,7 +8775,7 @@ function _optionalStringArray(raw, key, context) {
|
|
|
8766
8775
|
}
|
|
8767
8776
|
function _normalizeKeyId(value, operation) {
|
|
8768
8777
|
if (typeof value === "string") {
|
|
8769
|
-
if (value.length === 0) {
|
|
8778
|
+
if (value.trim().length === 0) {
|
|
8770
8779
|
throw new AlterValueError(`${operation}() requires a non-empty keyId`);
|
|
8771
8780
|
}
|
|
8772
8781
|
return value;
|
|
@@ -9468,6 +9477,26 @@ var SpansNamespace = class {
|
|
|
9468
9477
|
`spans[${index}] must be a UserSpan with traceId, name, and startTime`
|
|
9469
9478
|
);
|
|
9470
9479
|
}
|
|
9480
|
+
if (span.traceId.length < 1 || span.traceId.length > 255) {
|
|
9481
|
+
throw new AlterValueError(
|
|
9482
|
+
`spans[${index}].traceId must be 1\u2013255 characters`
|
|
9483
|
+
);
|
|
9484
|
+
}
|
|
9485
|
+
if (span.name.length < 1 || span.name.length > 255) {
|
|
9486
|
+
throw new AlterValueError(
|
|
9487
|
+
`spans[${index}].name must be 1\u2013255 characters`
|
|
9488
|
+
);
|
|
9489
|
+
}
|
|
9490
|
+
if (span.spanId !== void 0 && (typeof span.spanId !== "string" || span.spanId.length < 1 || span.spanId.length > 32)) {
|
|
9491
|
+
throw new AlterValueError(
|
|
9492
|
+
`spans[${index}].spanId must be 1\u201332 characters when provided`
|
|
9493
|
+
);
|
|
9494
|
+
}
|
|
9495
|
+
if (span.parentSpanId !== void 0 && (typeof span.parentSpanId !== "string" || span.parentSpanId.length < 1 || span.parentSpanId.length > 32)) {
|
|
9496
|
+
throw new AlterValueError(
|
|
9497
|
+
`spans[${index}].parentSpanId must be 1\u201332 characters when provided`
|
|
9498
|
+
);
|
|
9499
|
+
}
|
|
9471
9500
|
});
|
|
9472
9501
|
const body = {
|
|
9473
9502
|
spans: spans.map((span) => {
|
|
@@ -9794,8 +9823,14 @@ var FORBIDDEN_INJECTION_HEADERS2 = /* @__PURE__ */ new Set([
|
|
|
9794
9823
|
function _coerceInt(value) {
|
|
9795
9824
|
return Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
9796
9825
|
}
|
|
9826
|
+
var IDENTITY_SYNC_POLL_STATUS = {
|
|
9827
|
+
service_unavailable: 503,
|
|
9828
|
+
identity_provider_inactive: 409,
|
|
9829
|
+
identity_group_name_ambiguous: 409
|
|
9830
|
+
};
|
|
9797
9831
|
function _messageOr(errorData, fallback) {
|
|
9798
|
-
|
|
9832
|
+
const raw = errorData.message;
|
|
9833
|
+
return typeof raw === "string" && raw ? raw : fallback;
|
|
9799
9834
|
}
|
|
9800
9835
|
function _errorPayload(errorData) {
|
|
9801
9836
|
const nested = errorData.details;
|
|
@@ -9918,7 +9953,8 @@ function validateTokenInjectionMetadata(tokenData) {
|
|
|
9918
9953
|
let additionalCredentials = null;
|
|
9919
9954
|
if (additionalCredentialsRaw != null) {
|
|
9920
9955
|
if (typeof additionalCredentialsRaw !== "object" || Array.isArray(additionalCredentialsRaw) || Object.keys(additionalCredentialsRaw).length > MAX_ADDITIONAL_CREDENTIAL_KEYS || !Object.entries(additionalCredentialsRaw).every(
|
|
9921
|
-
([key, value]) => key.length <= MAX_ADDITIONAL_CREDENTIAL_KEY_LENGTH && typeof value === "string" &&
|
|
9956
|
+
([key, value]) => key.length <= MAX_ADDITIONAL_CREDENTIAL_KEY_LENGTH && typeof value === "string" && // pythonStrip: match the backend's Python str.strip() (see token_type).
|
|
9957
|
+
pythonStrip(value).length > 0 && value.length <= MAX_ADDITIONAL_CREDENTIAL_VALUE_LENGTH && // eslint-disable-next-line no-control-regex
|
|
9922
9958
|
!/[\r\n\x00]/.test(value)
|
|
9923
9959
|
)) {
|
|
9924
9960
|
throw new BackendError("Backend returned invalid additional_credentials");
|
|
@@ -10016,7 +10052,23 @@ function isValidOffsetDateTime(value) {
|
|
|
10016
10052
|
const hour = Number(hourText);
|
|
10017
10053
|
const minute = Number(minuteText);
|
|
10018
10054
|
const second = Number(secondText);
|
|
10019
|
-
|
|
10055
|
+
const isLeapYear = year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
|
|
10056
|
+
const daysInMonth = [
|
|
10057
|
+
31,
|
|
10058
|
+
isLeapYear ? 29 : 28,
|
|
10059
|
+
31,
|
|
10060
|
+
30,
|
|
10061
|
+
31,
|
|
10062
|
+
30,
|
|
10063
|
+
31,
|
|
10064
|
+
31,
|
|
10065
|
+
30,
|
|
10066
|
+
31,
|
|
10067
|
+
30,
|
|
10068
|
+
31
|
|
10069
|
+
];
|
|
10070
|
+
const maxDay = daysInMonth[month - 1] ?? 0;
|
|
10071
|
+
if (year < 1 || month < 1 || month > 12 || day < 1 || day > maxDay || hour > 23 || minute > 59 || second > 59 || offsetHour > 23 || offsetMinute > 59) {
|
|
10020
10072
|
return false;
|
|
10021
10073
|
}
|
|
10022
10074
|
return !Number.isNaN(Date.parse(value));
|
|
@@ -10036,7 +10088,10 @@ function validateTokenResponseContract(tokenData) {
|
|
|
10036
10088
|
throw new BackendError("Backend returned an invalid access_token");
|
|
10037
10089
|
}
|
|
10038
10090
|
const tokenType = tokenData.token_type === void 0 ? "Bearer" : tokenData.token_type;
|
|
10039
|
-
if (typeof tokenType !== "string" ||
|
|
10091
|
+
if (typeof tokenType !== "string" || // pythonStrip (not .trim()): the backend normalizes with Python str.strip(),
|
|
10092
|
+
// whose whitespace set differs from JS trim() at the edges (NEL/BOM/C0
|
|
10093
|
+
// separators). Match the backend so the two SDKs accept/reject identically.
|
|
10094
|
+
pythonStrip(tokenType).length === 0 || tokenType !== pythonStrip(tokenType) || tokenType.length > 255 || // eslint-disable-next-line no-control-regex
|
|
10040
10095
|
/[\x00-\x1f\x7f]/.test(tokenType)) {
|
|
10041
10096
|
throw new BackendError("Backend returned an invalid token_type");
|
|
10042
10097
|
}
|
|
@@ -10050,7 +10105,8 @@ function validateTokenResponseContract(tokenData) {
|
|
|
10050
10105
|
}
|
|
10051
10106
|
const scopes = tokenData.scopes === void 0 ? [] : tokenData.scopes;
|
|
10052
10107
|
if (!Array.isArray(scopes) || scopes.some(
|
|
10053
|
-
(scope) => typeof scope !== "string" ||
|
|
10108
|
+
(scope) => typeof scope !== "string" || // pythonStrip: match the backend's Python str.strip() (see token_type).
|
|
10109
|
+
pythonStrip(scope).length === 0 || scope !== pythonStrip(scope) || // eslint-disable-next-line no-control-regex
|
|
10054
10110
|
/[\x00-\x1f\x7f]/.test(scope)
|
|
10055
10111
|
)) {
|
|
10056
10112
|
throw new BackendError("Backend returned invalid scopes");
|
|
@@ -10060,7 +10116,8 @@ function validateTokenResponseContract(tokenData) {
|
|
|
10060
10116
|
throw new BackendError("Backend returned an invalid grant_id");
|
|
10061
10117
|
}
|
|
10062
10118
|
const providerId = tokenData.provider_id === void 0 ? "" : tokenData.provider_id;
|
|
10063
|
-
if (typeof providerId !== "string" ||
|
|
10119
|
+
if (typeof providerId !== "string" || // pythonStrip: match the backend's Python str.strip() (see token_type).
|
|
10120
|
+
providerId !== pythonStrip(providerId) || providerId.length > 255 || // eslint-disable-next-line no-control-regex
|
|
10064
10121
|
/[\x00-\x1f\x7f]/.test(providerId)) {
|
|
10065
10122
|
throw new BackendError("Backend returned an invalid provider_id");
|
|
10066
10123
|
}
|
|
@@ -10089,7 +10146,10 @@ function isRetryErrorInfoPayload(x) {
|
|
|
10089
10146
|
return false;
|
|
10090
10147
|
}
|
|
10091
10148
|
const e = x;
|
|
10092
|
-
return typeof e.attempt === "number" &&
|
|
10149
|
+
return typeof e.attempt === "number" && // Integer-only (not merely finite) — mirrors the backend Pydantic `int`
|
|
10150
|
+
// schema and the Python SDK's strict `RetryErrorInfo.attempt`, so a
|
|
10151
|
+
// fractional attempt (e.g. 1.5) is dropped identically in both SDKs.
|
|
10152
|
+
Number.isInteger(e.attempt) && e.attempt >= 1 && typeof e.error === "string" && e.error.length <= 500 && typeof e.error_type === "string" && typeof e.delay_s === "number" && Number.isFinite(e.delay_s) && e.delay_s >= 0 && (e.permanent === void 0 || typeof e.permanent === "boolean");
|
|
10093
10153
|
}
|
|
10094
10154
|
var _tokenStore = /* @__PURE__ */ new WeakMap();
|
|
10095
10155
|
var _additionalCredsStore = /* @__PURE__ */ new WeakMap();
|
|
@@ -10112,7 +10172,7 @@ function _extractAdditionalCredentials(token) {
|
|
|
10112
10172
|
return _additionalCredsStore.get(token);
|
|
10113
10173
|
}
|
|
10114
10174
|
var _fetch;
|
|
10115
|
-
var SDK_VERSION = "0.
|
|
10175
|
+
var SDK_VERSION = "0.24.0";
|
|
10116
10176
|
var SDK_USER_AGENT = `alter-sdk-node/${SDK_VERSION}`;
|
|
10117
10177
|
function pyUnquote(s) {
|
|
10118
10178
|
if (!s.includes("%")) return s;
|
|
@@ -10381,7 +10441,14 @@ var PROXY_FORBIDDEN_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
|
10381
10441
|
"x-goog-authorization",
|
|
10382
10442
|
"x-goog-iam-authorization-token",
|
|
10383
10443
|
"x-stripe-account",
|
|
10384
|
-
"x-shopify-access-token"
|
|
10444
|
+
"x-shopify-access-token",
|
|
10445
|
+
// Method tunnelling: OData V2 (SAP S/4HANA) and several enterprise APIs
|
|
10446
|
+
// honour these as an override of the real HTTP verb. The policy gate
|
|
10447
|
+
// classifies on the wire method, so a tunnelled verb would split the
|
|
10448
|
+
// operation the gate names from the one the provider executes.
|
|
10449
|
+
"x-http-method",
|
|
10450
|
+
"x-http-method-override",
|
|
10451
|
+
"x-method-override"
|
|
10385
10452
|
]);
|
|
10386
10453
|
var MAX_CONTEXT_LENGTH = 4096;
|
|
10387
10454
|
var MAX_CONTEXT_KEYS = 20;
|
|
@@ -10418,9 +10485,10 @@ function validateAndSerializeContext(context) {
|
|
|
10418
10485
|
`context keys must be strings, got ${typeof k}`
|
|
10419
10486
|
);
|
|
10420
10487
|
}
|
|
10421
|
-
|
|
10488
|
+
const keyLength = [...k].length;
|
|
10489
|
+
if (keyLength > MAX_CONTEXT_KEY_LENGTH) {
|
|
10422
10490
|
throw new AlterValueError(
|
|
10423
|
-
`context key "${k.slice(0, 32)}" exceeds max length (${
|
|
10491
|
+
`context key "${k.slice(0, 32)}" exceeds max length (${keyLength} > ${MAX_CONTEXT_KEY_LENGTH})`
|
|
10424
10492
|
);
|
|
10425
10493
|
}
|
|
10426
10494
|
const v = context[k];
|
|
@@ -10429,9 +10497,10 @@ function validateAndSerializeContext(context) {
|
|
|
10429
10497
|
`context value for key "${k}" must be a string, got ${typeof v}`
|
|
10430
10498
|
);
|
|
10431
10499
|
}
|
|
10432
|
-
|
|
10500
|
+
const valueLength = [...v].length;
|
|
10501
|
+
if (valueLength > MAX_CONTEXT_VALUE_LENGTH) {
|
|
10433
10502
|
throw new AlterValueError(
|
|
10434
|
-
`context value for key "${k}" exceeds max length (${
|
|
10503
|
+
`context value for key "${k}" exceeds max length (${valueLength} > ${MAX_CONTEXT_VALUE_LENGTH})`
|
|
10435
10504
|
);
|
|
10436
10505
|
}
|
|
10437
10506
|
}
|
|
@@ -10531,11 +10600,28 @@ function connectMetadataToWire(metadata) {
|
|
|
10531
10600
|
}
|
|
10532
10601
|
return wire;
|
|
10533
10602
|
}
|
|
10534
|
-
var
|
|
10535
|
-
"expiresAt",
|
|
10536
|
-
"maxTtlSeconds",
|
|
10537
|
-
"defaultTtlSeconds"
|
|
10538
|
-
]
|
|
10603
|
+
var GRANT_POLICY_CAMEL_TO_WIRE = [
|
|
10604
|
+
["expiresAt", "expires_at"],
|
|
10605
|
+
["maxTtlSeconds", "max_ttl_seconds"],
|
|
10606
|
+
["defaultTtlSeconds", "default_ttl_seconds"]
|
|
10607
|
+
];
|
|
10608
|
+
var GRANT_POLICY_INPUT_KEYS = new Set(
|
|
10609
|
+
GRANT_POLICY_CAMEL_TO_WIRE.map(([camelKey]) => camelKey)
|
|
10610
|
+
);
|
|
10611
|
+
function assertParseableExpiresAt(value) {
|
|
10612
|
+
if (typeof value !== "string" || value.length === 0 || Number.isNaN(Date.parse(value))) {
|
|
10613
|
+
throw new AlterValueError(
|
|
10614
|
+
"grantPolicy.expiresAt must be a non-empty ISO 8601 string when provided"
|
|
10615
|
+
);
|
|
10616
|
+
}
|
|
10617
|
+
}
|
|
10618
|
+
function assertPositiveIntegerSeconds(camelKey, value) {
|
|
10619
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
10620
|
+
throw new AlterValueError(
|
|
10621
|
+
`grantPolicy.${camelKey} must be a positive integer (seconds) when provided`
|
|
10622
|
+
);
|
|
10623
|
+
}
|
|
10624
|
+
}
|
|
10539
10625
|
function grantPolicyInputToWire(policy) {
|
|
10540
10626
|
if (policy === null || typeof policy !== "object" || Array.isArray(policy)) {
|
|
10541
10627
|
throw new AlterValueError(
|
|
@@ -10545,32 +10631,57 @@ function grantPolicyInputToWire(policy) {
|
|
|
10545
10631
|
for (const key of Object.keys(policy)) {
|
|
10546
10632
|
if (!GRANT_POLICY_INPUT_KEYS.has(key)) {
|
|
10547
10633
|
throw new AlterValueError(
|
|
10548
|
-
`grantPolicy contains unknown key "${key}" (allowed:
|
|
10634
|
+
`grantPolicy contains unknown key "${key}" (allowed: ${[...GRANT_POLICY_INPUT_KEYS].join(", ")}). Note: keys are camelCase in the TypeScript SDK \u2014 snake_case keys like max_ttl_seconds are the Python SDK surface.`
|
|
10549
10635
|
);
|
|
10550
10636
|
}
|
|
10551
10637
|
}
|
|
10552
10638
|
const wire = {};
|
|
10553
|
-
|
|
10554
|
-
|
|
10639
|
+
for (const [camelKey, wireKey] of GRANT_POLICY_CAMEL_TO_WIRE) {
|
|
10640
|
+
const value = policy[camelKey];
|
|
10641
|
+
if (value === void 0 || value === null) {
|
|
10642
|
+
continue;
|
|
10643
|
+
}
|
|
10644
|
+
if (camelKey === "expiresAt") {
|
|
10645
|
+
assertParseableExpiresAt(value);
|
|
10646
|
+
} else {
|
|
10647
|
+
assertPositiveIntegerSeconds(camelKey, value);
|
|
10648
|
+
}
|
|
10649
|
+
wire[wireKey] = value;
|
|
10650
|
+
}
|
|
10651
|
+
return wire;
|
|
10652
|
+
}
|
|
10653
|
+
function _mintGrantPolicyToWire(policy) {
|
|
10654
|
+
for (const spelling of ["defaultTtlSeconds", "default_ttl_seconds"]) {
|
|
10655
|
+
if (Object.prototype.hasOwnProperty.call(policy, spelling)) {
|
|
10555
10656
|
throw new AlterValueError(
|
|
10556
|
-
|
|
10657
|
+
`grantPolicy.${spelling} is not supported on mint \u2014 a default TTL is accepted only by Connect sessions (createConnectSession / connect) and createManagedSecretGrant`
|
|
10557
10658
|
);
|
|
10558
10659
|
}
|
|
10559
|
-
wire.expires_at = policy.expiresAt;
|
|
10560
10660
|
}
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10661
|
+
const wire = {};
|
|
10662
|
+
for (const [key, value] of Object.entries(policy)) {
|
|
10663
|
+
if (!GRANT_POLICY_INPUT_KEYS.has(key)) {
|
|
10664
|
+
wire[key] = value;
|
|
10665
|
+
}
|
|
10666
|
+
}
|
|
10667
|
+
for (const [camelKey, wireKey] of GRANT_POLICY_CAMEL_TO_WIRE) {
|
|
10565
10668
|
const value = policy[camelKey];
|
|
10566
|
-
if (value === void 0
|
|
10669
|
+
if (value === void 0) {
|
|
10567
10670
|
continue;
|
|
10568
10671
|
}
|
|
10569
|
-
if (
|
|
10672
|
+
if (Object.prototype.hasOwnProperty.call(policy, wireKey)) {
|
|
10570
10673
|
throw new AlterValueError(
|
|
10571
|
-
`grantPolicy
|
|
10674
|
+
`grantPolicy sets both "${camelKey}" and "${wireKey}" \u2014 supply exactly one spelling of the field`
|
|
10572
10675
|
);
|
|
10573
10676
|
}
|
|
10677
|
+
if (value === null) {
|
|
10678
|
+
continue;
|
|
10679
|
+
}
|
|
10680
|
+
if (camelKey === "expiresAt") {
|
|
10681
|
+
assertParseableExpiresAt(value);
|
|
10682
|
+
} else {
|
|
10683
|
+
assertPositiveIntegerSeconds(camelKey, value);
|
|
10684
|
+
}
|
|
10574
10685
|
wire[wireKey] = value;
|
|
10575
10686
|
}
|
|
10576
10687
|
return wire;
|
|
@@ -11428,8 +11539,13 @@ ${label}:${value}`;
|
|
|
11428
11539
|
};
|
|
11429
11540
|
throw new ScopeReauthRequiredError(
|
|
11430
11541
|
_messageOr(errorData, "Grant is missing required scopes"),
|
|
11431
|
-
|
|
11432
|
-
|
|
11542
|
+
// `?? undefined` alone only replaces null — a NON-string grant_id /
|
|
11543
|
+
// provider_id (number, object, bool) would be cast `as string` and
|
|
11544
|
+
// flow through as the wrong runtime value. typeof-guard instead, to
|
|
11545
|
+
// match every sibling branch in this method AND the Python SDK's
|
|
11546
|
+
// `_handle_forbidden` scope_mismatch branch (isinstance → None).
|
|
11547
|
+
typeof details.grant_id === "string" ? details.grant_id : void 0,
|
|
11548
|
+
typeof details.provider_id === "string" ? details.provider_id : void 0,
|
|
11433
11549
|
response.status,
|
|
11434
11550
|
// No provider response body on this path — the backend refused at
|
|
11435
11551
|
// /sdk/token before any provider call, so responseBody is left unset
|
|
@@ -11471,6 +11587,15 @@ ${label}:${value}`;
|
|
|
11471
11587
|
}
|
|
11472
11588
|
if (response.status === HTTP_CONFLICT) {
|
|
11473
11589
|
const errorData = await __VaultClient.#safeParseJson(response);
|
|
11590
|
+
if (errorData.error === "managed_oauth_scope_approval_required") {
|
|
11591
|
+
throw new ConnectConfigError(
|
|
11592
|
+
_messageOr(
|
|
11593
|
+
errorData,
|
|
11594
|
+
"The requested permissions require managed OAuth approval."
|
|
11595
|
+
),
|
|
11596
|
+
errorData
|
|
11597
|
+
);
|
|
11598
|
+
}
|
|
11474
11599
|
if (errorData.error === "token_refresh_in_progress") {
|
|
11475
11600
|
throw new TokenRefreshInProgressError(
|
|
11476
11601
|
_messageOr(
|
|
@@ -13350,6 +13475,16 @@ ${label}:${value}`;
|
|
|
13350
13475
|
* entry. Thrown before any wire traffic (parity with the Python SDK).
|
|
13351
13476
|
* @throws AlterSDKError if the SDK instance is closed
|
|
13352
13477
|
* @throws NetworkError if the connection to the backend fails
|
|
13478
|
+
* @throws ConnectConfigError if the session's requested scopes exceed what
|
|
13479
|
+
* the Alter-managed OAuth client is approved for (HTTP 409
|
|
13480
|
+
* `managed_oauth_scope_approval_required`). NOT a `BackendError` — a
|
|
13481
|
+
* caller that branches only on `BackendError` leaves this uncaught.
|
|
13482
|
+
* `details` carries the whole 409 body: `details.details.provider_id` and
|
|
13483
|
+
* `details.details.unapproved_scopes` name what needs approving,
|
|
13484
|
+
* `details.remediation.action` / `.description` carry the typed remedy,
|
|
13485
|
+
* and `details.retryable` is `false`. Retrying is futile: contact Alter to
|
|
13486
|
+
* approve the scopes, or narrow the app's configured scopes to the
|
|
13487
|
+
* approved set, and only then create a new Connect session.
|
|
13353
13488
|
* @throws BackendError if the backend rejects the request (e.g.
|
|
13354
13489
|
* `scope_not_allowed` when a requested scope is not in the app's Dev
|
|
13355
13490
|
* Portal allowlist)
|
|
@@ -13682,6 +13817,10 @@ ${label}:${value}`;
|
|
|
13682
13817
|
* authorized grant is revoked because the user's usage limits could not be
|
|
13683
13818
|
* applied. In the last case, `details.failed_grants` contains the typed
|
|
13684
13819
|
* failure data in snake_case wire form.
|
|
13820
|
+
* @throws BackendError if identity synchronization could not be completed —
|
|
13821
|
+
* `statusCode` 503 (transient, with `details.retry_after_seconds`) or 409
|
|
13822
|
+
* (an operator must repair the provider; no retry hint). Not a
|
|
13823
|
+
* `ConnectFlowError`: the flow itself was fine.
|
|
13685
13824
|
* @throws AlterSDKError if SDK is closed or session creation fails
|
|
13686
13825
|
*/
|
|
13687
13826
|
async connect(options) {
|
|
@@ -13731,7 +13870,9 @@ ${label}:${value}`;
|
|
|
13731
13870
|
* @throws ConnectTimeoutError if the local `timeoutMs` elapses, or a
|
|
13732
13871
|
* session observed as pending expires before Alter receives a
|
|
13733
13872
|
* completion callback. `details.reason` distinguishes
|
|
13734
|
-
* `poll_deadline_elapsed`
|
|
13873
|
+
* `poll_deadline_elapsed`, `session_expired`, and `rate_limited` (the
|
|
13874
|
+
* whole budget was spent backing off a throttled poll —
|
|
13875
|
+
* `details.retryAfter` carries the last hint the server gave).
|
|
13735
13876
|
* @throws ConnectFlowError / ConnectDeniedError / ConnectConfigError
|
|
13736
13877
|
* for user denial, a first poll that finds an unavailable/expired
|
|
13737
13878
|
* session, unrecognized status, or a completed session whose every grant
|
|
@@ -13739,6 +13880,11 @@ ${label}:${value}`;
|
|
|
13739
13880
|
* total-failure error carries
|
|
13740
13881
|
* `details.failed_grants`; partial failures are surfaced on every
|
|
13741
13882
|
* returned result as `failedGrants`.
|
|
13883
|
+
* @throws BackendError if identity synchronization could not be completed.
|
|
13884
|
+
* `statusCode` says whether to come back: 503 is transient contention
|
|
13885
|
+
* and `details.retry_after_seconds` carries the backoff hint, while 409
|
|
13886
|
+
* is an operator-repairable provider fault (disabled provider, duplicate
|
|
13887
|
+
* group name) that no retry will clear and which carries no hint.
|
|
13742
13888
|
* @throws AlterSDKError if the SDK instance has been closed.
|
|
13743
13889
|
*/
|
|
13744
13890
|
async pollConnectSession(sessionToken, options) {
|
|
@@ -13750,7 +13896,34 @@ ${label}:${value}`;
|
|
|
13750
13896
|
let sawPending = false;
|
|
13751
13897
|
while (true) {
|
|
13752
13898
|
this.#assertNotClosed();
|
|
13753
|
-
|
|
13899
|
+
let pollResult;
|
|
13900
|
+
try {
|
|
13901
|
+
pollResult = await this.#pollSession(sessionToken);
|
|
13902
|
+
} catch (e) {
|
|
13903
|
+
if (!(e instanceof RateLimitError)) {
|
|
13904
|
+
throw e;
|
|
13905
|
+
}
|
|
13906
|
+
const remaining2 = deadline - performance.now();
|
|
13907
|
+
if (remaining2 <= 0) {
|
|
13908
|
+
throw new ConnectTimeoutError(
|
|
13909
|
+
`OAuth flow did not complete within ${Math.round(timeoutMs / 1e3)} seconds: polling was rate-limited for the remainder of the budget. The authorization may still have succeeded \u2014 retry the poll with a fresh timeout, or raise the API key's per-minute rate limit if Connect flows run concurrently.`,
|
|
13910
|
+
{
|
|
13911
|
+
timeoutMs,
|
|
13912
|
+
reason: "rate_limited",
|
|
13913
|
+
retryAfter: e.retryAfter
|
|
13914
|
+
}
|
|
13915
|
+
);
|
|
13916
|
+
}
|
|
13917
|
+
let backoffMs = pollIntervalMs;
|
|
13918
|
+
if (typeof e.retryAfter === "number" && e.retryAfter > 0) {
|
|
13919
|
+
backoffMs = Math.max(e.retryAfter * 1e3, pollIntervalMs);
|
|
13920
|
+
}
|
|
13921
|
+
await new Promise(
|
|
13922
|
+
(resolve5) => setTimeout(resolve5, Math.min(backoffMs, remaining2))
|
|
13923
|
+
);
|
|
13924
|
+
this.#assertNotClosed();
|
|
13925
|
+
continue;
|
|
13926
|
+
}
|
|
13754
13927
|
const pollStatus = pollResult.status;
|
|
13755
13928
|
if (pollStatus === "completed") {
|
|
13756
13929
|
const rawGrants = pollResult.grants;
|
|
@@ -13848,7 +14021,15 @@ ${label}:${value}`;
|
|
|
13848
14021
|
}
|
|
13849
14022
|
);
|
|
13850
14023
|
}
|
|
13851
|
-
const errorDetails = {
|
|
14024
|
+
const errorDetails = {
|
|
14025
|
+
error_code: errorCode
|
|
14026
|
+
};
|
|
14027
|
+
const retryAfter = _coerceInt(
|
|
14028
|
+
err2.retry_after_seconds
|
|
14029
|
+
);
|
|
14030
|
+
if (retryAfter !== void 0) {
|
|
14031
|
+
errorDetails.retry_after_seconds = retryAfter;
|
|
14032
|
+
}
|
|
13852
14033
|
if (errorCode === "connect_denied") {
|
|
13853
14034
|
throw new ConnectDeniedError(errorMessage, errorDetails);
|
|
13854
14035
|
}
|
|
@@ -13858,10 +14039,19 @@ ${label}:${value}`;
|
|
|
13858
14039
|
"invalid_client",
|
|
13859
14040
|
"unauthorized_client",
|
|
13860
14041
|
"provider_configuration_unavailable",
|
|
13861
|
-
"shared_dev_credential_unavailable"
|
|
14042
|
+
"shared_dev_credential_unavailable",
|
|
14043
|
+
"managed_oauth_scope_approval_required"
|
|
13862
14044
|
].includes(errorCode)) {
|
|
13863
14045
|
throw new ConnectConfigError(errorMessage, errorDetails);
|
|
13864
14046
|
}
|
|
14047
|
+
const identitySyncStatus = IDENTITY_SYNC_POLL_STATUS[errorCode];
|
|
14048
|
+
if (identitySyncStatus !== void 0) {
|
|
14049
|
+
throw new BackendError(
|
|
14050
|
+
errorMessage,
|
|
14051
|
+
errorDetails,
|
|
14052
|
+
identitySyncStatus
|
|
14053
|
+
);
|
|
14054
|
+
}
|
|
13865
14055
|
throw new ConnectFlowError(errorMessage, errorDetails);
|
|
13866
14056
|
}
|
|
13867
14057
|
if (pollStatus === "expired") {
|
|
@@ -13955,6 +14145,14 @@ ${label}:${value}`;
|
|
|
13955
14145
|
* shape (popup, mobile redirect, headless). The convenience
|
|
13956
14146
|
* method doesn't infer these from the error.
|
|
13957
14147
|
* @throws AlterValueError if `error.providerId` is `undefined`.
|
|
14148
|
+
* @throws ConnectConfigError if the recovery session could not be minted
|
|
14149
|
+
* because the Alter-managed OAuth client is not approved for the scopes
|
|
14150
|
+
* this app requests (HTTP 409 `managed_oauth_scope_approval_required`).
|
|
14151
|
+
* This helper forwards to `createConnectSession`, so it throws
|
|
14152
|
+
* everything that method throws — see its `@throws` list for the
|
|
14153
|
+
* `details` payload. Retrying is futile: contact Alter to approve the
|
|
14154
|
+
* scopes, or narrow the app's configured scopes to the approved set, and
|
|
14155
|
+
* only then mint a recovery session.
|
|
13958
14156
|
*/
|
|
13959
14157
|
async createConnectSessionForError(error51, options) {
|
|
13960
14158
|
const providerId = error51.providerId;
|
|
@@ -14096,6 +14294,11 @@ ${label}:${value}`;
|
|
|
14096
14294
|
* `pollIntervalMs` is non-finite or not greater than zero.
|
|
14097
14295
|
* @throws AlterSDKError if the SDK is closed or the IDP returned a terminal
|
|
14098
14296
|
* error / the session expired.
|
|
14297
|
+
* @throws BackendError if identity synchronization could not be completed.
|
|
14298
|
+
* Surfaced separately from the terminal `AlterSDKError` above precisely
|
|
14299
|
+
* because it is NOT a rejected login: `statusCode` 503 is transient
|
|
14300
|
+
* contention carrying `details.retry_after_seconds`, and 409 is an
|
|
14301
|
+
* operator-repairable provider fault that no retry will clear.
|
|
14099
14302
|
* @throws ConnectTimeoutError if the user did not complete login in time
|
|
14100
14303
|
* (`details.reason` is `"poll_deadline_elapsed"`).
|
|
14101
14304
|
*/
|
|
@@ -14127,8 +14330,19 @@ ${label}:${value}`;
|
|
|
14127
14330
|
});
|
|
14128
14331
|
}
|
|
14129
14332
|
if (pollData.status === "error") {
|
|
14130
|
-
const errorMessage = pollData.error_message
|
|
14131
|
-
|
|
14333
|
+
const errorMessage = typeof pollData.error_message === "string" && pollData.error_message ? pollData.error_message : "unknown error";
|
|
14334
|
+
const message = `Authentication failed: ${errorMessage}`;
|
|
14335
|
+
const syncCode = typeof pollData.error_code === "string" ? pollData.error_code : "";
|
|
14336
|
+
const identitySyncStatus = IDENTITY_SYNC_POLL_STATUS[syncCode];
|
|
14337
|
+
if (identitySyncStatus !== void 0) {
|
|
14338
|
+
const details = { error_code: syncCode };
|
|
14339
|
+
const retryAfter = _coerceInt(pollData.retry_after_seconds);
|
|
14340
|
+
if (retryAfter !== void 0) {
|
|
14341
|
+
details.retry_after_seconds = retryAfter;
|
|
14342
|
+
}
|
|
14343
|
+
throw new BackendError(message, details, identitySyncStatus);
|
|
14344
|
+
}
|
|
14345
|
+
throw new AlterSDKError(message);
|
|
14132
14346
|
}
|
|
14133
14347
|
if (pollData.status === "expired") {
|
|
14134
14348
|
throw new AlterSDKError("Authentication session expired");
|
|
@@ -14145,10 +14359,13 @@ ${label}:${value}`;
|
|
|
14145
14359
|
(resolve5) => setTimeout(resolve5, Math.min(pollIntervalMs, remaining))
|
|
14146
14360
|
);
|
|
14147
14361
|
}
|
|
14148
|
-
throw new ConnectTimeoutError(
|
|
14149
|
-
timeoutMs
|
|
14150
|
-
|
|
14151
|
-
|
|
14362
|
+
throw new ConnectTimeoutError(
|
|
14363
|
+
`Authentication timed out after ${timeoutMs}ms. The user may not have completed login in the browser.`,
|
|
14364
|
+
{
|
|
14365
|
+
timeoutMs,
|
|
14366
|
+
reason: "poll_deadline_elapsed"
|
|
14367
|
+
}
|
|
14368
|
+
);
|
|
14152
14369
|
}
|
|
14153
14370
|
/**
|
|
14154
14371
|
* Single sign-in poll attempt (INTERNAL).
|
|
@@ -14712,12 +14929,13 @@ ${label}:${value}`;
|
|
|
14712
14929
|
async #mapProxyErrorResponse(response) {
|
|
14713
14930
|
let detail = {};
|
|
14714
14931
|
try {
|
|
14715
|
-
const
|
|
14716
|
-
|
|
14932
|
+
const parsed = await response.clone().json();
|
|
14933
|
+
const payloadDict = isPlainRecord(parsed) ? parsed : {};
|
|
14934
|
+
detail = isPlainRecord(payloadDict.detail) ? payloadDict.detail : payloadDict;
|
|
14717
14935
|
} catch {
|
|
14718
14936
|
}
|
|
14719
14937
|
const errCode = detail.error || "";
|
|
14720
|
-
const msg = detail.message
|
|
14938
|
+
const msg = typeof detail.message === "string" && detail.message ? detail.message : `/sdk/proxy returned ${response.status}`;
|
|
14721
14939
|
if (errCode === "approval_denied")
|
|
14722
14940
|
throw new ApprovalDeniedError({ message: msg });
|
|
14723
14941
|
if (errCode === "approval_expired")
|
|
@@ -14801,11 +15019,21 @@ ${label}:${value}`;
|
|
|
14801
15019
|
* @throws ApprovalExpiredError on `expired`.
|
|
14802
15020
|
* @throws ApprovalExecutionFailedError on `failed`.
|
|
14803
15021
|
* @throws ApprovalTimeoutError if the local wait elapses before any decision.
|
|
15022
|
+
* @throws AlterValueError if `timeoutMs` is not a finite number, or
|
|
15023
|
+
* `pollIntervalMs` is non-finite or not greater than zero.
|
|
14804
15024
|
*/
|
|
14805
15025
|
async awaitApproval(approvalId, options = {}) {
|
|
14806
15026
|
this.#assertNotClosed();
|
|
14807
15027
|
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
14808
15028
|
const pollIntervalMs = options.pollIntervalMs ?? 2e3;
|
|
15029
|
+
if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) {
|
|
15030
|
+
throw new AlterValueError("timeoutMs must be a finite number");
|
|
15031
|
+
}
|
|
15032
|
+
if (typeof pollIntervalMs !== "number" || !Number.isFinite(pollIntervalMs) || pollIntervalMs <= 0) {
|
|
15033
|
+
throw new AlterValueError(
|
|
15034
|
+
"pollIntervalMs must be a finite number greater than 0"
|
|
15035
|
+
);
|
|
15036
|
+
}
|
|
14809
15037
|
const deadline = Date.now() + timeoutMs;
|
|
14810
15038
|
let lastTransient = null;
|
|
14811
15039
|
while (true) {
|
|
@@ -14831,16 +15059,16 @@ ${label}:${value}`;
|
|
|
14831
15059
|
lastTransient
|
|
14832
15060
|
);
|
|
14833
15061
|
}
|
|
14834
|
-
const sleep2 = Math.
|
|
14835
|
-
|
|
14836
|
-
Math.
|
|
15062
|
+
const sleep2 = Math.min(
|
|
15063
|
+
pollIntervalMs,
|
|
15064
|
+
Math.max(0, deadline - Date.now())
|
|
14837
15065
|
);
|
|
14838
15066
|
await new Promise((res) => setTimeout(res, sleep2));
|
|
14839
15067
|
continue;
|
|
14840
15068
|
}
|
|
14841
15069
|
lastTransient = null;
|
|
14842
15070
|
if (status.status === "executed" && !status.hasResult) {
|
|
14843
|
-
const next = Math.max(
|
|
15071
|
+
const next = Math.max(0, deadline - Date.now());
|
|
14844
15072
|
await new Promise((r) => setTimeout(r, Math.min(pollIntervalMs, next)));
|
|
14845
15073
|
continue;
|
|
14846
15074
|
}
|
|
@@ -14867,9 +15095,9 @@ ${label}:${value}`;
|
|
|
14867
15095
|
lastTransient
|
|
14868
15096
|
);
|
|
14869
15097
|
}
|
|
14870
|
-
const sleep2 = Math.
|
|
14871
|
-
|
|
14872
|
-
Math.
|
|
15098
|
+
const sleep2 = Math.min(
|
|
15099
|
+
pollIntervalMs,
|
|
15100
|
+
Math.max(0, deadline - Date.now())
|
|
14873
15101
|
);
|
|
14874
15102
|
await new Promise((res) => setTimeout(res, sleep2));
|
|
14875
15103
|
continue;
|
|
@@ -14879,7 +15107,7 @@ ${label}:${value}`;
|
|
|
14879
15107
|
if (now >= deadline) {
|
|
14880
15108
|
throw this.#buildApprovalTimeout(approvalId, timeoutMs, lastTransient);
|
|
14881
15109
|
}
|
|
14882
|
-
const sleep = Math.
|
|
15110
|
+
const sleep = Math.min(pollIntervalMs, Math.max(0, deadline - now));
|
|
14883
15111
|
await new Promise((res) => setTimeout(res, sleep));
|
|
14884
15112
|
}
|
|
14885
15113
|
}
|
|
@@ -15841,11 +16069,18 @@ var App = class _App {
|
|
|
15841
16069
|
* @param options.label - Required sibling address (resolution key:
|
|
15842
16070
|
* provider + label). Must be unique among the credential's active
|
|
15843
16071
|
* sibling grants.
|
|
15844
|
-
* @param options.grantPolicy - Optional per-grant policy
|
|
15845
|
-
*
|
|
15846
|
-
*
|
|
15847
|
-
*
|
|
15848
|
-
* `
|
|
16072
|
+
* @param options.grantPolicy - Optional per-grant policy. `expiresAt`
|
|
16073
|
+
* and `maxTtlSeconds` are accepted in this SDK's camelCase
|
|
16074
|
+
* (validated locally and mapped to the snake_case wire keys,
|
|
16075
|
+
* exactly like `createConnectSession`); the wider policy grammar
|
|
16076
|
+
* (`restrictions`, `requires_approval`, …) uses the backend's
|
|
16077
|
+
* snake_case keys and is validated server-side — an invalid policy
|
|
16078
|
+
* throws `BackendError` from the 422 `invalid_grant_policy`
|
|
16079
|
+
* response. A default TTL is not part of the mint grammar:
|
|
16080
|
+
* `defaultTtlSeconds` / `default_ttl_seconds` throws
|
|
16081
|
+
* `AlterValueError` (it is accepted only by Connect sessions and
|
|
16082
|
+
* `createManagedSecretGrant`). Supplying both spellings of one
|
|
16083
|
+
* trio field also throws `AlterValueError`.
|
|
15849
16084
|
* @param options.grantTags - Optional list of tag strings stored on
|
|
15850
16085
|
* the sibling grant.
|
|
15851
16086
|
* @returns {@link GrantInfo} for the newly minted sibling grant
|
|
@@ -15881,7 +16116,7 @@ var App = class _App {
|
|
|
15881
16116
|
}
|
|
15882
16117
|
const wireBody = { label: options.label };
|
|
15883
16118
|
if (options.grantPolicy !== void 0) {
|
|
15884
|
-
wireBody.grant_policy = options.grantPolicy;
|
|
16119
|
+
wireBody.grant_policy = _mintGrantPolicyToWire(options.grantPolicy);
|
|
15885
16120
|
}
|
|
15886
16121
|
if (options.grantTags !== void 0) {
|
|
15887
16122
|
wireBody.grant_tags = options.grantTags;
|
|
@@ -16324,7 +16559,7 @@ var DEFAULT_BASE_URL = "https://backend.alterauth.com";
|
|
|
16324
16559
|
var PAT_API_PREFIX = "/api/v1/dev-portal";
|
|
16325
16560
|
var HTTP_ERROR_THRESHOLD = 400;
|
|
16326
16561
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
16327
|
-
var CLI_VERSION = "0.
|
|
16562
|
+
var CLI_VERSION = "0.9.0";
|
|
16328
16563
|
var USER_AGENT = buildUserAgent();
|
|
16329
16564
|
function buildUserAgent() {
|
|
16330
16565
|
let osTag = "";
|
|
@@ -17330,7 +17565,7 @@ function isProviderCatalogEntry(value) {
|
|
|
17330
17565
|
const v = value;
|
|
17331
17566
|
return typeof v.id === "string" && typeof v.name === "string" && typeof v.display_name === "string" && (v.category === null || typeof v.category === "string") && (v.supports_refresh === null || typeof v.supports_refresh === "boolean") && (v.supports_pkce === null || typeof v.supports_pkce === "boolean") && (v.available_environments === void 0 || Array.isArray(v.available_environments) && v.available_environments.every(
|
|
17332
17567
|
(environment) => environment === "production" || environment === "sandbox"
|
|
17333
|
-
)) && v.available_scopes !== null && typeof v.available_scopes === "object" && !Array.isArray(v.available_scopes) && Object.values(v.available_scopes).every(
|
|
17568
|
+
)) && (v.scopes_configured_on_provider === void 0 || typeof v.scopes_configured_on_provider === "boolean") && v.available_scopes !== null && typeof v.available_scopes === "object" && !Array.isArray(v.available_scopes) && Object.values(v.available_scopes).every(
|
|
17334
17569
|
isProviderScopeCatalogEntry
|
|
17335
17570
|
) && (v.managed_credentials_available === void 0 || typeof v.managed_credentials_available === "boolean") && (v.managed_approved_scopes === void 0 || v.managed_approved_scopes === null || Array.isArray(v.managed_approved_scopes) && v.managed_approved_scopes.every(
|
|
17336
17571
|
(scope) => typeof scope === "string"
|
|
@@ -18485,6 +18720,25 @@ var AuditNamespace = class {
|
|
|
18485
18720
|
});
|
|
18486
18721
|
return expectDict(body, "audit.list", 200);
|
|
18487
18722
|
}
|
|
18723
|
+
/** Provider facet values present in one app's audit events. Requires
|
|
18724
|
+
* `dashboard_audit:read`.
|
|
18725
|
+
*
|
|
18726
|
+
* Returns ``{providers}`` — the alphabetized distinct provider ids
|
|
18727
|
+
* (OAuth provider ids, managed-secret template ids — a custom secret's
|
|
18728
|
+
* name-derived slug — and IDP types) actually
|
|
18729
|
+
* present on the app's audit rows; the accepted vocabulary for the
|
|
18730
|
+
* ``--provider`` filters. Data-derived, never a hardcoded shortlist.
|
|
18731
|
+
*/
|
|
18732
|
+
async providers(appId) {
|
|
18733
|
+
const query = optionsToQuery({ app_id: appId });
|
|
18734
|
+
const body = await this.#client._call(
|
|
18735
|
+
"GET",
|
|
18736
|
+
"/audit-logs/unified/providers",
|
|
18737
|
+
"audit.providers",
|
|
18738
|
+
{ query }
|
|
18739
|
+
);
|
|
18740
|
+
return expectDict(body, "audit.providers", 200);
|
|
18741
|
+
}
|
|
18488
18742
|
/** List dashboard / CLI admin actions. Requires `dashboard_audit:read`.
|
|
18489
18743
|
*
|
|
18490
18744
|
* Returns the canonical envelope ``{items, total, limit, offset, has_more}``
|
|
@@ -35607,6 +35861,9 @@ var CONTENT_MATCH_LIMITS = {
|
|
|
35607
35861
|
};
|
|
35608
35862
|
var MAX_RULE_NAME_LEN = 120;
|
|
35609
35863
|
var MAX_RULE_DESCRIPTION_LEN = 2e3;
|
|
35864
|
+
var MAX_RULES_PER_TARGET = 100;
|
|
35865
|
+
var MAX_DERIVED_RULES_PER_TARGET = 2;
|
|
35866
|
+
var MAX_POLICY_RULE_PAGE_SIZE = MAX_RULES_PER_TARGET + MAX_DERIVED_RULES_PER_TARGET;
|
|
35610
35867
|
var HITL_NOTIFICATION_CHANNELS = ["email"];
|
|
35611
35868
|
var HITL_LIMITS = {
|
|
35612
35869
|
maxApprovers: 10,
|
|
@@ -37302,6 +37559,33 @@ function buildAuditCommand() {
|
|
|
37302
37559
|
});
|
|
37303
37560
|
}
|
|
37304
37561
|
);
|
|
37562
|
+
audit.command("providers").description(
|
|
37563
|
+
"List the provider IDs present in an app's audit events \u2014 the values `--provider` accepts (OAuth provider ids, managed-secret template ids \u2014 a custom secret's name-derived slug \u2014 and IDP types). Derived from the audit data, so providers whose config was since deleted still appear."
|
|
37564
|
+
).option(
|
|
37565
|
+
APP_FLAG,
|
|
37566
|
+
"App whose audit trail to facet (ID or name; falls back to ALTER_APP_ID / the workspace pin)"
|
|
37567
|
+
).option(
|
|
37568
|
+
"--output <format>",
|
|
37569
|
+
"Output format: json|jsonl|table (default: table)",
|
|
37570
|
+
"table"
|
|
37571
|
+
).action(async (options) => {
|
|
37572
|
+
const format = coerceOutputFormat(options.output);
|
|
37573
|
+
const appId = await resolveAppOrExit(options.app);
|
|
37574
|
+
await withClient(async (client) => {
|
|
37575
|
+
const result = await client.audit.providers(appId);
|
|
37576
|
+
const providers = result["providers"];
|
|
37577
|
+
if (!Array.isArray(providers) || !providers.every((p) => typeof p === "string")) {
|
|
37578
|
+
err(
|
|
37579
|
+
'audit.providers: response is missing the "providers" string array. This is a wire-format regression; report it to the backend team.'
|
|
37580
|
+
);
|
|
37581
|
+
process.exit(EXIT_ERROR);
|
|
37582
|
+
}
|
|
37583
|
+
const rows = providers.map((provider_id) => ({ provider_id }));
|
|
37584
|
+
emit2(format, rows, [
|
|
37585
|
+
{ label: "PROVIDER", get: (r) => r.provider_id }
|
|
37586
|
+
]);
|
|
37587
|
+
});
|
|
37588
|
+
});
|
|
37305
37589
|
audit.command("show <trace-id>").description("Show every event for one trace").option(
|
|
37306
37590
|
"--output <format>",
|
|
37307
37591
|
"Output format: json|jsonl|table (default: json)",
|
|
@@ -44326,8 +44610,8 @@ function buildRulesSubcommand() {
|
|
|
44326
44610
|
)
|
|
44327
44611
|
).option(
|
|
44328
44612
|
"--limit <n>",
|
|
44329
|
-
|
|
44330
|
-
parseBoundedInt("--limit", 1,
|
|
44613
|
+
`Page size (1-${MAX_POLICY_RULE_PAGE_SIZE}; when omitted the backend default of ${MAX_POLICY_RULE_PAGE_SIZE} applies)`,
|
|
44614
|
+
parseBoundedInt("--limit", 1, MAX_POLICY_RULE_PAGE_SIZE)
|
|
44331
44615
|
).option(
|
|
44332
44616
|
"--offset <n>",
|
|
44333
44617
|
"Page offset (default 0)",
|
|
@@ -44761,7 +45045,8 @@ async function providerCatalogError(client, providerId, environment, credentialS
|
|
|
44761
45045
|
const availableScopes = provider.available_scopes ?? {};
|
|
44762
45046
|
if (Object.keys(availableScopes).length === 0) {
|
|
44763
45047
|
if (scopes !== void 0 && scopes.length > 0) {
|
|
44764
|
-
|
|
45048
|
+
const where = provider.scopes_configured_on_provider === true ? `; its permissions are configured on the ${provider.display_name} app itself` : "";
|
|
45049
|
+
return `alter: provider '${providerId}' publishes no selectable scopes \u2014 omit --scopes (Alter does not send a scope parameter to it${where})
|
|
44765
45050
|
`;
|
|
44766
45051
|
}
|
|
44767
45052
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alter-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Command-line interface for the Alter Vault dev portal — scripted dashboard automation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"keytar": "^7.9.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@alter-ai/alter-sdk": "workspace:0.
|
|
46
|
+
"@alter-ai/alter-sdk": "workspace:0.24.0",
|
|
47
47
|
"@alter-vault/shared-types": "workspace:0.0.1",
|
|
48
48
|
"@alter-vault/shared-utils": "workspace:0.0.1",
|
|
49
49
|
"@eslint/js": "9.39.4",
|