@vectros-ai/cli 0.24.0 → 0.25.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/AUTHORING.md +12 -8
- package/CHANGELOG.md +67 -0
- package/README.md +51 -22
- package/dist/cli.js +314 -184
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +314 -184
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -1619,7 +1619,7 @@ var require_proper_lockfile = __commonJS({
|
|
|
1619
1619
|
// src/cli-bootstrap.ts
|
|
1620
1620
|
import * as readline2 from "readline/promises";
|
|
1621
1621
|
|
|
1622
|
-
// node_modules/@vectros-ai/sdk/dist/index.mjs
|
|
1622
|
+
// ../../node_modules/@vectros-ai/sdk/dist/index.mjs
|
|
1623
1623
|
var __defProp2 = Object.defineProperty;
|
|
1624
1624
|
var __export = (target, all) => {
|
|
1625
1625
|
for (var name in all)
|
|
@@ -4049,7 +4049,7 @@ var AuthClient = class {
|
|
|
4049
4049
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/jwks");
|
|
4050
4050
|
}
|
|
4051
4051
|
/**
|
|
4052
|
-
* Returns a page of per-subject PHI read-access rows: who read which subject's PHI, when, against which record, and whether any sensitive value was actually revealed in plaintext. Metadata only — never the PHI itself.
|
|
4052
|
+
* Returns a page of per-subject PHI read-access rows: who read which subject's PHI, when, against which record, and whether any sensitive value was actually revealed in plaintext. Metadata only — never the PHI itself. Rows exist only where read-access logging is enabled. Provide at least one query axis: a subject (`subjectType` + `subjectId`) within a `contextId` for the primary accounting query; `resourceId` within a `contextId` for 'who read this record'; `callerKeyId` for 'what did this credential read' (account-wide forensic); or `contextId` alone to enumerate a whole context. `from`/`to` bound the time window. Results are scoped to your account, derived from your token — never from input. Requires the `access-log:r` scope.
|
|
4053
4053
|
*
|
|
4054
4054
|
* @param {Vectros.GetAccessLogRequest} request
|
|
4055
4055
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5754,7 +5754,7 @@ var AuthClient = class {
|
|
|
5754
5754
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/issuers/{issuerId}");
|
|
5755
5755
|
}
|
|
5756
5756
|
/**
|
|
5757
|
-
* Updates the mutable fields of a registered issuer: `subClaim`, `emailClaim`, `userinfoUri`, `capturedClaims`, `restrictedToDomain` (a new non-blank value must already be a VERIFIED domain for your account; an empty string clears it
|
|
5757
|
+
* Updates the mutable fields of a registered issuer: `subClaim`, `emailClaim`, `userinfoUri`, `capturedClaims`, `restrictedToDomain` (a new non-blank value must already be a VERIFIED domain for your account; an empty string clears it where permitted; see `IssuerUpdateRequest.restrictedToDomain` for the full semantics), `status` (`active`/`suspended` — a registration awaiting verification (`pending_verification`) accepts no status change until it is verified; a suspended issuer's tokens are rejected identically to an unregistered issuer at exchange time), and `selfSignupPolicies`. Fields omitted from the body are left unchanged (partial update). `issuer`, `jwksUri`, `audience`, and `contextId` are trust-anchor / routing-pin fields and are immutable via this route — supplying a value that differs from the current registration is rejected with 400; supplying the current value back is a no-op. Rotating a trust anchor requires deleting and re-registering the issuer, which is itself refused while any user is bound through it. **`subClaim` is identity-determining, not merely cosmetic**: it names which verified JWT claim is read as the federated user's identifier, so changing it on an issuer that already has bound users would silently re-identify (or, under self-signup, orphan) every one of them — changing it is therefore refused once any user has bound through this issuer, the same guard `DELETE` already applies, and is only free before the first real login. Requires a root API key or the bootstrap's provisioning capability, gated identically to every other operation on this surface. A credential confined to one app context may only update an issuer registered in that context; naming one registered in another context returns 404, identically to a nonexistent issuerId. A root API key may update any issuer.
|
|
5758
5758
|
*
|
|
5759
5759
|
* @param {Vectros.IssuerUpdateRequest} request
|
|
5760
5760
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6564,7 +6564,7 @@ var ComplianceClient = class {
|
|
|
6564
6564
|
this._options = normalizeClientOptionsWithAuth(options);
|
|
6565
6565
|
}
|
|
6566
6566
|
/**
|
|
6567
|
-
* Submits
|
|
6567
|
+
* Submits an erasure request for a single end-subject (a user, or an identity entity in any namespace). Erasure deletes the records, documents, folders and record schemas the subject solely owns across the declared contexts (and, for a user, its access profiles there), plus its identity and lookup rows when all contexts are covered. Rows co-owned with another owner scope or principal are kept; read-access log entries are not erased. The request is asynchronous: it returns 202 with a `requestId`; poll `GET /v1/erasure-requests/{id}` until the job completes to obtain the completion certificate. Requires a root API key — a scoped credential is rejected with 403.
|
|
6568
6568
|
*
|
|
6569
6569
|
* @param {Vectros.ErasureRequest} request
|
|
6570
6570
|
* @param {ComplianceClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6627,7 +6627,7 @@ var ComplianceClient = class {
|
|
|
6627
6627
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/erasure-requests");
|
|
6628
6628
|
}
|
|
6629
6629
|
/**
|
|
6630
|
-
* Polls an erasure request by id. While the job is still running this returns its status only; once it completes, the response also includes the
|
|
6630
|
+
* Polls an erasure request by id. While the job is still running this returns its status only; once it completes, the response also includes the completion certificate (which contexts were swept, per-context deletion counts, whether the identity was kept, and reports of dangling references and shared rows left intact). Requires a root API key.
|
|
6631
6631
|
*
|
|
6632
6632
|
* @param {Vectros.GetErasureRequestRequest} request
|
|
6633
6633
|
* @param {ComplianceClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -9446,6 +9446,8 @@ var InferenceClient = class {
|
|
|
9446
9446
|
throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
|
|
9447
9447
|
case 403:
|
|
9448
9448
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
9449
|
+
case 413:
|
|
9450
|
+
throw new ContentTooLargeError(_response.error.body, _response.rawResponse);
|
|
9449
9451
|
case 429:
|
|
9450
9452
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
9451
9453
|
default:
|
|
@@ -9585,6 +9587,8 @@ var InferenceClient = class {
|
|
|
9585
9587
|
throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
|
|
9586
9588
|
case 403:
|
|
9587
9589
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
9590
|
+
case 413:
|
|
9591
|
+
throw new ContentTooLargeError(_response.error.body, _response.rawResponse);
|
|
9588
9592
|
case 429:
|
|
9589
9593
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
9590
9594
|
case 500:
|
|
@@ -11882,7 +11886,7 @@ var RenderHintsSchema = z.object({
|
|
|
11882
11886
|
helpText: z.string().optional(),
|
|
11883
11887
|
// Marks this field as the record's headline (display) field — the linked
|
|
11884
11888
|
// primary column in the records list + the title on the detail view. At most
|
|
11885
|
-
// one per schema (the platform
|
|
11889
|
+
// one per schema (the platform stores the hint and does not enforce one; the reference UI uses the first by order). Format passthrough →
|
|
11886
11890
|
// SchemaRequest.renderHints[fieldId].displayField.
|
|
11887
11891
|
displayField: z.boolean().optional()
|
|
11888
11892
|
}).strict();
|
|
@@ -13541,7 +13545,8 @@ var BlueprintSchema = z.object({
|
|
|
13541
13545
|
* alongside app-context/service-principal creation and issuers, BEFORE
|
|
13542
13546
|
* schemas/seed (an `entityBacked` namespace's entities, and an
|
|
13543
13547
|
* `allowedSurfaces: ['entity']` schema, both need the registration to
|
|
13544
|
-
* already exist). `org`/`client`
|
|
13548
|
+
* already exist). `org`/`client` are registered the same way as any other
|
|
13549
|
+
* namespace: signup does not register them, so declare an entry for each you use.
|
|
13545
13550
|
*/
|
|
13546
13551
|
namespaces: z.array(BlueprintNamespaceSchema).optional(),
|
|
13547
13552
|
/**
|
|
@@ -16505,6 +16510,25 @@ async function clearSession(baseDir) {
|
|
|
16505
16510
|
return false;
|
|
16506
16511
|
}
|
|
16507
16512
|
}
|
|
16513
|
+
var TokenEndpointError = class extends Error {
|
|
16514
|
+
status;
|
|
16515
|
+
/** The OAuth `error` field of a JSON error body (e.g. `invalid_grant`), when there is one. */
|
|
16516
|
+
oauthError;
|
|
16517
|
+
constructor(status, text) {
|
|
16518
|
+
super(`token endpoint HTTP ${status}${text ? ` \u2014 ${stripControlCharacters(text, 500)}` : ""}`);
|
|
16519
|
+
this.name = "TokenEndpointError";
|
|
16520
|
+
this.status = status;
|
|
16521
|
+
this.oauthError = oauthErrorOf(text);
|
|
16522
|
+
}
|
|
16523
|
+
};
|
|
16524
|
+
function oauthErrorOf(text) {
|
|
16525
|
+
try {
|
|
16526
|
+
const parsed = JSON.parse(text);
|
|
16527
|
+
return typeof parsed.error === "string" ? parsed.error : void 0;
|
|
16528
|
+
} catch {
|
|
16529
|
+
return void 0;
|
|
16530
|
+
}
|
|
16531
|
+
}
|
|
16508
16532
|
async function postToken(config, body, fetchImpl) {
|
|
16509
16533
|
const res = await fetchImpl(tokenEndpoint(config), {
|
|
16510
16534
|
method: "POST",
|
|
@@ -16512,9 +16536,7 @@ async function postToken(config, body, fetchImpl) {
|
|
|
16512
16536
|
body: body.toString()
|
|
16513
16537
|
});
|
|
16514
16538
|
const text = await res.text().catch(() => "");
|
|
16515
|
-
if (!res.ok)
|
|
16516
|
-
throw new Error(`token endpoint HTTP ${res.status}${text ? ` \u2014 ${stripControlCharacters(text, 500)}` : ""}`);
|
|
16517
|
-
}
|
|
16539
|
+
if (!res.ok) throw new TokenEndpointError(res.status, text);
|
|
16518
16540
|
try {
|
|
16519
16541
|
return JSON.parse(text);
|
|
16520
16542
|
} catch {
|
|
@@ -16530,31 +16552,54 @@ async function refreshTokens(config, refreshToken, fetchImpl = fetch) {
|
|
|
16530
16552
|
|
|
16531
16553
|
// src/auth-context.ts
|
|
16532
16554
|
var COGNITO_TOKEN_ENV = "VECTROS_BOOTSTRAP_TOKEN";
|
|
16555
|
+
function loginCommandFor(env) {
|
|
16556
|
+
return env === "staging" ? "vectros login --env staging" : "vectros login";
|
|
16557
|
+
}
|
|
16533
16558
|
var MissingCredentialError = class extends Error {
|
|
16534
|
-
constructor() {
|
|
16559
|
+
constructor(env) {
|
|
16535
16560
|
super(
|
|
16536
|
-
`No credential found.
|
|
16561
|
+
`No credential found. Run \`${loginCommandFor(env)}\` to sign in, or set ${COGNITO_TOKEN_ENV} or pass --token (generate a token in the developer portal).`
|
|
16537
16562
|
);
|
|
16538
16563
|
this.name = "MissingCredentialError";
|
|
16539
16564
|
}
|
|
16540
16565
|
};
|
|
16566
|
+
var SessionExpiredError = class extends Error {
|
|
16567
|
+
constructor(env) {
|
|
16568
|
+
super(
|
|
16569
|
+
`Your Vectros login session has expired or was revoked. Run \`${loginCommandFor(env)}\` to sign in again.`
|
|
16570
|
+
);
|
|
16571
|
+
this.name = "SessionExpiredError";
|
|
16572
|
+
}
|
|
16573
|
+
};
|
|
16541
16574
|
function resolveCognitoTokenSync(explicit) {
|
|
16542
16575
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
16543
16576
|
const fromEnv = process.env[COGNITO_TOKEN_ENV];
|
|
16544
16577
|
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
16545
16578
|
return void 0;
|
|
16546
16579
|
}
|
|
16547
|
-
async function resolveCognitoToken(env, explicit) {
|
|
16580
|
+
async function resolveCognitoToken(env, explicit, deps = { readSession, refreshTokens, saveSession }) {
|
|
16548
16581
|
const sync = resolveCognitoTokenSync(explicit);
|
|
16549
16582
|
if (sync) return sync;
|
|
16550
|
-
const session = await readSession();
|
|
16583
|
+
const session = await deps.readSession();
|
|
16551
16584
|
if (session && session.env === env) {
|
|
16552
16585
|
const config = resolveCognitoConfig(env);
|
|
16553
|
-
|
|
16586
|
+
let tokens;
|
|
16587
|
+
try {
|
|
16588
|
+
tokens = await deps.refreshTokens(config, session.refreshToken);
|
|
16589
|
+
} catch (err) {
|
|
16590
|
+
if (err instanceof TokenEndpointError && err.oauthError === "invalid_grant") throw new SessionExpiredError(env);
|
|
16591
|
+
throw err;
|
|
16592
|
+
}
|
|
16593
|
+
if (tokens.refresh_token && tokens.refresh_token !== session.refreshToken) {
|
|
16594
|
+
const stored = await deps.readSession().catch(() => void 0);
|
|
16595
|
+
if (stored?.refreshToken === session.refreshToken) {
|
|
16596
|
+
await deps.saveSession({ ...session, refreshToken: tokens.refresh_token }).catch(() => void 0);
|
|
16597
|
+
}
|
|
16598
|
+
}
|
|
16554
16599
|
const bearer = tokens.id_token ?? tokens.access_token;
|
|
16555
16600
|
if (bearer) return bearer;
|
|
16556
16601
|
}
|
|
16557
|
-
throw new MissingCredentialError();
|
|
16602
|
+
throw new MissingCredentialError(env);
|
|
16558
16603
|
}
|
|
16559
16604
|
async function resolveAuthContext(opts) {
|
|
16560
16605
|
const baseUrl = envToBaseUrl(opts.env, opts.baseUrl ?? process.env.VECTROS_API_BASE_URL);
|
|
@@ -17066,6 +17111,7 @@ function keyNameFor(pack, machineId) {
|
|
|
17066
17111
|
}
|
|
17067
17112
|
|
|
17068
17113
|
// src/identity-resolver.ts
|
|
17114
|
+
var IDENTITIES_TO_ENSURE_HEADING = " Identities to ensure (idempotent by external id; a non-user entity lives with its namespace):";
|
|
17069
17115
|
function userPayload(decl) {
|
|
17070
17116
|
const meta = decl.metadata;
|
|
17071
17117
|
if (decl.displayName === void 0) return meta;
|
|
@@ -17206,7 +17252,7 @@ async function withAuth(opts, deps, body) {
|
|
|
17206
17252
|
log
|
|
17207
17253
|
});
|
|
17208
17254
|
} catch (err) {
|
|
17209
|
-
if (err instanceof MissingCredentialError || err instanceof InvalidBaseUrlError || err instanceof InvalidCognitoRegionError) {
|
|
17255
|
+
if (err instanceof MissingCredentialError || err instanceof SessionExpiredError || err instanceof InvalidBaseUrlError || err instanceof InvalidCognitoRegionError) {
|
|
17210
17256
|
process.stderr.write(` \u2716 ${err.message}
|
|
17211
17257
|
`);
|
|
17212
17258
|
return 2;
|
|
@@ -17801,15 +17847,6 @@ async function provisionContext(client, input) {
|
|
|
17801
17847
|
const { blueprint, allowTenantWideNamespaces = false, readExistingKey, confirmExistingPrincipal, log } = input;
|
|
17802
17848
|
assertBlueprintGates(blueprint);
|
|
17803
17849
|
assertTenantWideNamespacesAllowed(blueprint, allowTenantWideNamespaces);
|
|
17804
|
-
const ctxResp = await client.auth.createAppContext({
|
|
17805
|
-
body: {
|
|
17806
|
-
contextId: blueprint.contextId,
|
|
17807
|
-
name: contextNameOf(blueprint),
|
|
17808
|
-
...blueprint.identityProjectionClaims === void 0 ? {} : { identityProjectionClaims: blueprint.identityProjectionClaims }
|
|
17809
|
-
}
|
|
17810
|
-
});
|
|
17811
|
-
const contextAction = actionOf(ctxResp.created);
|
|
17812
|
-
log.debug({ component: "loader", contextId: blueprint.contextId, contextAction }, "app-context ensured");
|
|
17813
17850
|
const user = await client.identity.createUser({
|
|
17814
17851
|
body: {
|
|
17815
17852
|
externalId: blueprint.servicePrincipal.externalId,
|
|
@@ -17850,6 +17887,15 @@ async function provisionContext(client, input) {
|
|
|
17850
17887
|
"existing-principal collision confirmed (or unwired) \u2014 proceeding with any further write to this principal"
|
|
17851
17888
|
);
|
|
17852
17889
|
}
|
|
17890
|
+
const ctxResp = await client.auth.createAppContext({
|
|
17891
|
+
body: {
|
|
17892
|
+
contextId: blueprint.contextId,
|
|
17893
|
+
name: contextNameOf(blueprint),
|
|
17894
|
+
...blueprint.identityProjectionClaims === void 0 ? {} : { identityProjectionClaims: blueprint.identityProjectionClaims }
|
|
17895
|
+
}
|
|
17896
|
+
});
|
|
17897
|
+
const contextAction = actionOf(ctxResp.created);
|
|
17898
|
+
log.debug({ component: "loader", contextId: blueprint.contextId, contextAction }, "app-context ensured");
|
|
17853
17899
|
const needsContextPinnedBootstrap = (blueprint.namespaces ?? []).length > 0;
|
|
17854
17900
|
const ctxBootstrapClient = needsContextPinnedBootstrap ? input.remintBootstrapForContext ? await input.remintBootstrapForContext(blueprint.contextId) : client : void 0;
|
|
17855
17901
|
if (ctxBootstrapClient && input.remintBootstrapForContext) {
|
|
@@ -18358,14 +18404,14 @@ function renderPlanDataScope(ds) {
|
|
|
18358
18404
|
function buildPlan(blueprint, opts = {}) {
|
|
18359
18405
|
const steps = [];
|
|
18360
18406
|
const projection = blueprint.identityProjectionClaims?.length ? `, identity projection: [${blueprint.identityProjectionClaims.join(", ")}]` : blueprint.identityProjectionClaims ? ", identity projection: disabled (empty list)" : "";
|
|
18361
|
-
steps.push({
|
|
18362
|
-
kind: "context",
|
|
18363
|
-
summary: `context '${blueprint.contextId}' (${contextNameOf(blueprint)}${projection})`
|
|
18364
|
-
});
|
|
18365
18407
|
steps.push({
|
|
18366
18408
|
kind: "principal",
|
|
18367
18409
|
summary: `principal '${blueprint.servicePrincipal.externalId}' (SERVICE)`
|
|
18368
18410
|
});
|
|
18411
|
+
steps.push({
|
|
18412
|
+
kind: "context",
|
|
18413
|
+
summary: `context '${blueprint.contextId}' (${contextNameOf(blueprint)}${projection})`
|
|
18414
|
+
});
|
|
18369
18415
|
for (const ns of blueprint.namespaces ?? []) {
|
|
18370
18416
|
const ownerNote = ns.tenantWide ? "TENANT-WIDE \u2014 visible to every context" : `owned by '${blueprint.contextId}'`;
|
|
18371
18417
|
const entityNote = ns.entityBacked ? ", entity-backed" : "";
|
|
@@ -18920,9 +18966,8 @@ var OsKeychainSecretStore = class {
|
|
|
18920
18966
|
* enumeration, and `deleteCredential` on a nonexistent ref returns the same
|
|
18921
18967
|
* meaningless `false` as Linux. So the `.some(c => c.account === ref)` comparison
|
|
18922
18968
|
* is sound on both measured platforms. Still unmeasured: a CI lane that runs this
|
|
18923
|
-
* against the real Windows store on every
|
|
18924
|
-
* measurement, not a regression gate.
|
|
18925
|
-
* public npm, so no tracker number here.)
|
|
18969
|
+
* against the real Windows store on every change — the dev-machine probe is a point
|
|
18970
|
+
* measurement, not a regression gate.
|
|
18926
18971
|
*
|
|
18927
18972
|
* ⚠️ THIS METHOD'S FAIL-CLOSED GUARANTEE IS AN UPSTREAM BEHAVIOURAL CONTRACT, NOT
|
|
18928
18973
|
* SOMETHING THIS CODE ENFORCES. `@napi-rs/keyring` is pinned to an EXACT version
|
|
@@ -19078,7 +19123,7 @@ var CryptoFileSecretStore = class {
|
|
|
19078
19123
|
async setSecret(ref, secret) {
|
|
19079
19124
|
const key = await this.encryptionKey();
|
|
19080
19125
|
const iv = randomBytes2(IV_LEN);
|
|
19081
|
-
const cipher = createCipheriv(ALGO, key, iv);
|
|
19126
|
+
const cipher = createCipheriv(ALGO, key, iv, { authTagLength: TAG_LEN });
|
|
19082
19127
|
const enc = Buffer.concat([cipher.update(secret, "utf8"), cipher.final()]);
|
|
19083
19128
|
const tag = cipher.getAuthTag();
|
|
19084
19129
|
const blob = Buffer.concat([iv, tag, enc]);
|
|
@@ -19101,7 +19146,7 @@ var CryptoFileSecretStore = class {
|
|
|
19101
19146
|
const iv = blob.subarray(0, IV_LEN);
|
|
19102
19147
|
const tag = blob.subarray(IV_LEN, IV_LEN + TAG_LEN);
|
|
19103
19148
|
const enc = blob.subarray(IV_LEN + TAG_LEN);
|
|
19104
|
-
const decipher = createDecipheriv(ALGO, key, iv);
|
|
19149
|
+
const decipher = createDecipheriv(ALGO, key, iv, { authTagLength: TAG_LEN });
|
|
19105
19150
|
decipher.setAuthTag(tag);
|
|
19106
19151
|
return Buffer.concat([decipher.update(enc), decipher.final()]).toString("utf8");
|
|
19107
19152
|
} catch {
|
|
@@ -19301,7 +19346,7 @@ async function withIndexLock(dir, fn, timings = LOCK_OPTS) {
|
|
|
19301
19346
|
// judged: two waiters can both condemn one crash remnant, and the second's
|
|
19302
19347
|
// already-decided rmdir destroys the first's FRESH lock. Nothing freezes.
|
|
19303
19348
|
//
|
|
19304
|
-
// MEASURED against this exact config (
|
|
19349
|
+
// MEASURED against this exact config (probe results below):
|
|
19305
19350
|
// real OS processes, no injection : 0/25 and 1/25 (two independent runs)
|
|
19306
19351
|
// in-process, 5 waiters : ~3% per round
|
|
19307
19352
|
// rival's rmdir lagged one turn : 19/25
|
|
@@ -21134,7 +21179,7 @@ async function resolveBridgeCognitoToken(args, prompt, isTty, resolveCognito = r
|
|
|
21134
21179
|
}
|
|
21135
21180
|
if (!isTty) {
|
|
21136
21181
|
throw new Error(
|
|
21137
|
-
|
|
21182
|
+
`No bridge token: set VECTROS_BOOTSTRAP_TOKEN, pass --token, or run \`${loginCommandFor(args.env)}\` first (required in non-interactive mode).`
|
|
21138
21183
|
);
|
|
21139
21184
|
}
|
|
21140
21185
|
process.stdout.write(
|
|
@@ -21243,7 +21288,9 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
21243
21288
|
`);
|
|
21244
21289
|
const declaredIdentities = Object.entries(blueprint.identities ?? {});
|
|
21245
21290
|
if (declaredIdentities.length > 0) {
|
|
21246
|
-
process.stdout.write(
|
|
21291
|
+
process.stdout.write(`
|
|
21292
|
+
${IDENTITIES_TO_ENSURE_HEADING}
|
|
21293
|
+
`);
|
|
21247
21294
|
for (const [name, decl] of declaredIdentities) {
|
|
21248
21295
|
process.stdout.write(` ~ ${name} (${decl.kind}) \u2014 external id '${decl.externalId}'
|
|
21249
21296
|
`);
|
|
@@ -21474,6 +21521,12 @@ ${codeAddHint}`);
|
|
|
21474
21521
|
}
|
|
21475
21522
|
return finishApply();
|
|
21476
21523
|
} catch (err) {
|
|
21524
|
+
if (err instanceof SessionExpiredError) {
|
|
21525
|
+
process.stderr.write(`
|
|
21526
|
+
\u2716 ${err.message}
|
|
21527
|
+
`);
|
|
21528
|
+
return 2;
|
|
21529
|
+
}
|
|
21477
21530
|
if (err instanceof LostKeyError) {
|
|
21478
21531
|
process.stderr.write(`
|
|
21479
21532
|
\u2716 ${err.message}
|
|
@@ -21845,7 +21898,9 @@ ${renderPlan(blueprint, buildPlan(blueprint, { noSeed, noKey: true }), { prune:
|
|
|
21845
21898
|
process.stdout.write(applySeedNotice(blueprint, args.seed));
|
|
21846
21899
|
const declaredIdentities = Object.entries(blueprint.identities ?? {});
|
|
21847
21900
|
if (declaredIdentities.length > 0) {
|
|
21848
|
-
process.stdout.write(
|
|
21901
|
+
process.stdout.write(`
|
|
21902
|
+
${IDENTITIES_TO_ENSURE_HEADING}
|
|
21903
|
+
`);
|
|
21849
21904
|
for (const [name, decl] of declaredIdentities) {
|
|
21850
21905
|
process.stdout.write(` ~ ${name} (${decl.kind}) \u2014 external id '${decl.externalId}'
|
|
21851
21906
|
`);
|
|
@@ -21978,7 +22033,7 @@ function reportApplyError(err) {
|
|
|
21978
22033
|
If this is expected (a re-apply of a blueprint you manage), re-run with --confirm-existing-principal
|
|
21979
22034
|
(with --yes in a pipeline, where it is standing consent), or confirm at the prompt.
|
|
21980
22035
|
If you don't recognize this identity, investigate the blueprint's servicePrincipal.externalId first.
|
|
21981
|
-
Only the
|
|
22036
|
+
Only the principal lookup had run; no context, grant or anything else was created or changed.
|
|
21982
22037
|
`
|
|
21983
22038
|
);
|
|
21984
22039
|
return 3;
|
|
@@ -22331,10 +22386,14 @@ schemas:
|
|
|
22331
22386
|
accessProfile:
|
|
22332
22387
|
# SCOPE GATE: data-plane only (records/schemas/search/documents/folders/inference/entities),
|
|
22333
22388
|
# PLUS profiles:r/u/d (bounded by the platform to redistributing access you already have,
|
|
22334
|
-
# never granting more)
|
|
22335
|
-
# platform resolves "self" from the caller, not from anything you declare here)
|
|
22336
|
-
#
|
|
22337
|
-
#
|
|
22389
|
+
# never granting more), profiles:c:self (create a profile for yourself only \u2014 the
|
|
22390
|
+
# platform resolves "self" from the caller, not from anything you declare here) and
|
|
22391
|
+
# scripts:x:<script-name> (run one named script that this blueprint ships; there is no bare
|
|
22392
|
+
# or wildcard form).
|
|
22393
|
+
# A bare profiles:c is accepted ONLY when this same clause also declares the
|
|
22394
|
+
# member-lifecycle capability (see "capabilities" below); without it, and always for
|
|
22395
|
+
# profiles:c:usr_<id> naming another principal, it is REJECTED \u2014 no override. Every other
|
|
22396
|
+
# control-plane resource (keys/users/admin/namespaces/triggers/...) and "*" is REJECTED the same way.
|
|
22338
22397
|
# Ops: c|r|u|d, combinable (records:cru). Prefer the narrowest set (least privilege).
|
|
22339
22398
|
allowedActions: [records:r, records:c, records:u, search:r, schemas:r]
|
|
22340
22399
|
# dataScope binds the key to specific owners. Include null to ALSO see
|
|
@@ -22350,8 +22409,10 @@ accessProfile:
|
|
|
22350
22409
|
# above) unless every owner genuinely sharing every record is what you want.
|
|
22351
22410
|
dataScope: {}
|
|
22352
22411
|
# capabilities is a SEPARATE gated dimension from allowedActions \u2014 named platform
|
|
22353
|
-
# effects a resource:ops verb can't express. Only 'member-lifecycle'
|
|
22354
|
-
# today; every other name is
|
|
22412
|
+
# effects a resource:ops verb can't express. Only 'member-lifecycle' and
|
|
22413
|
+
# 'delegate-principal-stamp' are admitted today; every other name is REJECTED, same
|
|
22414
|
+
# posture as the scope gate above. 'member-lifecycle' is also what lets this clause
|
|
22415
|
+
# carry a bare profiles:c.
|
|
22355
22416
|
# capabilities: [member-lifecycle]
|
|
22356
22417
|
|
|
22357
22418
|
servicePrincipal:
|
|
@@ -22438,8 +22499,10 @@ Usage:
|
|
|
22438
22499
|
|
|
22439
22500
|
<file|name> is a path to your own blueprint (YAML .yaml/.yml or JSON .json) OR
|
|
22440
22501
|
the name of a bundled blueprint. The same scope gate that protects bundled
|
|
22441
|
-
blueprints applies to file blueprints
|
|
22442
|
-
|
|
22502
|
+
blueprints applies to file blueprints: beyond data-plane access, a forked or
|
|
22503
|
+
hand-written blueprint can request only a short allowlist of narrow exceptions
|
|
22504
|
+
(profiles:r/u/d, profiles:c:self, a bare profiles:c beside member-lifecycle,
|
|
22505
|
+
scripts:x:<script-name>); every other control-plane scope is rejected.
|
|
22443
22506
|
|
|
22444
22507
|
A blueprint may declare a top-level 'inputs:' block and reference its values
|
|
22445
22508
|
with \${{ inputs.<name> }} (and the built-ins \${{ vectros.context }} /
|
|
@@ -22551,7 +22614,9 @@ ${renderPlan(blueprint, steps)}
|
|
|
22551
22614
|
`);
|
|
22552
22615
|
const declaredIdentities = Object.entries(blueprint.identities ?? {});
|
|
22553
22616
|
if (declaredIdentities.length > 0) {
|
|
22554
|
-
process.stdout.write(
|
|
22617
|
+
process.stdout.write(`
|
|
22618
|
+
${IDENTITIES_TO_ENSURE_HEADING}
|
|
22619
|
+
`);
|
|
22555
22620
|
for (const [name, decl] of declaredIdentities) {
|
|
22556
22621
|
process.stdout.write(` ~ ${name} (${decl.kind}) \u2014 external id '${decl.externalId}'
|
|
22557
22622
|
`);
|
|
@@ -22685,6 +22750,89 @@ ${BLUEPRINT_HELP}
|
|
|
22685
22750
|
// src/program.ts
|
|
22686
22751
|
import { Command, Option } from "commander";
|
|
22687
22752
|
|
|
22753
|
+
// src/dev-api.ts
|
|
22754
|
+
var DeveloperApiError = class extends Error {
|
|
22755
|
+
statusCode;
|
|
22756
|
+
/** The platform's error body, when it answered with one (its message, request id and error code). */
|
|
22757
|
+
body;
|
|
22758
|
+
constructor(message, statusCode, body) {
|
|
22759
|
+
super(message);
|
|
22760
|
+
this.name = "DeveloperApiError";
|
|
22761
|
+
this.statusCode = statusCode;
|
|
22762
|
+
this.body = body;
|
|
22763
|
+
}
|
|
22764
|
+
};
|
|
22765
|
+
async function fetchDeveloperUsersPage(opts) {
|
|
22766
|
+
const { cognitoToken, apiBaseUrl, tenant, startFrom, limit, fetchImpl = fetch } = opts;
|
|
22767
|
+
const base = apiBaseUrl.replace(/\/$/, "");
|
|
22768
|
+
let url = `${base}/developer/users?tenant=${encodeURIComponent(tenant)}`;
|
|
22769
|
+
if (startFrom) url += `&startFrom=${encodeURIComponent(startFrom)}`;
|
|
22770
|
+
if (limit !== void 0) url += `&limit=${encodeURIComponent(String(limit))}`;
|
|
22771
|
+
let res;
|
|
22772
|
+
try {
|
|
22773
|
+
res = await fetchImpl(url, {
|
|
22774
|
+
method: "GET",
|
|
22775
|
+
headers: { Authorization: `Bearer ${cognitoToken}` }
|
|
22776
|
+
});
|
|
22777
|
+
} catch (err) {
|
|
22778
|
+
throw new DeveloperApiError(
|
|
22779
|
+
`Request to /developer/users failed: ${err instanceof Error ? err.message : String(err)}`
|
|
22780
|
+
);
|
|
22781
|
+
}
|
|
22782
|
+
if (!res.ok) {
|
|
22783
|
+
const body = await res.text().catch(() => "");
|
|
22784
|
+
const hint = res.status === 403 ? " (this command requires an account owner login \u2014 sign in as the owner and retry)" : "";
|
|
22785
|
+
throw new DeveloperApiError(
|
|
22786
|
+
`GET /developer/users failed: HTTP ${res.status}${hint}${body ? ` \u2014 ${body}` : ""}`,
|
|
22787
|
+
res.status
|
|
22788
|
+
);
|
|
22789
|
+
}
|
|
22790
|
+
let parsed;
|
|
22791
|
+
try {
|
|
22792
|
+
parsed = await res.json();
|
|
22793
|
+
} catch {
|
|
22794
|
+
throw new DeveloperApiError("GET /developer/users returned a non-JSON body");
|
|
22795
|
+
}
|
|
22796
|
+
const data = Array.isArray(parsed.data) ? parsed.data : [];
|
|
22797
|
+
const nextCursor = typeof parsed.nextCursor === "string" ? parsed.nextCursor : null;
|
|
22798
|
+
return { data, nextCursor };
|
|
22799
|
+
}
|
|
22800
|
+
async function fetchDeveloperMe(opts) {
|
|
22801
|
+
const { cognitoToken, apiBaseUrl, tenant, fetchImpl = fetch } = opts;
|
|
22802
|
+
const url = `${apiBaseUrl.replace(/\/$/, "")}/developer/me?tenant=${encodeURIComponent(tenant)}`;
|
|
22803
|
+
let res;
|
|
22804
|
+
try {
|
|
22805
|
+
res = await fetchImpl(url, { method: "GET", headers: { Authorization: `Bearer ${cognitoToken}` } });
|
|
22806
|
+
} catch (err) {
|
|
22807
|
+
throw new DeveloperApiError(
|
|
22808
|
+
`Request to /developer/me failed: ${err instanceof Error ? err.message : String(err)}`
|
|
22809
|
+
);
|
|
22810
|
+
}
|
|
22811
|
+
if (res.status === 404) return null;
|
|
22812
|
+
if (!res.ok) {
|
|
22813
|
+
const failure = await devRouteError(res, "GET /developer/me");
|
|
22814
|
+
throw new DeveloperApiError(
|
|
22815
|
+
`GET /developer/me failed: HTTP ${res.status}`,
|
|
22816
|
+
res.status,
|
|
22817
|
+
failure.body
|
|
22818
|
+
);
|
|
22819
|
+
}
|
|
22820
|
+
let parsed;
|
|
22821
|
+
try {
|
|
22822
|
+
parsed = await res.json();
|
|
22823
|
+
} catch {
|
|
22824
|
+
throw new DeveloperApiError("GET /developer/me returned a non-JSON body");
|
|
22825
|
+
}
|
|
22826
|
+
if (typeof parsed.principalId !== "string" || typeof parsed.userId !== "string") {
|
|
22827
|
+
throw new DeveloperApiError("GET /developer/me returned no principal");
|
|
22828
|
+
}
|
|
22829
|
+
return {
|
|
22830
|
+
principalId: parsed.principalId,
|
|
22831
|
+
userId: parsed.userId,
|
|
22832
|
+
tenantId: typeof parsed.tenantId === "string" ? parsed.tenantId : void 0
|
|
22833
|
+
};
|
|
22834
|
+
}
|
|
22835
|
+
|
|
22688
22836
|
// src/self-principal.ts
|
|
22689
22837
|
var PRINCIPAL_PREFIX_USER = "usr_";
|
|
22690
22838
|
function emailFromJwt(token) {
|
|
@@ -22713,6 +22861,37 @@ async function resolveSelfPrincipal(client, cognitoToken) {
|
|
|
22713
22861
|
if (!match?.id) return null;
|
|
22714
22862
|
return { principalId: `${PRINCIPAL_PREFIX_USER}${match.id}`, userId: match.id, email };
|
|
22715
22863
|
}
|
|
22864
|
+
async function resolveSelfPrincipalForTenant(auth, fetchImpl) {
|
|
22865
|
+
const tenant = auth.tenant ?? "live";
|
|
22866
|
+
let me;
|
|
22867
|
+
try {
|
|
22868
|
+
me = await fetchDeveloperMe({
|
|
22869
|
+
cognitoToken: auth.cognitoToken,
|
|
22870
|
+
apiBaseUrl: auth.baseUrl,
|
|
22871
|
+
tenant,
|
|
22872
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
22873
|
+
});
|
|
22874
|
+
} catch (err) {
|
|
22875
|
+
if (err instanceof DeveloperApiError && err.statusCode !== void 0) {
|
|
22876
|
+
if (err.body?.errorCode === "SESSION_REFRESH_REQUIRED") throw new SessionExpiredError(auth.env ?? "production");
|
|
22877
|
+
throw err;
|
|
22878
|
+
}
|
|
22879
|
+
if (tenant === "test") throw err;
|
|
22880
|
+
return resolveSelfPrincipal(auth.client, auth.cognitoToken);
|
|
22881
|
+
}
|
|
22882
|
+
if (me === null) return null;
|
|
22883
|
+
const forThisTenant = Boolean(auth.tenantId && me.tenantId && me.tenantId === auth.tenantId);
|
|
22884
|
+
if (forThisTenant && me.principalId.startsWith(PRINCIPAL_PREFIX_USER)) {
|
|
22885
|
+
return { principalId: me.principalId, userId: me.userId, email: emailFromJwt(auth.cognitoToken) ?? "" };
|
|
22886
|
+
}
|
|
22887
|
+
return tenant === "live" ? resolveSelfPrincipal(auth.client, auth.cognitoToken) : null;
|
|
22888
|
+
}
|
|
22889
|
+
function selfPrincipalUnresolvedMessage(tenant, flag) {
|
|
22890
|
+
if (tenant === "test") {
|
|
22891
|
+
return `Could not resolve your own user id in the test tenant: the platform did not report one for this login. Find it with \`vectros identity list-all --tenant test\` (an owner login) and pass ${flag} usr_<id> explicitly.`;
|
|
22892
|
+
}
|
|
22893
|
+
return `Could not resolve your own user id from your login. Pass ${flag} usr_<id> explicitly (find the id with \`vectros identity list --type user\`).`;
|
|
22894
|
+
}
|
|
22716
22895
|
|
|
22717
22896
|
// src/commands/whoami.ts
|
|
22718
22897
|
async function runWhoami(options, deps = {}) {
|
|
@@ -22720,7 +22899,10 @@ async function runWhoami(options, deps = {}) {
|
|
|
22720
22899
|
const { identity, baseUrl } = auth;
|
|
22721
22900
|
let self2 = null;
|
|
22722
22901
|
try {
|
|
22723
|
-
self2 = await
|
|
22902
|
+
self2 = await resolveSelfPrincipalForTenant(
|
|
22903
|
+
{ ...auth, client: auth.client },
|
|
22904
|
+
deps.fetchImpl
|
|
22905
|
+
);
|
|
22724
22906
|
} catch {
|
|
22725
22907
|
self2 = null;
|
|
22726
22908
|
}
|
|
@@ -23126,51 +23308,6 @@ ${list}
|
|
|
23126
23308
|
});
|
|
23127
23309
|
}
|
|
23128
23310
|
|
|
23129
|
-
// src/dev-api.ts
|
|
23130
|
-
var DeveloperApiError = class extends Error {
|
|
23131
|
-
statusCode;
|
|
23132
|
-
constructor(message, statusCode) {
|
|
23133
|
-
super(message);
|
|
23134
|
-
this.name = "DeveloperApiError";
|
|
23135
|
-
this.statusCode = statusCode;
|
|
23136
|
-
}
|
|
23137
|
-
};
|
|
23138
|
-
async function fetchDeveloperUsersPage(opts) {
|
|
23139
|
-
const { cognitoToken, apiBaseUrl, tenant, startFrom, limit, fetchImpl = fetch } = opts;
|
|
23140
|
-
const base = apiBaseUrl.replace(/\/$/, "");
|
|
23141
|
-
let url = `${base}/developer/users?tenant=${encodeURIComponent(tenant)}`;
|
|
23142
|
-
if (startFrom) url += `&startFrom=${encodeURIComponent(startFrom)}`;
|
|
23143
|
-
if (limit !== void 0) url += `&limit=${encodeURIComponent(String(limit))}`;
|
|
23144
|
-
let res;
|
|
23145
|
-
try {
|
|
23146
|
-
res = await fetchImpl(url, {
|
|
23147
|
-
method: "GET",
|
|
23148
|
-
headers: { Authorization: `Bearer ${cognitoToken}` }
|
|
23149
|
-
});
|
|
23150
|
-
} catch (err) {
|
|
23151
|
-
throw new DeveloperApiError(
|
|
23152
|
-
`Request to /developer/users failed: ${err instanceof Error ? err.message : String(err)}`
|
|
23153
|
-
);
|
|
23154
|
-
}
|
|
23155
|
-
if (!res.ok) {
|
|
23156
|
-
const body = await res.text().catch(() => "");
|
|
23157
|
-
const hint = res.status === 403 ? " (this command requires an account owner login \u2014 sign in as the owner and retry)" : "";
|
|
23158
|
-
throw new DeveloperApiError(
|
|
23159
|
-
`GET /developer/users failed: HTTP ${res.status}${hint}${body ? ` \u2014 ${body}` : ""}`,
|
|
23160
|
-
res.status
|
|
23161
|
-
);
|
|
23162
|
-
}
|
|
23163
|
-
let parsed;
|
|
23164
|
-
try {
|
|
23165
|
-
parsed = await res.json();
|
|
23166
|
-
} catch {
|
|
23167
|
-
throw new DeveloperApiError("GET /developer/users returned a non-JSON body");
|
|
23168
|
-
}
|
|
23169
|
-
const data = Array.isArray(parsed.data) ? parsed.data : [];
|
|
23170
|
-
const nextCursor = typeof parsed.nextCursor === "string" ? parsed.nextCursor : null;
|
|
23171
|
-
return { data, nextCursor };
|
|
23172
|
-
}
|
|
23173
|
-
|
|
23174
23311
|
// src/commands/identity.ts
|
|
23175
23312
|
function isUserDimension(type) {
|
|
23176
23313
|
return type === "user";
|
|
@@ -23466,14 +23603,13 @@ function assertPrincipalShape(principal) {
|
|
|
23466
23603
|
}
|
|
23467
23604
|
}
|
|
23468
23605
|
var SELF_PRINCIPAL_ALIAS = "me";
|
|
23469
|
-
async function resolvePrincipalArg(raw, auth) {
|
|
23606
|
+
async function resolvePrincipalArg(raw, auth, deps) {
|
|
23470
23607
|
if (raw !== SELF_PRINCIPAL_ALIAS) return raw;
|
|
23471
|
-
const self2 = await
|
|
23472
|
-
|
|
23473
|
-
|
|
23474
|
-
|
|
23475
|
-
|
|
23476
|
-
}
|
|
23608
|
+
const self2 = await resolveSelfPrincipalForTenant(
|
|
23609
|
+
{ ...auth, client: auth.client },
|
|
23610
|
+
deps.fetchImpl
|
|
23611
|
+
);
|
|
23612
|
+
if (!self2) throw new Error(selfPrincipalUnresolvedMessage(auth.tenant, "--principal"));
|
|
23477
23613
|
return self2.principalId;
|
|
23478
23614
|
}
|
|
23479
23615
|
function roleSummary(p) {
|
|
@@ -23526,7 +23662,7 @@ async function runAccessGrant(opts, deps = {}) {
|
|
|
23526
23662
|
return withAuth(opts, deps, async (auth) => {
|
|
23527
23663
|
const client = auth.client;
|
|
23528
23664
|
try {
|
|
23529
|
-
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
23665
|
+
const principal = await resolvePrincipalArg(opts.principal, auth, deps);
|
|
23530
23666
|
const scopes = hasActions ? [{ allowed_actions: splitCsv(opts.actions) }] : void 0;
|
|
23531
23667
|
const profile = await client.auth.createAccessProfile({
|
|
23532
23668
|
contextId: opts.context,
|
|
@@ -23536,8 +23672,10 @@ async function runAccessGrant(opts, deps = {}) {
|
|
|
23536
23672
|
// Always send the plural `roleIds`, even for a single
|
|
23537
23673
|
// `--role`: it is a strict superset of the deprecated singular
|
|
23538
23674
|
// `roleId` (equivalent to `roleIds: ["<value>"]`), and the platform
|
|
23539
|
-
// reads it uniformly regardless of how many roles compose.
|
|
23540
|
-
|
|
23675
|
+
// reads it uniformly regardless of how many roles compose. With
|
|
23676
|
+
// `--actions` there is no role: omit `roleIds` (the platform refuses an
|
|
23677
|
+
// empty list next to inline `scopes`).
|
|
23678
|
+
roleIds: hasRole ? opts.role : void 0,
|
|
23541
23679
|
scopes,
|
|
23542
23680
|
identityOverrides
|
|
23543
23681
|
}
|
|
@@ -23574,7 +23712,7 @@ async function runAccessRevoke(opts, deps = {}) {
|
|
|
23574
23712
|
return withAuth(opts, deps, async (auth) => {
|
|
23575
23713
|
const client = auth.client;
|
|
23576
23714
|
try {
|
|
23577
|
-
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
23715
|
+
const principal = await resolvePrincipalArg(opts.principal, auth, deps);
|
|
23578
23716
|
await client.auth.deleteAccessProfile({ contextId: opts.context, principalId: principal });
|
|
23579
23717
|
process.stdout.write(` \u2714 Revoked ${principal} from '${opts.context}'
|
|
23580
23718
|
`);
|
|
@@ -23603,7 +23741,7 @@ async function runAccessList(opts, deps = {}) {
|
|
|
23603
23741
|
return withAuth(opts, deps, async (auth) => {
|
|
23604
23742
|
const client = auth.client;
|
|
23605
23743
|
try {
|
|
23606
|
-
const principal = opts.principal ? await resolvePrincipalArg(opts.principal, auth) : void 0;
|
|
23744
|
+
const principal = opts.principal ? await resolvePrincipalArg(opts.principal, auth, deps) : void 0;
|
|
23607
23745
|
const rows = opts.context ? await drainPages(
|
|
23608
23746
|
(startFrom) => client.auth.listAccessProfiles({ contextId: opts.context, startFrom, limit: PAGE_LIMIT })
|
|
23609
23747
|
) : await drainPages(
|
|
@@ -23643,7 +23781,7 @@ async function runAccessGet(opts, deps = {}) {
|
|
|
23643
23781
|
return withAuth(opts, deps, async (auth) => {
|
|
23644
23782
|
const client = auth.client;
|
|
23645
23783
|
try {
|
|
23646
|
-
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
23784
|
+
const principal = await resolvePrincipalArg(opts.principal, auth, deps);
|
|
23647
23785
|
const p = await client.auth.getAccessProfile({ contextId: opts.context, principalId: principal });
|
|
23648
23786
|
if (opts.json) {
|
|
23649
23787
|
process.stdout.write(`${formatJson(p)}
|
|
@@ -23865,11 +24003,13 @@ async function runAccessExplain(opts, deps = {}) {
|
|
|
23865
24003
|
try {
|
|
23866
24004
|
let principal = opts.principal;
|
|
23867
24005
|
if (principal === SELF_PRINCIPAL_ALIAS2) {
|
|
23868
|
-
const self2 = await
|
|
24006
|
+
const self2 = await resolveSelfPrincipalForTenant(
|
|
24007
|
+
{ ...auth, client: auth.client },
|
|
24008
|
+
deps.fetchImpl
|
|
24009
|
+
);
|
|
23869
24010
|
if (!self2) {
|
|
23870
|
-
process.stderr.write(
|
|
23871
|
-
|
|
23872
|
-
);
|
|
24011
|
+
process.stderr.write(` \u2716 ${selfPrincipalUnresolvedMessage(auth.tenant, "--principal")}
|
|
24012
|
+
`);
|
|
23873
24013
|
return 1;
|
|
23874
24014
|
}
|
|
23875
24015
|
principal = self2.principalId;
|
|
@@ -24248,10 +24388,17 @@ async function runContextDestroy(opts, deps = {}) {
|
|
|
24248
24388
|
`);
|
|
24249
24389
|
return 0;
|
|
24250
24390
|
}
|
|
24391
|
+
const tenantFlag = auth.tenant === "test" ? " --tenant test" : "";
|
|
24251
24392
|
process.stdout.write(
|
|
24252
24393
|
`
|
|
24253
24394
|
\u2714 teardown of '${contextId}' started \u2014 the context and its contents are being
|
|
24254
24395
|
deleted asynchronously (it reports status 'purging' until the drain completes)
|
|
24396
|
+
|
|
24397
|
+
Not removed: a service principal a blueprint bootstrap created for this context (the
|
|
24398
|
+
externalId in the blueprint's servicePrincipal) belongs to the whole tenant, so it stays. To clean it up
|
|
24399
|
+
when it exists and nothing else uses it:
|
|
24400
|
+
vectros identity list-all${tenantFlag}
|
|
24401
|
+
vectros identity delete --type user --id <id>${tenantFlag}
|
|
24255
24402
|
`
|
|
24256
24403
|
);
|
|
24257
24404
|
return 0;
|
|
@@ -24790,7 +24937,7 @@ ${renderIssuer(issuer)}
|
|
|
24790
24937
|
import { createReadStream as createReadStream2 } from "fs";
|
|
24791
24938
|
import * as readline6 from "readline/promises";
|
|
24792
24939
|
var PROVIDER_TYPES = ["ANTHROPIC_NATIVE", "OPENAI_COMPATIBLE"];
|
|
24793
|
-
var BILLING_MODES = ["
|
|
24940
|
+
var BILLING_MODES = ["CUSTOMER_DIRECT"];
|
|
24794
24941
|
var STATUSES = ["ACTIVE", "DISABLED"];
|
|
24795
24942
|
var MAX_SECRET_BYTES = 8 * 1024;
|
|
24796
24943
|
async function readBoundedSecret(stream) {
|
|
@@ -24869,12 +25016,17 @@ function renderProviderConfig(c) {
|
|
|
24869
25016
|
["defaultProviderModelId", c.defaultProviderModelId],
|
|
24870
25017
|
["waiverType", c.waiverType],
|
|
24871
25018
|
["billingMode", c.billingMode],
|
|
24872
|
-
["platformTokenFactor", c.platformTokenFactor],
|
|
24873
|
-
["platformOutputWeight", c.platformOutputWeight],
|
|
24874
25019
|
["status", c.status],
|
|
24875
25020
|
["createdAt", c.createdAt]
|
|
24876
25021
|
]);
|
|
24877
25022
|
}
|
|
25023
|
+
function billingModeError(value) {
|
|
25024
|
+
if (value === void 0 || BILLING_MODES.includes(value)) return void 0;
|
|
25025
|
+
if (value === "PLATFORM_BILLED") {
|
|
25026
|
+
return "--billing-mode PLATFORM_BILLED is retired: a provider you configure bills you directly, so CUSTOMER_DIRECT is the only billing mode.";
|
|
25027
|
+
}
|
|
25028
|
+
return invalidEnum("--billing-mode", value, BILLING_MODES);
|
|
25029
|
+
}
|
|
24878
25030
|
function invalidEnum(flag, value, allowed) {
|
|
24879
25031
|
return `${flag} must be one of ${allowed.join(", ")} (got '${value}').`;
|
|
24880
25032
|
}
|
|
@@ -24885,8 +25037,9 @@ async function runProviderConfigCreate(opts, deps = {}) {
|
|
|
24885
25037
|
`);
|
|
24886
25038
|
return 2;
|
|
24887
25039
|
}
|
|
24888
|
-
|
|
24889
|
-
|
|
25040
|
+
const billingModeProblem = billingModeError(opts.billingMode);
|
|
25041
|
+
if (billingModeProblem !== void 0) {
|
|
25042
|
+
process.stderr.write(` \u2716 ${billingModeProblem}
|
|
24890
25043
|
`);
|
|
24891
25044
|
return 2;
|
|
24892
25045
|
}
|
|
@@ -24895,19 +25048,6 @@ async function runProviderConfigCreate(opts, deps = {}) {
|
|
|
24895
25048
|
`);
|
|
24896
25049
|
return 2;
|
|
24897
25050
|
}
|
|
24898
|
-
if (opts.billingMode === "PLATFORM_BILLED") {
|
|
24899
|
-
if (opts.platformTokenFactor === void 0 || opts.platformOutputWeight === void 0) {
|
|
24900
|
-
process.stderr.write(
|
|
24901
|
-
" \u2716 --platform-token-factor and --platform-output-weight are required when --billing-mode PLATFORM_BILLED.\n"
|
|
24902
|
-
);
|
|
24903
|
-
return 2;
|
|
24904
|
-
}
|
|
24905
|
-
} else if (opts.platformTokenFactor !== void 0 || opts.platformOutputWeight !== void 0) {
|
|
24906
|
-
process.stderr.write(
|
|
24907
|
-
" \u2716 --platform-token-factor/--platform-output-weight only apply with --billing-mode PLATFORM_BILLED.\n"
|
|
24908
|
-
);
|
|
24909
|
-
return 2;
|
|
24910
|
-
}
|
|
24911
25051
|
let apiKeySecret;
|
|
24912
25052
|
try {
|
|
24913
25053
|
apiKeySecret = await resolveApiKeySecret(opts.apiKeySecretFile, deps, "required");
|
|
@@ -24932,9 +25072,7 @@ async function runProviderConfigCreate(opts, deps = {}) {
|
|
|
24932
25072
|
defaultProviderModelId: opts.defaultProviderModelId,
|
|
24933
25073
|
apiKeySecret,
|
|
24934
25074
|
waiverType: opts.waiverType,
|
|
24935
|
-
billingMode: opts.billingMode,
|
|
24936
|
-
platformTokenFactor: opts.platformTokenFactor,
|
|
24937
|
-
platformOutputWeight: opts.platformOutputWeight,
|
|
25075
|
+
billingMode: opts.billingMode ?? "CUSTOMER_DIRECT",
|
|
24938
25076
|
status: opts.status
|
|
24939
25077
|
})
|
|
24940
25078
|
});
|
|
@@ -25015,8 +25153,9 @@ async function runProviderConfigUpdate(opts, deps = {}) {
|
|
|
25015
25153
|
`);
|
|
25016
25154
|
return 2;
|
|
25017
25155
|
}
|
|
25018
|
-
|
|
25019
|
-
|
|
25156
|
+
const billingModeProblem = billingModeError(opts.billingMode);
|
|
25157
|
+
if (billingModeProblem !== void 0) {
|
|
25158
|
+
process.stderr.write(` \u2716 ${billingModeProblem}
|
|
25020
25159
|
`);
|
|
25021
25160
|
return 2;
|
|
25022
25161
|
}
|
|
@@ -25049,8 +25188,6 @@ async function runProviderConfigUpdate(opts, deps = {}) {
|
|
|
25049
25188
|
apiKeySecret,
|
|
25050
25189
|
waiverType: opts.waiverType,
|
|
25051
25190
|
billingMode: opts.billingMode,
|
|
25052
|
-
platformTokenFactor: opts.platformTokenFactor,
|
|
25053
|
-
platformOutputWeight: opts.platformOutputWeight,
|
|
25054
25191
|
status: opts.status
|
|
25055
25192
|
})
|
|
25056
25193
|
});
|
|
@@ -26627,15 +26764,14 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
|
|
|
26627
26764
|
|
|
26628
26765
|
// src/commands/join.ts
|
|
26629
26766
|
var SELF_ALIAS = "me";
|
|
26630
|
-
async function resolvePrincipal(as, auth) {
|
|
26767
|
+
async function resolvePrincipal(as, auth, deps) {
|
|
26631
26768
|
const raw = as ?? SELF_ALIAS;
|
|
26632
26769
|
if (raw === SELF_ALIAS) {
|
|
26633
|
-
const self2 = await
|
|
26634
|
-
|
|
26635
|
-
|
|
26636
|
-
|
|
26637
|
-
|
|
26638
|
-
}
|
|
26770
|
+
const self2 = await resolveSelfPrincipalForTenant(
|
|
26771
|
+
{ ...auth, client: auth.client },
|
|
26772
|
+
deps.fetchImpl
|
|
26773
|
+
);
|
|
26774
|
+
if (!self2) throw new Error(selfPrincipalUnresolvedMessage(auth.tenant, "--as"));
|
|
26639
26775
|
return self2.principalId;
|
|
26640
26776
|
}
|
|
26641
26777
|
if (!raw.startsWith("usr_")) {
|
|
@@ -26705,7 +26841,7 @@ async function runJoin(opts, deps = {}) {
|
|
|
26705
26841
|
const client = auth.client;
|
|
26706
26842
|
try {
|
|
26707
26843
|
const tenantId = requireTenantId(auth);
|
|
26708
|
-
const principalId = await resolvePrincipal(opts.as, auth);
|
|
26844
|
+
const principalId = await resolvePrincipal(opts.as, auth, deps);
|
|
26709
26845
|
const userId = normalizePrincipalUserId(principalId);
|
|
26710
26846
|
const scopes = hasActions ? [{ allowed_actions: splitCsv(opts.actions) }] : void 0;
|
|
26711
26847
|
await client.auth.createAccessProfile({
|
|
@@ -26716,7 +26852,9 @@ async function runJoin(opts, deps = {}) {
|
|
|
26716
26852
|
// Always send the plural `roleIds`, even for a single
|
|
26717
26853
|
// `--role`: a strict superset of the deprecated singular `roleId`,
|
|
26718
26854
|
// read uniformly by the platform regardless of how many compose.
|
|
26719
|
-
|
|
26855
|
+
// With `--actions` there is no role: omit `roleIds` (the platform
|
|
26856
|
+
// refuses an empty list next to inline `scopes`).
|
|
26857
|
+
roleIds: hasRole ? opts.role : void 0,
|
|
26720
26858
|
scopes,
|
|
26721
26859
|
identityOverrides
|
|
26722
26860
|
}
|
|
@@ -26803,9 +26941,9 @@ function emitJoined(opts) {
|
|
|
26803
26941
|
|
|
26804
26942
|
// src/build-info.ts
|
|
26805
26943
|
var BUILD_INFO = {
|
|
26806
|
-
cli: true ? "0.
|
|
26807
|
-
sdk: true ? "0.
|
|
26808
|
-
blueprints: true ? "0.20.
|
|
26944
|
+
cli: true ? "0.25.0" : "dev",
|
|
26945
|
+
sdk: true ? "0.47.0" : "dev",
|
|
26946
|
+
blueprints: true ? "0.20.2" : "dev"
|
|
26809
26947
|
};
|
|
26810
26948
|
function formatBuildInfo() {
|
|
26811
26949
|
return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
|
|
@@ -26842,18 +26980,6 @@ function parseTenantFlag(raw) {
|
|
|
26842
26980
|
}
|
|
26843
26981
|
return raw;
|
|
26844
26982
|
}
|
|
26845
|
-
var NUMBER_FLAG_INVALID = /* @__PURE__ */ Symbol("parseOptionalNumberFlag:invalid");
|
|
26846
|
-
function parseOptionalNumberFlag(flag, raw) {
|
|
26847
|
-
if (raw === void 0) return void 0;
|
|
26848
|
-
const n = Number(raw);
|
|
26849
|
-
if (!Number.isFinite(n)) {
|
|
26850
|
-
process.stderr.write(` \u2716 ${flag} must be a number (got '${raw}')
|
|
26851
|
-
`);
|
|
26852
|
-
process.exitCode = 2;
|
|
26853
|
-
return NUMBER_FLAG_INVALID;
|
|
26854
|
-
}
|
|
26855
|
-
return n;
|
|
26856
|
-
}
|
|
26857
26983
|
function toCommonOptions(flags) {
|
|
26858
26984
|
return {
|
|
26859
26985
|
env: flags.env,
|
|
@@ -27025,7 +27151,7 @@ function registerContextCommands(program, deps) {
|
|
|
27025
27151
|
);
|
|
27026
27152
|
});
|
|
27027
27153
|
const destroy = context.command("destroy").description(
|
|
27028
|
-
"Permanently destroy an app context and ALL of its data (irreversible; asks for confirmation)."
|
|
27154
|
+
"Permanently destroy an app context and ALL of its data (irreversible; asks for confirmation). A blueprint's service principal belongs to the whole tenant and is NOT removed: delete it with 'identity delete'."
|
|
27029
27155
|
).argument("<contextId>", "the app context to destroy").option("--force", "skip the interactive confirmation (required when stdin is not a TTY)").option("--tenant <tenant>", "tenant the context lives in: 'test' or 'live' (default live)");
|
|
27030
27156
|
addCommonAuthOptions(destroy).action(
|
|
27031
27157
|
async (contextId, flags) => {
|
|
@@ -27180,15 +27306,11 @@ function registerIssuersCommands(program, deps) {
|
|
|
27180
27306
|
}
|
|
27181
27307
|
function registerProviderConfigCommands(program, deps) {
|
|
27182
27308
|
const providerConfig = program.command("provider-config").description("Manage BYOM (bring-your-own-model) tenant-level provider configs.");
|
|
27183
|
-
const create = providerConfig.command("create").description("Create a new BYOM provider config (not idempotent \u2014 a repeat --alias 400s).").requiredOption("--alias <alias>", "operator-facing name used to select this config; unique per tenant").requiredOption("--provider-type <type>", "'ANTHROPIC_NATIVE' or 'OPENAI_COMPATIBLE'").option("--endpoint <url>", "required for OPENAI_COMPATIBLE (no default); an optional override for ANTHROPIC_NATIVE").option("--declared-processing-region <region>", "customer-asserted, free-text, Vectros-unverified transparency field").option("--default-provider-model-id <id>", "convenience default only \u2014 a per-request model id, when the caller supplies one, always takes precedence").requiredOption("--api-key-secret-file <path>", "path to a file holding the provider credential ('-' for stdin) \u2014 never pass the secret as a bare flag value").requiredOption("--waiver-type <type>", "the waiver code this config is entitled under
|
|
27309
|
+
const create = providerConfig.command("create").description("Create a new BYOM provider config (not idempotent \u2014 a repeat --alias 400s).").requiredOption("--alias <alias>", "operator-facing name used to select this config; unique per tenant; 1-64 lowercase letters, digits, - or _").requiredOption("--provider-type <type>", "'ANTHROPIC_NATIVE' or 'OPENAI_COMPATIBLE'").option("--endpoint <url>", "required for OPENAI_COMPATIBLE (no default); an optional override for ANTHROPIC_NATIVE; https on port 443").option("--declared-processing-region <region>", "customer-asserted, free-text, Vectros-unverified transparency field").option("--default-provider-model-id <id>", "convenience default only \u2014 a per-request model id, when the caller supplies one, always takes precedence").requiredOption("--api-key-secret-file <path>", "path to a file holding the provider credential ('-' for stdin) \u2014 never pass the secret as a bare flag value").requiredOption("--waiver-type <type>", "the waiver code this config is entitled under; the platform accepts byo-model-inference:customer-key for a config you set up yourself").option("--billing-mode <mode>", "only 'CUSTOMER_DIRECT' (your provider bills you), which is also the default; 'PLATFORM_BILLED' is retired").option("--status <status>", "'ACTIVE' or 'DISABLED' (default ACTIVE) \u2014 ACTIVE requires the tenant already hold a granted waiver for --waiver-type").option("--tenant <tenant>", "tenant to create the config in: 'test' or 'live' (default live)");
|
|
27184
27310
|
addCommonAuthOptions(create).action(
|
|
27185
27311
|
async (flags) => {
|
|
27186
27312
|
const tenant = parseTenantFlag(flags.tenant);
|
|
27187
27313
|
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27188
|
-
const platformTokenFactor = parseOptionalNumberFlag("--platform-token-factor", flags.platformTokenFactor);
|
|
27189
|
-
if (platformTokenFactor === NUMBER_FLAG_INVALID) return;
|
|
27190
|
-
const platformOutputWeight = parseOptionalNumberFlag("--platform-output-weight", flags.platformOutputWeight);
|
|
27191
|
-
if (platformOutputWeight === NUMBER_FLAG_INVALID) return;
|
|
27192
27314
|
process.exitCode = await runProviderConfigCreate(
|
|
27193
27315
|
{
|
|
27194
27316
|
...toCommonOptions(flags),
|
|
@@ -27200,8 +27322,6 @@ function registerProviderConfigCommands(program, deps) {
|
|
|
27200
27322
|
apiKeySecretFile: flags.apiKeySecretFile,
|
|
27201
27323
|
waiverType: flags.waiverType,
|
|
27202
27324
|
billingMode: flags.billingMode,
|
|
27203
|
-
platformTokenFactor,
|
|
27204
|
-
platformOutputWeight,
|
|
27205
27325
|
status: flags.status,
|
|
27206
27326
|
tenant
|
|
27207
27327
|
},
|
|
@@ -27219,13 +27339,9 @@ function registerProviderConfigCommands(program, deps) {
|
|
|
27219
27339
|
addCommonAuthOptions(get).action(async (id, flags) => {
|
|
27220
27340
|
process.exitCode = await runProviderConfigGet({ ...toCommonOptions(flags), id }, deps);
|
|
27221
27341
|
});
|
|
27222
|
-
const update = providerConfig.command("update").description("Patch a provider config (partial update \u2014 an omitted field is left unchanged).").argument("<id>").option("--alias <alias>", "rename this config (
|
|
27342
|
+
const update = providerConfig.command("update").description("Patch a provider config (partial update \u2014 an omitted field is left unchanged).").argument("<id>").option("--alias <alias>", "rename this config (unique per tenant); the platform refuses a new alias that is not 1-64 lowercase letters, digits, - or _ (an alias the config already holds is accepted)").option("--provider-type <type>", "'ANTHROPIC_NATIVE' or 'OPENAI_COMPATIBLE'; changing the type of an existing config needs --endpoint stated in the same command (--endpoint '' means the provider's default), or the platform answers 400").option("--endpoint <url>", "update the provider endpoint; https on port 443; changing the host needs --api-key-secret-file again; '' clears it (the provider's default), which also changes the host").option("--declared-processing-region <region>", "update the transparency field").option("--default-provider-model-id <id>", "update the convenience default model id").option("--api-key-secret-file <path>", "rotate the provider credential from a file ('-' for stdin); omit to leave the stored secret unchanged (required when --endpoint changes the host)").option("--waiver-type <type>", "update the waiver code this config is entitled under; the platform refuses a change to anything but byo-model-inference:customer-key (a value the config already holds is accepted)").option("--billing-mode <mode>", "only 'CUSTOMER_DIRECT' (your provider bills you); 'PLATFORM_BILLED' is retired. Omit it to leave the stored mode unchanged").option("--status <status>", "'ACTIVE' or 'DISABLED' \u2014 activating requires a granted waiver for this config's waiverType");
|
|
27223
27343
|
addCommonAuthOptions(update).action(
|
|
27224
27344
|
async (id, flags) => {
|
|
27225
|
-
const platformTokenFactor = parseOptionalNumberFlag("--platform-token-factor", flags.platformTokenFactor);
|
|
27226
|
-
if (platformTokenFactor === NUMBER_FLAG_INVALID) return;
|
|
27227
|
-
const platformOutputWeight = parseOptionalNumberFlag("--platform-output-weight", flags.platformOutputWeight);
|
|
27228
|
-
if (platformOutputWeight === NUMBER_FLAG_INVALID) return;
|
|
27229
27345
|
process.exitCode = await runProviderConfigUpdate(
|
|
27230
27346
|
{
|
|
27231
27347
|
...toCommonOptions(flags),
|
|
@@ -27238,8 +27354,6 @@ function registerProviderConfigCommands(program, deps) {
|
|
|
27238
27354
|
apiKeySecretFile: flags.apiKeySecretFile,
|
|
27239
27355
|
waiverType: flags.waiverType,
|
|
27240
27356
|
billingMode: flags.billingMode,
|
|
27241
|
-
platformTokenFactor,
|
|
27242
|
-
platformOutputWeight,
|
|
27243
27357
|
status: flags.status
|
|
27244
27358
|
},
|
|
27245
27359
|
deps
|
|
@@ -27507,12 +27621,15 @@ function dimensionOption() {
|
|
|
27507
27621
|
).makeOptionMandatory();
|
|
27508
27622
|
}
|
|
27509
27623
|
function registerIdentityCommands(program, deps) {
|
|
27510
|
-
const identity = program.command("identity").description("Manage tenant-wide
|
|
27511
|
-
const create = identity.command("create").description("Create (idempotent by externalId) a principal.").addOption(dimensionOption()).requiredOption("--external-id <id>", "your stable id for the principal (idempotency key)").option("--name <name>", "display name (entity namespaces only, not --type user)").option("--email <email>", "email (user only)").option("--service", "user only: mint a SERVICE principal (default HUMAN)").option("--scope <ns:value>", "entity only: a parent ownership edge (repeatable, \u22642, e.g. org:<id>)", collect, []).option("--metadata <json>", "JSON object of metadata");
|
|
27624
|
+
const identity = program.command("identity").description("Manage principals: users (tenant-wide) and namespaced entities (an entity lives with its namespace) \u2014 reference, never embed.");
|
|
27625
|
+
const create = identity.command("create").description("Create (idempotent by externalId) a principal.").addOption(dimensionOption()).requiredOption("--external-id <id>", "your stable id for the principal (idempotency key)").option("--name <name>", "display name (entity namespaces only, not --type user)").option("--email <email>", "email (user only)").option("--service", "user only: mint a SERVICE principal (default HUMAN)").option("--scope <ns:value>", "entity only: a parent ownership edge (repeatable, \u22642, e.g. org:<id>)", collect, []).option("--metadata <json>", "JSON object of metadata").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
27512
27626
|
addCommonAuthOptions(create).action(
|
|
27513
27627
|
async (flags) => {
|
|
27628
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27629
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27514
27630
|
process.exitCode = await runIdentityCreate({
|
|
27515
27631
|
...toCommonOptions(flags),
|
|
27632
|
+
tenant,
|
|
27516
27633
|
type: flags.type,
|
|
27517
27634
|
externalId: flags.externalId,
|
|
27518
27635
|
name: flags.name,
|
|
@@ -27523,11 +27640,14 @@ function registerIdentityCommands(program, deps) {
|
|
|
27523
27640
|
}, deps);
|
|
27524
27641
|
}
|
|
27525
27642
|
);
|
|
27526
|
-
const list = identity.command("list").description("List principals of a dimension (optionally filtered).").addOption(dimensionOption()).option("--external-id <id>", "filter to a stable id").option("--limit <n>", "page size (1\u2013100)", (v) => Number.parseInt(v, 10));
|
|
27643
|
+
const list = identity.command("list").description("List principals of a dimension (optionally filtered).").addOption(dimensionOption()).option("--external-id <id>", "filter to a stable id").option("--limit <n>", "page size (1\u2013100)", (v) => Number.parseInt(v, 10)).option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
27527
27644
|
addCommonAuthOptions(list).action(
|
|
27528
27645
|
async (flags) => {
|
|
27646
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27647
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27529
27648
|
process.exitCode = await runIdentityList({
|
|
27530
27649
|
...toCommonOptions(flags),
|
|
27650
|
+
tenant,
|
|
27531
27651
|
type: flags.type,
|
|
27532
27652
|
externalId: flags.externalId,
|
|
27533
27653
|
limit: flags.limit
|
|
@@ -27547,20 +27667,30 @@ function registerIdentityCommands(program, deps) {
|
|
|
27547
27667
|
);
|
|
27548
27668
|
}
|
|
27549
27669
|
);
|
|
27550
|
-
const get = identity.command("get").description("Show one principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id (from create/list)");
|
|
27670
|
+
const get = identity.command("get").description("Show one principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id (from create/list)").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
27551
27671
|
addCommonAuthOptions(get).action(async (flags) => {
|
|
27552
|
-
|
|
27672
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27673
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27674
|
+
process.exitCode = await runIdentityGet({ ...toCommonOptions(flags), tenant, type: flags.type, id: flags.id }, deps);
|
|
27553
27675
|
});
|
|
27554
|
-
const del = identity.command("delete").description("Delete a principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id");
|
|
27676
|
+
const del = identity.command("delete").description("Delete a principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
27555
27677
|
addAuthOptions(del).action(async (flags) => {
|
|
27556
|
-
|
|
27678
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27679
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27680
|
+
process.exitCode = await runIdentityDelete(
|
|
27681
|
+
{ ...toCommonOptions(flags), tenant, type: flags.type, id: flags.id },
|
|
27682
|
+
deps
|
|
27683
|
+
);
|
|
27557
27684
|
});
|
|
27558
|
-
const invite = identity.command("invite").description("Onboard a person into a context by email (POST /v1/users/invite) \u2014 no root key required.").requiredOption("--context <contextId>", "the app context the invitee will operate in").requiredOption("--role <roleId>", "an existing Role in that context; the invitee inherits its scopes").requiredOption("--email <email>", "the invitee's email address").option("--first-name <name>", "personalizes the invitation email greeting").option("--accept-url <url>", "your accept-invite landing page (required unless --no-send-email)").option("--no-send-email", "skip platform delivery; the response carries the raw inviteToken/acceptLink instead");
|
|
27685
|
+
const invite = identity.command("invite").description("Onboard a person into a context by email (POST /v1/users/invite) \u2014 no root key required.").requiredOption("--context <contextId>", "the app context the invitee will operate in").requiredOption("--role <roleId>", "an existing Role in that context; the invitee inherits its scopes").requiredOption("--email <email>", "the invitee's email address").option("--first-name <name>", "personalizes the invitation email greeting").option("--accept-url <url>", "your accept-invite landing page (required unless --no-send-email)").option("--no-send-email", "skip platform delivery; the response carries the raw inviteToken/acceptLink instead").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
27559
27686
|
addCommonAuthOptions(invite).action(
|
|
27560
27687
|
async (flags) => {
|
|
27688
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27689
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27561
27690
|
process.exitCode = await runIdentityInvite(
|
|
27562
27691
|
{
|
|
27563
27692
|
...toCommonOptions(flags),
|
|
27693
|
+
tenant,
|
|
27564
27694
|
context: flags.context,
|
|
27565
27695
|
role: flags.role,
|
|
27566
27696
|
email: flags.email,
|