@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.js
CHANGED
|
@@ -1614,7 +1614,7 @@ var require_proper_lockfile = __commonJS({
|
|
|
1614
1614
|
// src/cli-bootstrap.ts
|
|
1615
1615
|
var readline2 = __toESM(require("readline/promises"));
|
|
1616
1616
|
|
|
1617
|
-
// node_modules/@vectros-ai/sdk/dist/index.mjs
|
|
1617
|
+
// ../../node_modules/@vectros-ai/sdk/dist/index.mjs
|
|
1618
1618
|
var __defProp2 = Object.defineProperty;
|
|
1619
1619
|
var __export = (target, all) => {
|
|
1620
1620
|
for (var name in all)
|
|
@@ -4044,7 +4044,7 @@ var AuthClient = class {
|
|
|
4044
4044
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/jwks");
|
|
4045
4045
|
}
|
|
4046
4046
|
/**
|
|
4047
|
-
* 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.
|
|
4047
|
+
* 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.
|
|
4048
4048
|
*
|
|
4049
4049
|
* @param {Vectros.GetAccessLogRequest} request
|
|
4050
4050
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5749,7 +5749,7 @@ var AuthClient = class {
|
|
|
5749
5749
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/issuers/{issuerId}");
|
|
5750
5750
|
}
|
|
5751
5751
|
/**
|
|
5752
|
-
* 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
|
|
5752
|
+
* 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.
|
|
5753
5753
|
*
|
|
5754
5754
|
* @param {Vectros.IssuerUpdateRequest} request
|
|
5755
5755
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6559,7 +6559,7 @@ var ComplianceClient = class {
|
|
|
6559
6559
|
this._options = normalizeClientOptionsWithAuth(options);
|
|
6560
6560
|
}
|
|
6561
6561
|
/**
|
|
6562
|
-
* Submits
|
|
6562
|
+
* 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.
|
|
6563
6563
|
*
|
|
6564
6564
|
* @param {Vectros.ErasureRequest} request
|
|
6565
6565
|
* @param {ComplianceClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6622,7 +6622,7 @@ var ComplianceClient = class {
|
|
|
6622
6622
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/erasure-requests");
|
|
6623
6623
|
}
|
|
6624
6624
|
/**
|
|
6625
|
-
* 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
|
|
6625
|
+
* 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.
|
|
6626
6626
|
*
|
|
6627
6627
|
* @param {Vectros.GetErasureRequestRequest} request
|
|
6628
6628
|
* @param {ComplianceClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -9441,6 +9441,8 @@ var InferenceClient = class {
|
|
|
9441
9441
|
throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
|
|
9442
9442
|
case 403:
|
|
9443
9443
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
9444
|
+
case 413:
|
|
9445
|
+
throw new ContentTooLargeError(_response.error.body, _response.rawResponse);
|
|
9444
9446
|
case 429:
|
|
9445
9447
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
9446
9448
|
default:
|
|
@@ -9580,6 +9582,8 @@ var InferenceClient = class {
|
|
|
9580
9582
|
throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
|
|
9581
9583
|
case 403:
|
|
9582
9584
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
9585
|
+
case 413:
|
|
9586
|
+
throw new ContentTooLargeError(_response.error.body, _response.rawResponse);
|
|
9583
9587
|
case 429:
|
|
9584
9588
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
9585
9589
|
case 500:
|
|
@@ -11877,7 +11881,7 @@ var RenderHintsSchema = import_zod.z.object({
|
|
|
11877
11881
|
helpText: import_zod.z.string().optional(),
|
|
11878
11882
|
// Marks this field as the record's headline (display) field — the linked
|
|
11879
11883
|
// primary column in the records list + the title on the detail view. At most
|
|
11880
|
-
// one per schema (the platform
|
|
11884
|
+
// one per schema (the platform stores the hint and does not enforce one; the reference UI uses the first by order). Format passthrough →
|
|
11881
11885
|
// SchemaRequest.renderHints[fieldId].displayField.
|
|
11882
11886
|
displayField: import_zod.z.boolean().optional()
|
|
11883
11887
|
}).strict();
|
|
@@ -13536,7 +13540,8 @@ var BlueprintSchema = import_zod.z.object({
|
|
|
13536
13540
|
* alongside app-context/service-principal creation and issuers, BEFORE
|
|
13537
13541
|
* schemas/seed (an `entityBacked` namespace's entities, and an
|
|
13538
13542
|
* `allowedSurfaces: ['entity']` schema, both need the registration to
|
|
13539
|
-
* already exist). `org`/`client`
|
|
13543
|
+
* already exist). `org`/`client` are registered the same way as any other
|
|
13544
|
+
* namespace: signup does not register them, so declare an entry for each you use.
|
|
13540
13545
|
*/
|
|
13541
13546
|
namespaces: import_zod.z.array(BlueprintNamespaceSchema).optional(),
|
|
13542
13547
|
/**
|
|
@@ -16500,6 +16505,25 @@ async function clearSession(baseDir) {
|
|
|
16500
16505
|
return false;
|
|
16501
16506
|
}
|
|
16502
16507
|
}
|
|
16508
|
+
var TokenEndpointError = class extends Error {
|
|
16509
|
+
status;
|
|
16510
|
+
/** The OAuth `error` field of a JSON error body (e.g. `invalid_grant`), when there is one. */
|
|
16511
|
+
oauthError;
|
|
16512
|
+
constructor(status, text) {
|
|
16513
|
+
super(`token endpoint HTTP ${status}${text ? ` \u2014 ${stripControlCharacters(text, 500)}` : ""}`);
|
|
16514
|
+
this.name = "TokenEndpointError";
|
|
16515
|
+
this.status = status;
|
|
16516
|
+
this.oauthError = oauthErrorOf(text);
|
|
16517
|
+
}
|
|
16518
|
+
};
|
|
16519
|
+
function oauthErrorOf(text) {
|
|
16520
|
+
try {
|
|
16521
|
+
const parsed = JSON.parse(text);
|
|
16522
|
+
return typeof parsed.error === "string" ? parsed.error : void 0;
|
|
16523
|
+
} catch {
|
|
16524
|
+
return void 0;
|
|
16525
|
+
}
|
|
16526
|
+
}
|
|
16503
16527
|
async function postToken(config, body, fetchImpl) {
|
|
16504
16528
|
const res = await fetchImpl(tokenEndpoint(config), {
|
|
16505
16529
|
method: "POST",
|
|
@@ -16507,9 +16531,7 @@ async function postToken(config, body, fetchImpl) {
|
|
|
16507
16531
|
body: body.toString()
|
|
16508
16532
|
});
|
|
16509
16533
|
const text = await res.text().catch(() => "");
|
|
16510
|
-
if (!res.ok)
|
|
16511
|
-
throw new Error(`token endpoint HTTP ${res.status}${text ? ` \u2014 ${stripControlCharacters(text, 500)}` : ""}`);
|
|
16512
|
-
}
|
|
16534
|
+
if (!res.ok) throw new TokenEndpointError(res.status, text);
|
|
16513
16535
|
try {
|
|
16514
16536
|
return JSON.parse(text);
|
|
16515
16537
|
} catch {
|
|
@@ -16525,31 +16547,54 @@ async function refreshTokens(config, refreshToken, fetchImpl = fetch) {
|
|
|
16525
16547
|
|
|
16526
16548
|
// src/auth-context.ts
|
|
16527
16549
|
var COGNITO_TOKEN_ENV = "VECTROS_BOOTSTRAP_TOKEN";
|
|
16550
|
+
function loginCommandFor(env) {
|
|
16551
|
+
return env === "staging" ? "vectros login --env staging" : "vectros login";
|
|
16552
|
+
}
|
|
16528
16553
|
var MissingCredentialError = class extends Error {
|
|
16529
|
-
constructor() {
|
|
16554
|
+
constructor(env) {
|
|
16530
16555
|
super(
|
|
16531
|
-
`No credential found.
|
|
16556
|
+
`No credential found. Run \`${loginCommandFor(env)}\` to sign in, or set ${COGNITO_TOKEN_ENV} or pass --token (generate a token in the developer portal).`
|
|
16532
16557
|
);
|
|
16533
16558
|
this.name = "MissingCredentialError";
|
|
16534
16559
|
}
|
|
16535
16560
|
};
|
|
16561
|
+
var SessionExpiredError = class extends Error {
|
|
16562
|
+
constructor(env) {
|
|
16563
|
+
super(
|
|
16564
|
+
`Your Vectros login session has expired or was revoked. Run \`${loginCommandFor(env)}\` to sign in again.`
|
|
16565
|
+
);
|
|
16566
|
+
this.name = "SessionExpiredError";
|
|
16567
|
+
}
|
|
16568
|
+
};
|
|
16536
16569
|
function resolveCognitoTokenSync(explicit) {
|
|
16537
16570
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
16538
16571
|
const fromEnv = process.env[COGNITO_TOKEN_ENV];
|
|
16539
16572
|
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
16540
16573
|
return void 0;
|
|
16541
16574
|
}
|
|
16542
|
-
async function resolveCognitoToken(env, explicit) {
|
|
16575
|
+
async function resolveCognitoToken(env, explicit, deps = { readSession, refreshTokens, saveSession }) {
|
|
16543
16576
|
const sync = resolveCognitoTokenSync(explicit);
|
|
16544
16577
|
if (sync) return sync;
|
|
16545
|
-
const session = await readSession();
|
|
16578
|
+
const session = await deps.readSession();
|
|
16546
16579
|
if (session && session.env === env) {
|
|
16547
16580
|
const config = resolveCognitoConfig(env);
|
|
16548
|
-
|
|
16581
|
+
let tokens;
|
|
16582
|
+
try {
|
|
16583
|
+
tokens = await deps.refreshTokens(config, session.refreshToken);
|
|
16584
|
+
} catch (err) {
|
|
16585
|
+
if (err instanceof TokenEndpointError && err.oauthError === "invalid_grant") throw new SessionExpiredError(env);
|
|
16586
|
+
throw err;
|
|
16587
|
+
}
|
|
16588
|
+
if (tokens.refresh_token && tokens.refresh_token !== session.refreshToken) {
|
|
16589
|
+
const stored = await deps.readSession().catch(() => void 0);
|
|
16590
|
+
if (stored?.refreshToken === session.refreshToken) {
|
|
16591
|
+
await deps.saveSession({ ...session, refreshToken: tokens.refresh_token }).catch(() => void 0);
|
|
16592
|
+
}
|
|
16593
|
+
}
|
|
16549
16594
|
const bearer = tokens.id_token ?? tokens.access_token;
|
|
16550
16595
|
if (bearer) return bearer;
|
|
16551
16596
|
}
|
|
16552
|
-
throw new MissingCredentialError();
|
|
16597
|
+
throw new MissingCredentialError(env);
|
|
16553
16598
|
}
|
|
16554
16599
|
async function resolveAuthContext(opts) {
|
|
16555
16600
|
const baseUrl = envToBaseUrl(opts.env, opts.baseUrl ?? process.env.VECTROS_API_BASE_URL);
|
|
@@ -17061,6 +17106,7 @@ function keyNameFor(pack, machineId) {
|
|
|
17061
17106
|
}
|
|
17062
17107
|
|
|
17063
17108
|
// src/identity-resolver.ts
|
|
17109
|
+
var IDENTITIES_TO_ENSURE_HEADING = " Identities to ensure (idempotent by external id; a non-user entity lives with its namespace):";
|
|
17064
17110
|
function userPayload(decl) {
|
|
17065
17111
|
const meta = decl.metadata;
|
|
17066
17112
|
if (decl.displayName === void 0) return meta;
|
|
@@ -17201,7 +17247,7 @@ async function withAuth(opts, deps, body) {
|
|
|
17201
17247
|
log
|
|
17202
17248
|
});
|
|
17203
17249
|
} catch (err) {
|
|
17204
|
-
if (err instanceof MissingCredentialError || err instanceof InvalidBaseUrlError || err instanceof InvalidCognitoRegionError) {
|
|
17250
|
+
if (err instanceof MissingCredentialError || err instanceof SessionExpiredError || err instanceof InvalidBaseUrlError || err instanceof InvalidCognitoRegionError) {
|
|
17205
17251
|
process.stderr.write(` \u2716 ${err.message}
|
|
17206
17252
|
`);
|
|
17207
17253
|
return 2;
|
|
@@ -17796,15 +17842,6 @@ async function provisionContext(client, input) {
|
|
|
17796
17842
|
const { blueprint, allowTenantWideNamespaces = false, readExistingKey, confirmExistingPrincipal, log } = input;
|
|
17797
17843
|
assertBlueprintGates(blueprint);
|
|
17798
17844
|
assertTenantWideNamespacesAllowed(blueprint, allowTenantWideNamespaces);
|
|
17799
|
-
const ctxResp = await client.auth.createAppContext({
|
|
17800
|
-
body: {
|
|
17801
|
-
contextId: blueprint.contextId,
|
|
17802
|
-
name: contextNameOf(blueprint),
|
|
17803
|
-
...blueprint.identityProjectionClaims === void 0 ? {} : { identityProjectionClaims: blueprint.identityProjectionClaims }
|
|
17804
|
-
}
|
|
17805
|
-
});
|
|
17806
|
-
const contextAction = actionOf(ctxResp.created);
|
|
17807
|
-
log.debug({ component: "loader", contextId: blueprint.contextId, contextAction }, "app-context ensured");
|
|
17808
17845
|
const user = await client.identity.createUser({
|
|
17809
17846
|
body: {
|
|
17810
17847
|
externalId: blueprint.servicePrincipal.externalId,
|
|
@@ -17845,6 +17882,15 @@ async function provisionContext(client, input) {
|
|
|
17845
17882
|
"existing-principal collision confirmed (or unwired) \u2014 proceeding with any further write to this principal"
|
|
17846
17883
|
);
|
|
17847
17884
|
}
|
|
17885
|
+
const ctxResp = await client.auth.createAppContext({
|
|
17886
|
+
body: {
|
|
17887
|
+
contextId: blueprint.contextId,
|
|
17888
|
+
name: contextNameOf(blueprint),
|
|
17889
|
+
...blueprint.identityProjectionClaims === void 0 ? {} : { identityProjectionClaims: blueprint.identityProjectionClaims }
|
|
17890
|
+
}
|
|
17891
|
+
});
|
|
17892
|
+
const contextAction = actionOf(ctxResp.created);
|
|
17893
|
+
log.debug({ component: "loader", contextId: blueprint.contextId, contextAction }, "app-context ensured");
|
|
17848
17894
|
const needsContextPinnedBootstrap = (blueprint.namespaces ?? []).length > 0;
|
|
17849
17895
|
const ctxBootstrapClient = needsContextPinnedBootstrap ? input.remintBootstrapForContext ? await input.remintBootstrapForContext(blueprint.contextId) : client : void 0;
|
|
17850
17896
|
if (ctxBootstrapClient && input.remintBootstrapForContext) {
|
|
@@ -18353,14 +18399,14 @@ function renderPlanDataScope(ds) {
|
|
|
18353
18399
|
function buildPlan(blueprint, opts = {}) {
|
|
18354
18400
|
const steps = [];
|
|
18355
18401
|
const projection = blueprint.identityProjectionClaims?.length ? `, identity projection: [${blueprint.identityProjectionClaims.join(", ")}]` : blueprint.identityProjectionClaims ? ", identity projection: disabled (empty list)" : "";
|
|
18356
|
-
steps.push({
|
|
18357
|
-
kind: "context",
|
|
18358
|
-
summary: `context '${blueprint.contextId}' (${contextNameOf(blueprint)}${projection})`
|
|
18359
|
-
});
|
|
18360
18402
|
steps.push({
|
|
18361
18403
|
kind: "principal",
|
|
18362
18404
|
summary: `principal '${blueprint.servicePrincipal.externalId}' (SERVICE)`
|
|
18363
18405
|
});
|
|
18406
|
+
steps.push({
|
|
18407
|
+
kind: "context",
|
|
18408
|
+
summary: `context '${blueprint.contextId}' (${contextNameOf(blueprint)}${projection})`
|
|
18409
|
+
});
|
|
18364
18410
|
for (const ns of blueprint.namespaces ?? []) {
|
|
18365
18411
|
const ownerNote = ns.tenantWide ? "TENANT-WIDE \u2014 visible to every context" : `owned by '${blueprint.contextId}'`;
|
|
18366
18412
|
const entityNote = ns.entityBacked ? ", entity-backed" : "";
|
|
@@ -18915,9 +18961,8 @@ var OsKeychainSecretStore = class {
|
|
|
18915
18961
|
* enumeration, and `deleteCredential` on a nonexistent ref returns the same
|
|
18916
18962
|
* meaningless `false` as Linux. So the `.some(c => c.account === ref)` comparison
|
|
18917
18963
|
* is sound on both measured platforms. Still unmeasured: a CI lane that runs this
|
|
18918
|
-
* against the real Windows store on every
|
|
18919
|
-
* measurement, not a regression gate.
|
|
18920
|
-
* public npm, so no tracker number here.)
|
|
18964
|
+
* against the real Windows store on every change — the dev-machine probe is a point
|
|
18965
|
+
* measurement, not a regression gate.
|
|
18921
18966
|
*
|
|
18922
18967
|
* ⚠️ THIS METHOD'S FAIL-CLOSED GUARANTEE IS AN UPSTREAM BEHAVIOURAL CONTRACT, NOT
|
|
18923
18968
|
* SOMETHING THIS CODE ENFORCES. `@napi-rs/keyring` is pinned to an EXACT version
|
|
@@ -19073,7 +19118,7 @@ var CryptoFileSecretStore = class {
|
|
|
19073
19118
|
async setSecret(ref, secret) {
|
|
19074
19119
|
const key = await this.encryptionKey();
|
|
19075
19120
|
const iv = (0, import_node_crypto3.randomBytes)(IV_LEN);
|
|
19076
|
-
const cipher = (0, import_node_crypto3.createCipheriv)(ALGO, key, iv);
|
|
19121
|
+
const cipher = (0, import_node_crypto3.createCipheriv)(ALGO, key, iv, { authTagLength: TAG_LEN });
|
|
19077
19122
|
const enc = Buffer.concat([cipher.update(secret, "utf8"), cipher.final()]);
|
|
19078
19123
|
const tag = cipher.getAuthTag();
|
|
19079
19124
|
const blob = Buffer.concat([iv, tag, enc]);
|
|
@@ -19096,7 +19141,7 @@ var CryptoFileSecretStore = class {
|
|
|
19096
19141
|
const iv = blob.subarray(0, IV_LEN);
|
|
19097
19142
|
const tag = blob.subarray(IV_LEN, IV_LEN + TAG_LEN);
|
|
19098
19143
|
const enc = blob.subarray(IV_LEN + TAG_LEN);
|
|
19099
|
-
const decipher = (0, import_node_crypto3.createDecipheriv)(ALGO, key, iv);
|
|
19144
|
+
const decipher = (0, import_node_crypto3.createDecipheriv)(ALGO, key, iv, { authTagLength: TAG_LEN });
|
|
19100
19145
|
decipher.setAuthTag(tag);
|
|
19101
19146
|
return Buffer.concat([decipher.update(enc), decipher.final()]).toString("utf8");
|
|
19102
19147
|
} catch {
|
|
@@ -19296,7 +19341,7 @@ async function withIndexLock(dir, fn, timings = LOCK_OPTS) {
|
|
|
19296
19341
|
// judged: two waiters can both condemn one crash remnant, and the second's
|
|
19297
19342
|
// already-decided rmdir destroys the first's FRESH lock. Nothing freezes.
|
|
19298
19343
|
//
|
|
19299
|
-
// MEASURED against this exact config (
|
|
19344
|
+
// MEASURED against this exact config (probe results below):
|
|
19300
19345
|
// real OS processes, no injection : 0/25 and 1/25 (two independent runs)
|
|
19301
19346
|
// in-process, 5 waiters : ~3% per round
|
|
19302
19347
|
// rival's rmdir lagged one turn : 19/25
|
|
@@ -21129,7 +21174,7 @@ async function resolveBridgeCognitoToken(args, prompt, isTty, resolveCognito = r
|
|
|
21129
21174
|
}
|
|
21130
21175
|
if (!isTty) {
|
|
21131
21176
|
throw new Error(
|
|
21132
|
-
|
|
21177
|
+
`No bridge token: set VECTROS_BOOTSTRAP_TOKEN, pass --token, or run \`${loginCommandFor(args.env)}\` first (required in non-interactive mode).`
|
|
21133
21178
|
);
|
|
21134
21179
|
}
|
|
21135
21180
|
process.stdout.write(
|
|
@@ -21238,7 +21283,9 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
21238
21283
|
`);
|
|
21239
21284
|
const declaredIdentities = Object.entries(blueprint.identities ?? {});
|
|
21240
21285
|
if (declaredIdentities.length > 0) {
|
|
21241
|
-
process.stdout.write(
|
|
21286
|
+
process.stdout.write(`
|
|
21287
|
+
${IDENTITIES_TO_ENSURE_HEADING}
|
|
21288
|
+
`);
|
|
21242
21289
|
for (const [name, decl] of declaredIdentities) {
|
|
21243
21290
|
process.stdout.write(` ~ ${name} (${decl.kind}) \u2014 external id '${decl.externalId}'
|
|
21244
21291
|
`);
|
|
@@ -21469,6 +21516,12 @@ ${codeAddHint}`);
|
|
|
21469
21516
|
}
|
|
21470
21517
|
return finishApply();
|
|
21471
21518
|
} catch (err) {
|
|
21519
|
+
if (err instanceof SessionExpiredError) {
|
|
21520
|
+
process.stderr.write(`
|
|
21521
|
+
\u2716 ${err.message}
|
|
21522
|
+
`);
|
|
21523
|
+
return 2;
|
|
21524
|
+
}
|
|
21472
21525
|
if (err instanceof LostKeyError) {
|
|
21473
21526
|
process.stderr.write(`
|
|
21474
21527
|
\u2716 ${err.message}
|
|
@@ -21840,7 +21893,9 @@ ${renderPlan(blueprint, buildPlan(blueprint, { noSeed, noKey: true }), { prune:
|
|
|
21840
21893
|
process.stdout.write(applySeedNotice(blueprint, args.seed));
|
|
21841
21894
|
const declaredIdentities = Object.entries(blueprint.identities ?? {});
|
|
21842
21895
|
if (declaredIdentities.length > 0) {
|
|
21843
|
-
process.stdout.write(
|
|
21896
|
+
process.stdout.write(`
|
|
21897
|
+
${IDENTITIES_TO_ENSURE_HEADING}
|
|
21898
|
+
`);
|
|
21844
21899
|
for (const [name, decl] of declaredIdentities) {
|
|
21845
21900
|
process.stdout.write(` ~ ${name} (${decl.kind}) \u2014 external id '${decl.externalId}'
|
|
21846
21901
|
`);
|
|
@@ -21973,7 +22028,7 @@ function reportApplyError(err) {
|
|
|
21973
22028
|
If this is expected (a re-apply of a blueprint you manage), re-run with --confirm-existing-principal
|
|
21974
22029
|
(with --yes in a pipeline, where it is standing consent), or confirm at the prompt.
|
|
21975
22030
|
If you don't recognize this identity, investigate the blueprint's servicePrincipal.externalId first.
|
|
21976
|
-
Only the
|
|
22031
|
+
Only the principal lookup had run; no context, grant or anything else was created or changed.
|
|
21977
22032
|
`
|
|
21978
22033
|
);
|
|
21979
22034
|
return 3;
|
|
@@ -22326,10 +22381,14 @@ schemas:
|
|
|
22326
22381
|
accessProfile:
|
|
22327
22382
|
# SCOPE GATE: data-plane only (records/schemas/search/documents/folders/inference/entities),
|
|
22328
22383
|
# PLUS profiles:r/u/d (bounded by the platform to redistributing access you already have,
|
|
22329
|
-
# never granting more)
|
|
22330
|
-
# platform resolves "self" from the caller, not from anything you declare here)
|
|
22331
|
-
#
|
|
22332
|
-
#
|
|
22384
|
+
# never granting more), profiles:c:self (create a profile for yourself only \u2014 the
|
|
22385
|
+
# platform resolves "self" from the caller, not from anything you declare here) and
|
|
22386
|
+
# scripts:x:<script-name> (run one named script that this blueprint ships; there is no bare
|
|
22387
|
+
# or wildcard form).
|
|
22388
|
+
# A bare profiles:c is accepted ONLY when this same clause also declares the
|
|
22389
|
+
# member-lifecycle capability (see "capabilities" below); without it, and always for
|
|
22390
|
+
# profiles:c:usr_<id> naming another principal, it is REJECTED \u2014 no override. Every other
|
|
22391
|
+
# control-plane resource (keys/users/admin/namespaces/triggers/...) and "*" is REJECTED the same way.
|
|
22333
22392
|
# Ops: c|r|u|d, combinable (records:cru). Prefer the narrowest set (least privilege).
|
|
22334
22393
|
allowedActions: [records:r, records:c, records:u, search:r, schemas:r]
|
|
22335
22394
|
# dataScope binds the key to specific owners. Include null to ALSO see
|
|
@@ -22345,8 +22404,10 @@ accessProfile:
|
|
|
22345
22404
|
# above) unless every owner genuinely sharing every record is what you want.
|
|
22346
22405
|
dataScope: {}
|
|
22347
22406
|
# capabilities is a SEPARATE gated dimension from allowedActions \u2014 named platform
|
|
22348
|
-
# effects a resource:ops verb can't express. Only 'member-lifecycle'
|
|
22349
|
-
# today; every other name is
|
|
22407
|
+
# effects a resource:ops verb can't express. Only 'member-lifecycle' and
|
|
22408
|
+
# 'delegate-principal-stamp' are admitted today; every other name is REJECTED, same
|
|
22409
|
+
# posture as the scope gate above. 'member-lifecycle' is also what lets this clause
|
|
22410
|
+
# carry a bare profiles:c.
|
|
22350
22411
|
# capabilities: [member-lifecycle]
|
|
22351
22412
|
|
|
22352
22413
|
servicePrincipal:
|
|
@@ -22433,8 +22494,10 @@ Usage:
|
|
|
22433
22494
|
|
|
22434
22495
|
<file|name> is a path to your own blueprint (YAML .yaml/.yml or JSON .json) OR
|
|
22435
22496
|
the name of a bundled blueprint. The same scope gate that protects bundled
|
|
22436
|
-
blueprints applies to file blueprints
|
|
22437
|
-
|
|
22497
|
+
blueprints applies to file blueprints: beyond data-plane access, a forked or
|
|
22498
|
+
hand-written blueprint can request only a short allowlist of narrow exceptions
|
|
22499
|
+
(profiles:r/u/d, profiles:c:self, a bare profiles:c beside member-lifecycle,
|
|
22500
|
+
scripts:x:<script-name>); every other control-plane scope is rejected.
|
|
22438
22501
|
|
|
22439
22502
|
A blueprint may declare a top-level 'inputs:' block and reference its values
|
|
22440
22503
|
with \${{ inputs.<name> }} (and the built-ins \${{ vectros.context }} /
|
|
@@ -22546,7 +22609,9 @@ ${renderPlan(blueprint, steps)}
|
|
|
22546
22609
|
`);
|
|
22547
22610
|
const declaredIdentities = Object.entries(blueprint.identities ?? {});
|
|
22548
22611
|
if (declaredIdentities.length > 0) {
|
|
22549
|
-
process.stdout.write(
|
|
22612
|
+
process.stdout.write(`
|
|
22613
|
+
${IDENTITIES_TO_ENSURE_HEADING}
|
|
22614
|
+
`);
|
|
22550
22615
|
for (const [name, decl] of declaredIdentities) {
|
|
22551
22616
|
process.stdout.write(` ~ ${name} (${decl.kind}) \u2014 external id '${decl.externalId}'
|
|
22552
22617
|
`);
|
|
@@ -22680,6 +22745,89 @@ ${BLUEPRINT_HELP}
|
|
|
22680
22745
|
// src/program.ts
|
|
22681
22746
|
var import_commander = require("commander");
|
|
22682
22747
|
|
|
22748
|
+
// src/dev-api.ts
|
|
22749
|
+
var DeveloperApiError = class extends Error {
|
|
22750
|
+
statusCode;
|
|
22751
|
+
/** The platform's error body, when it answered with one (its message, request id and error code). */
|
|
22752
|
+
body;
|
|
22753
|
+
constructor(message, statusCode, body) {
|
|
22754
|
+
super(message);
|
|
22755
|
+
this.name = "DeveloperApiError";
|
|
22756
|
+
this.statusCode = statusCode;
|
|
22757
|
+
this.body = body;
|
|
22758
|
+
}
|
|
22759
|
+
};
|
|
22760
|
+
async function fetchDeveloperUsersPage(opts) {
|
|
22761
|
+
const { cognitoToken, apiBaseUrl, tenant, startFrom, limit, fetchImpl = fetch } = opts;
|
|
22762
|
+
const base = apiBaseUrl.replace(/\/$/, "");
|
|
22763
|
+
let url = `${base}/developer/users?tenant=${encodeURIComponent(tenant)}`;
|
|
22764
|
+
if (startFrom) url += `&startFrom=${encodeURIComponent(startFrom)}`;
|
|
22765
|
+
if (limit !== void 0) url += `&limit=${encodeURIComponent(String(limit))}`;
|
|
22766
|
+
let res;
|
|
22767
|
+
try {
|
|
22768
|
+
res = await fetchImpl(url, {
|
|
22769
|
+
method: "GET",
|
|
22770
|
+
headers: { Authorization: `Bearer ${cognitoToken}` }
|
|
22771
|
+
});
|
|
22772
|
+
} catch (err) {
|
|
22773
|
+
throw new DeveloperApiError(
|
|
22774
|
+
`Request to /developer/users failed: ${err instanceof Error ? err.message : String(err)}`
|
|
22775
|
+
);
|
|
22776
|
+
}
|
|
22777
|
+
if (!res.ok) {
|
|
22778
|
+
const body = await res.text().catch(() => "");
|
|
22779
|
+
const hint = res.status === 403 ? " (this command requires an account owner login \u2014 sign in as the owner and retry)" : "";
|
|
22780
|
+
throw new DeveloperApiError(
|
|
22781
|
+
`GET /developer/users failed: HTTP ${res.status}${hint}${body ? ` \u2014 ${body}` : ""}`,
|
|
22782
|
+
res.status
|
|
22783
|
+
);
|
|
22784
|
+
}
|
|
22785
|
+
let parsed;
|
|
22786
|
+
try {
|
|
22787
|
+
parsed = await res.json();
|
|
22788
|
+
} catch {
|
|
22789
|
+
throw new DeveloperApiError("GET /developer/users returned a non-JSON body");
|
|
22790
|
+
}
|
|
22791
|
+
const data = Array.isArray(parsed.data) ? parsed.data : [];
|
|
22792
|
+
const nextCursor = typeof parsed.nextCursor === "string" ? parsed.nextCursor : null;
|
|
22793
|
+
return { data, nextCursor };
|
|
22794
|
+
}
|
|
22795
|
+
async function fetchDeveloperMe(opts) {
|
|
22796
|
+
const { cognitoToken, apiBaseUrl, tenant, fetchImpl = fetch } = opts;
|
|
22797
|
+
const url = `${apiBaseUrl.replace(/\/$/, "")}/developer/me?tenant=${encodeURIComponent(tenant)}`;
|
|
22798
|
+
let res;
|
|
22799
|
+
try {
|
|
22800
|
+
res = await fetchImpl(url, { method: "GET", headers: { Authorization: `Bearer ${cognitoToken}` } });
|
|
22801
|
+
} catch (err) {
|
|
22802
|
+
throw new DeveloperApiError(
|
|
22803
|
+
`Request to /developer/me failed: ${err instanceof Error ? err.message : String(err)}`
|
|
22804
|
+
);
|
|
22805
|
+
}
|
|
22806
|
+
if (res.status === 404) return null;
|
|
22807
|
+
if (!res.ok) {
|
|
22808
|
+
const failure = await devRouteError(res, "GET /developer/me");
|
|
22809
|
+
throw new DeveloperApiError(
|
|
22810
|
+
`GET /developer/me failed: HTTP ${res.status}`,
|
|
22811
|
+
res.status,
|
|
22812
|
+
failure.body
|
|
22813
|
+
);
|
|
22814
|
+
}
|
|
22815
|
+
let parsed;
|
|
22816
|
+
try {
|
|
22817
|
+
parsed = await res.json();
|
|
22818
|
+
} catch {
|
|
22819
|
+
throw new DeveloperApiError("GET /developer/me returned a non-JSON body");
|
|
22820
|
+
}
|
|
22821
|
+
if (typeof parsed.principalId !== "string" || typeof parsed.userId !== "string") {
|
|
22822
|
+
throw new DeveloperApiError("GET /developer/me returned no principal");
|
|
22823
|
+
}
|
|
22824
|
+
return {
|
|
22825
|
+
principalId: parsed.principalId,
|
|
22826
|
+
userId: parsed.userId,
|
|
22827
|
+
tenantId: typeof parsed.tenantId === "string" ? parsed.tenantId : void 0
|
|
22828
|
+
};
|
|
22829
|
+
}
|
|
22830
|
+
|
|
22683
22831
|
// src/self-principal.ts
|
|
22684
22832
|
var PRINCIPAL_PREFIX_USER = "usr_";
|
|
22685
22833
|
function emailFromJwt(token) {
|
|
@@ -22708,6 +22856,37 @@ async function resolveSelfPrincipal(client, cognitoToken) {
|
|
|
22708
22856
|
if (!match?.id) return null;
|
|
22709
22857
|
return { principalId: `${PRINCIPAL_PREFIX_USER}${match.id}`, userId: match.id, email };
|
|
22710
22858
|
}
|
|
22859
|
+
async function resolveSelfPrincipalForTenant(auth, fetchImpl) {
|
|
22860
|
+
const tenant = auth.tenant ?? "live";
|
|
22861
|
+
let me;
|
|
22862
|
+
try {
|
|
22863
|
+
me = await fetchDeveloperMe({
|
|
22864
|
+
cognitoToken: auth.cognitoToken,
|
|
22865
|
+
apiBaseUrl: auth.baseUrl,
|
|
22866
|
+
tenant,
|
|
22867
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
22868
|
+
});
|
|
22869
|
+
} catch (err) {
|
|
22870
|
+
if (err instanceof DeveloperApiError && err.statusCode !== void 0) {
|
|
22871
|
+
if (err.body?.errorCode === "SESSION_REFRESH_REQUIRED") throw new SessionExpiredError(auth.env ?? "production");
|
|
22872
|
+
throw err;
|
|
22873
|
+
}
|
|
22874
|
+
if (tenant === "test") throw err;
|
|
22875
|
+
return resolveSelfPrincipal(auth.client, auth.cognitoToken);
|
|
22876
|
+
}
|
|
22877
|
+
if (me === null) return null;
|
|
22878
|
+
const forThisTenant = Boolean(auth.tenantId && me.tenantId && me.tenantId === auth.tenantId);
|
|
22879
|
+
if (forThisTenant && me.principalId.startsWith(PRINCIPAL_PREFIX_USER)) {
|
|
22880
|
+
return { principalId: me.principalId, userId: me.userId, email: emailFromJwt(auth.cognitoToken) ?? "" };
|
|
22881
|
+
}
|
|
22882
|
+
return tenant === "live" ? resolveSelfPrincipal(auth.client, auth.cognitoToken) : null;
|
|
22883
|
+
}
|
|
22884
|
+
function selfPrincipalUnresolvedMessage(tenant, flag) {
|
|
22885
|
+
if (tenant === "test") {
|
|
22886
|
+
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.`;
|
|
22887
|
+
}
|
|
22888
|
+
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\`).`;
|
|
22889
|
+
}
|
|
22711
22890
|
|
|
22712
22891
|
// src/commands/whoami.ts
|
|
22713
22892
|
async function runWhoami(options, deps = {}) {
|
|
@@ -22715,7 +22894,10 @@ async function runWhoami(options, deps = {}) {
|
|
|
22715
22894
|
const { identity, baseUrl } = auth;
|
|
22716
22895
|
let self2 = null;
|
|
22717
22896
|
try {
|
|
22718
|
-
self2 = await
|
|
22897
|
+
self2 = await resolveSelfPrincipalForTenant(
|
|
22898
|
+
{ ...auth, client: auth.client },
|
|
22899
|
+
deps.fetchImpl
|
|
22900
|
+
);
|
|
22719
22901
|
} catch {
|
|
22720
22902
|
self2 = null;
|
|
22721
22903
|
}
|
|
@@ -23121,51 +23303,6 @@ ${list}
|
|
|
23121
23303
|
});
|
|
23122
23304
|
}
|
|
23123
23305
|
|
|
23124
|
-
// src/dev-api.ts
|
|
23125
|
-
var DeveloperApiError = class extends Error {
|
|
23126
|
-
statusCode;
|
|
23127
|
-
constructor(message, statusCode) {
|
|
23128
|
-
super(message);
|
|
23129
|
-
this.name = "DeveloperApiError";
|
|
23130
|
-
this.statusCode = statusCode;
|
|
23131
|
-
}
|
|
23132
|
-
};
|
|
23133
|
-
async function fetchDeveloperUsersPage(opts) {
|
|
23134
|
-
const { cognitoToken, apiBaseUrl, tenant, startFrom, limit, fetchImpl = fetch } = opts;
|
|
23135
|
-
const base = apiBaseUrl.replace(/\/$/, "");
|
|
23136
|
-
let url = `${base}/developer/users?tenant=${encodeURIComponent(tenant)}`;
|
|
23137
|
-
if (startFrom) url += `&startFrom=${encodeURIComponent(startFrom)}`;
|
|
23138
|
-
if (limit !== void 0) url += `&limit=${encodeURIComponent(String(limit))}`;
|
|
23139
|
-
let res;
|
|
23140
|
-
try {
|
|
23141
|
-
res = await fetchImpl(url, {
|
|
23142
|
-
method: "GET",
|
|
23143
|
-
headers: { Authorization: `Bearer ${cognitoToken}` }
|
|
23144
|
-
});
|
|
23145
|
-
} catch (err) {
|
|
23146
|
-
throw new DeveloperApiError(
|
|
23147
|
-
`Request to /developer/users failed: ${err instanceof Error ? err.message : String(err)}`
|
|
23148
|
-
);
|
|
23149
|
-
}
|
|
23150
|
-
if (!res.ok) {
|
|
23151
|
-
const body = await res.text().catch(() => "");
|
|
23152
|
-
const hint = res.status === 403 ? " (this command requires an account owner login \u2014 sign in as the owner and retry)" : "";
|
|
23153
|
-
throw new DeveloperApiError(
|
|
23154
|
-
`GET /developer/users failed: HTTP ${res.status}${hint}${body ? ` \u2014 ${body}` : ""}`,
|
|
23155
|
-
res.status
|
|
23156
|
-
);
|
|
23157
|
-
}
|
|
23158
|
-
let parsed;
|
|
23159
|
-
try {
|
|
23160
|
-
parsed = await res.json();
|
|
23161
|
-
} catch {
|
|
23162
|
-
throw new DeveloperApiError("GET /developer/users returned a non-JSON body");
|
|
23163
|
-
}
|
|
23164
|
-
const data = Array.isArray(parsed.data) ? parsed.data : [];
|
|
23165
|
-
const nextCursor = typeof parsed.nextCursor === "string" ? parsed.nextCursor : null;
|
|
23166
|
-
return { data, nextCursor };
|
|
23167
|
-
}
|
|
23168
|
-
|
|
23169
23306
|
// src/commands/identity.ts
|
|
23170
23307
|
function isUserDimension(type) {
|
|
23171
23308
|
return type === "user";
|
|
@@ -23461,14 +23598,13 @@ function assertPrincipalShape(principal) {
|
|
|
23461
23598
|
}
|
|
23462
23599
|
}
|
|
23463
23600
|
var SELF_PRINCIPAL_ALIAS = "me";
|
|
23464
|
-
async function resolvePrincipalArg(raw, auth) {
|
|
23601
|
+
async function resolvePrincipalArg(raw, auth, deps) {
|
|
23465
23602
|
if (raw !== SELF_PRINCIPAL_ALIAS) return raw;
|
|
23466
|
-
const self2 = await
|
|
23467
|
-
|
|
23468
|
-
|
|
23469
|
-
|
|
23470
|
-
|
|
23471
|
-
}
|
|
23603
|
+
const self2 = await resolveSelfPrincipalForTenant(
|
|
23604
|
+
{ ...auth, client: auth.client },
|
|
23605
|
+
deps.fetchImpl
|
|
23606
|
+
);
|
|
23607
|
+
if (!self2) throw new Error(selfPrincipalUnresolvedMessage(auth.tenant, "--principal"));
|
|
23472
23608
|
return self2.principalId;
|
|
23473
23609
|
}
|
|
23474
23610
|
function roleSummary(p) {
|
|
@@ -23521,7 +23657,7 @@ async function runAccessGrant(opts, deps = {}) {
|
|
|
23521
23657
|
return withAuth(opts, deps, async (auth) => {
|
|
23522
23658
|
const client = auth.client;
|
|
23523
23659
|
try {
|
|
23524
|
-
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
23660
|
+
const principal = await resolvePrincipalArg(opts.principal, auth, deps);
|
|
23525
23661
|
const scopes = hasActions ? [{ allowed_actions: splitCsv(opts.actions) }] : void 0;
|
|
23526
23662
|
const profile = await client.auth.createAccessProfile({
|
|
23527
23663
|
contextId: opts.context,
|
|
@@ -23531,8 +23667,10 @@ async function runAccessGrant(opts, deps = {}) {
|
|
|
23531
23667
|
// Always send the plural `roleIds`, even for a single
|
|
23532
23668
|
// `--role`: it is a strict superset of the deprecated singular
|
|
23533
23669
|
// `roleId` (equivalent to `roleIds: ["<value>"]`), and the platform
|
|
23534
|
-
// reads it uniformly regardless of how many roles compose.
|
|
23535
|
-
|
|
23670
|
+
// reads it uniformly regardless of how many roles compose. With
|
|
23671
|
+
// `--actions` there is no role: omit `roleIds` (the platform refuses an
|
|
23672
|
+
// empty list next to inline `scopes`).
|
|
23673
|
+
roleIds: hasRole ? opts.role : void 0,
|
|
23536
23674
|
scopes,
|
|
23537
23675
|
identityOverrides
|
|
23538
23676
|
}
|
|
@@ -23569,7 +23707,7 @@ async function runAccessRevoke(opts, deps = {}) {
|
|
|
23569
23707
|
return withAuth(opts, deps, async (auth) => {
|
|
23570
23708
|
const client = auth.client;
|
|
23571
23709
|
try {
|
|
23572
|
-
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
23710
|
+
const principal = await resolvePrincipalArg(opts.principal, auth, deps);
|
|
23573
23711
|
await client.auth.deleteAccessProfile({ contextId: opts.context, principalId: principal });
|
|
23574
23712
|
process.stdout.write(` \u2714 Revoked ${principal} from '${opts.context}'
|
|
23575
23713
|
`);
|
|
@@ -23598,7 +23736,7 @@ async function runAccessList(opts, deps = {}) {
|
|
|
23598
23736
|
return withAuth(opts, deps, async (auth) => {
|
|
23599
23737
|
const client = auth.client;
|
|
23600
23738
|
try {
|
|
23601
|
-
const principal = opts.principal ? await resolvePrincipalArg(opts.principal, auth) : void 0;
|
|
23739
|
+
const principal = opts.principal ? await resolvePrincipalArg(opts.principal, auth, deps) : void 0;
|
|
23602
23740
|
const rows = opts.context ? await drainPages(
|
|
23603
23741
|
(startFrom) => client.auth.listAccessProfiles({ contextId: opts.context, startFrom, limit: PAGE_LIMIT })
|
|
23604
23742
|
) : await drainPages(
|
|
@@ -23638,7 +23776,7 @@ async function runAccessGet(opts, deps = {}) {
|
|
|
23638
23776
|
return withAuth(opts, deps, async (auth) => {
|
|
23639
23777
|
const client = auth.client;
|
|
23640
23778
|
try {
|
|
23641
|
-
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
23779
|
+
const principal = await resolvePrincipalArg(opts.principal, auth, deps);
|
|
23642
23780
|
const p = await client.auth.getAccessProfile({ contextId: opts.context, principalId: principal });
|
|
23643
23781
|
if (opts.json) {
|
|
23644
23782
|
process.stdout.write(`${formatJson(p)}
|
|
@@ -23860,11 +23998,13 @@ async function runAccessExplain(opts, deps = {}) {
|
|
|
23860
23998
|
try {
|
|
23861
23999
|
let principal = opts.principal;
|
|
23862
24000
|
if (principal === SELF_PRINCIPAL_ALIAS2) {
|
|
23863
|
-
const self2 = await
|
|
24001
|
+
const self2 = await resolveSelfPrincipalForTenant(
|
|
24002
|
+
{ ...auth, client: auth.client },
|
|
24003
|
+
deps.fetchImpl
|
|
24004
|
+
);
|
|
23864
24005
|
if (!self2) {
|
|
23865
|
-
process.stderr.write(
|
|
23866
|
-
|
|
23867
|
-
);
|
|
24006
|
+
process.stderr.write(` \u2716 ${selfPrincipalUnresolvedMessage(auth.tenant, "--principal")}
|
|
24007
|
+
`);
|
|
23868
24008
|
return 1;
|
|
23869
24009
|
}
|
|
23870
24010
|
principal = self2.principalId;
|
|
@@ -24243,10 +24383,17 @@ async function runContextDestroy(opts, deps = {}) {
|
|
|
24243
24383
|
`);
|
|
24244
24384
|
return 0;
|
|
24245
24385
|
}
|
|
24386
|
+
const tenantFlag = auth.tenant === "test" ? " --tenant test" : "";
|
|
24246
24387
|
process.stdout.write(
|
|
24247
24388
|
`
|
|
24248
24389
|
\u2714 teardown of '${contextId}' started \u2014 the context and its contents are being
|
|
24249
24390
|
deleted asynchronously (it reports status 'purging' until the drain completes)
|
|
24391
|
+
|
|
24392
|
+
Not removed: a service principal a blueprint bootstrap created for this context (the
|
|
24393
|
+
externalId in the blueprint's servicePrincipal) belongs to the whole tenant, so it stays. To clean it up
|
|
24394
|
+
when it exists and nothing else uses it:
|
|
24395
|
+
vectros identity list-all${tenantFlag}
|
|
24396
|
+
vectros identity delete --type user --id <id>${tenantFlag}
|
|
24250
24397
|
`
|
|
24251
24398
|
);
|
|
24252
24399
|
return 0;
|
|
@@ -24785,7 +24932,7 @@ ${renderIssuer(issuer)}
|
|
|
24785
24932
|
var import_node_fs5 = require("fs");
|
|
24786
24933
|
var readline6 = __toESM(require("readline/promises"));
|
|
24787
24934
|
var PROVIDER_TYPES = ["ANTHROPIC_NATIVE", "OPENAI_COMPATIBLE"];
|
|
24788
|
-
var BILLING_MODES = ["
|
|
24935
|
+
var BILLING_MODES = ["CUSTOMER_DIRECT"];
|
|
24789
24936
|
var STATUSES = ["ACTIVE", "DISABLED"];
|
|
24790
24937
|
var MAX_SECRET_BYTES = 8 * 1024;
|
|
24791
24938
|
async function readBoundedSecret(stream) {
|
|
@@ -24864,12 +25011,17 @@ function renderProviderConfig(c) {
|
|
|
24864
25011
|
["defaultProviderModelId", c.defaultProviderModelId],
|
|
24865
25012
|
["waiverType", c.waiverType],
|
|
24866
25013
|
["billingMode", c.billingMode],
|
|
24867
|
-
["platformTokenFactor", c.platformTokenFactor],
|
|
24868
|
-
["platformOutputWeight", c.platformOutputWeight],
|
|
24869
25014
|
["status", c.status],
|
|
24870
25015
|
["createdAt", c.createdAt]
|
|
24871
25016
|
]);
|
|
24872
25017
|
}
|
|
25018
|
+
function billingModeError(value) {
|
|
25019
|
+
if (value === void 0 || BILLING_MODES.includes(value)) return void 0;
|
|
25020
|
+
if (value === "PLATFORM_BILLED") {
|
|
25021
|
+
return "--billing-mode PLATFORM_BILLED is retired: a provider you configure bills you directly, so CUSTOMER_DIRECT is the only billing mode.";
|
|
25022
|
+
}
|
|
25023
|
+
return invalidEnum("--billing-mode", value, BILLING_MODES);
|
|
25024
|
+
}
|
|
24873
25025
|
function invalidEnum(flag, value, allowed) {
|
|
24874
25026
|
return `${flag} must be one of ${allowed.join(", ")} (got '${value}').`;
|
|
24875
25027
|
}
|
|
@@ -24880,8 +25032,9 @@ async function runProviderConfigCreate(opts, deps = {}) {
|
|
|
24880
25032
|
`);
|
|
24881
25033
|
return 2;
|
|
24882
25034
|
}
|
|
24883
|
-
|
|
24884
|
-
|
|
25035
|
+
const billingModeProblem = billingModeError(opts.billingMode);
|
|
25036
|
+
if (billingModeProblem !== void 0) {
|
|
25037
|
+
process.stderr.write(` \u2716 ${billingModeProblem}
|
|
24885
25038
|
`);
|
|
24886
25039
|
return 2;
|
|
24887
25040
|
}
|
|
@@ -24890,19 +25043,6 @@ async function runProviderConfigCreate(opts, deps = {}) {
|
|
|
24890
25043
|
`);
|
|
24891
25044
|
return 2;
|
|
24892
25045
|
}
|
|
24893
|
-
if (opts.billingMode === "PLATFORM_BILLED") {
|
|
24894
|
-
if (opts.platformTokenFactor === void 0 || opts.platformOutputWeight === void 0) {
|
|
24895
|
-
process.stderr.write(
|
|
24896
|
-
" \u2716 --platform-token-factor and --platform-output-weight are required when --billing-mode PLATFORM_BILLED.\n"
|
|
24897
|
-
);
|
|
24898
|
-
return 2;
|
|
24899
|
-
}
|
|
24900
|
-
} else if (opts.platformTokenFactor !== void 0 || opts.platformOutputWeight !== void 0) {
|
|
24901
|
-
process.stderr.write(
|
|
24902
|
-
" \u2716 --platform-token-factor/--platform-output-weight only apply with --billing-mode PLATFORM_BILLED.\n"
|
|
24903
|
-
);
|
|
24904
|
-
return 2;
|
|
24905
|
-
}
|
|
24906
25046
|
let apiKeySecret;
|
|
24907
25047
|
try {
|
|
24908
25048
|
apiKeySecret = await resolveApiKeySecret(opts.apiKeySecretFile, deps, "required");
|
|
@@ -24927,9 +25067,7 @@ async function runProviderConfigCreate(opts, deps = {}) {
|
|
|
24927
25067
|
defaultProviderModelId: opts.defaultProviderModelId,
|
|
24928
25068
|
apiKeySecret,
|
|
24929
25069
|
waiverType: opts.waiverType,
|
|
24930
|
-
billingMode: opts.billingMode,
|
|
24931
|
-
platformTokenFactor: opts.platformTokenFactor,
|
|
24932
|
-
platformOutputWeight: opts.platformOutputWeight,
|
|
25070
|
+
billingMode: opts.billingMode ?? "CUSTOMER_DIRECT",
|
|
24933
25071
|
status: opts.status
|
|
24934
25072
|
})
|
|
24935
25073
|
});
|
|
@@ -25010,8 +25148,9 @@ async function runProviderConfigUpdate(opts, deps = {}) {
|
|
|
25010
25148
|
`);
|
|
25011
25149
|
return 2;
|
|
25012
25150
|
}
|
|
25013
|
-
|
|
25014
|
-
|
|
25151
|
+
const billingModeProblem = billingModeError(opts.billingMode);
|
|
25152
|
+
if (billingModeProblem !== void 0) {
|
|
25153
|
+
process.stderr.write(` \u2716 ${billingModeProblem}
|
|
25015
25154
|
`);
|
|
25016
25155
|
return 2;
|
|
25017
25156
|
}
|
|
@@ -25044,8 +25183,6 @@ async function runProviderConfigUpdate(opts, deps = {}) {
|
|
|
25044
25183
|
apiKeySecret,
|
|
25045
25184
|
waiverType: opts.waiverType,
|
|
25046
25185
|
billingMode: opts.billingMode,
|
|
25047
|
-
platformTokenFactor: opts.platformTokenFactor,
|
|
25048
|
-
platformOutputWeight: opts.platformOutputWeight,
|
|
25049
25186
|
status: opts.status
|
|
25050
25187
|
})
|
|
25051
25188
|
});
|
|
@@ -26622,15 +26759,14 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
|
|
|
26622
26759
|
|
|
26623
26760
|
// src/commands/join.ts
|
|
26624
26761
|
var SELF_ALIAS = "me";
|
|
26625
|
-
async function resolvePrincipal(as, auth) {
|
|
26762
|
+
async function resolvePrincipal(as, auth, deps) {
|
|
26626
26763
|
const raw = as ?? SELF_ALIAS;
|
|
26627
26764
|
if (raw === SELF_ALIAS) {
|
|
26628
|
-
const self2 = await
|
|
26629
|
-
|
|
26630
|
-
|
|
26631
|
-
|
|
26632
|
-
|
|
26633
|
-
}
|
|
26765
|
+
const self2 = await resolveSelfPrincipalForTenant(
|
|
26766
|
+
{ ...auth, client: auth.client },
|
|
26767
|
+
deps.fetchImpl
|
|
26768
|
+
);
|
|
26769
|
+
if (!self2) throw new Error(selfPrincipalUnresolvedMessage(auth.tenant, "--as"));
|
|
26634
26770
|
return self2.principalId;
|
|
26635
26771
|
}
|
|
26636
26772
|
if (!raw.startsWith("usr_")) {
|
|
@@ -26700,7 +26836,7 @@ async function runJoin(opts, deps = {}) {
|
|
|
26700
26836
|
const client = auth.client;
|
|
26701
26837
|
try {
|
|
26702
26838
|
const tenantId = requireTenantId(auth);
|
|
26703
|
-
const principalId = await resolvePrincipal(opts.as, auth);
|
|
26839
|
+
const principalId = await resolvePrincipal(opts.as, auth, deps);
|
|
26704
26840
|
const userId = normalizePrincipalUserId(principalId);
|
|
26705
26841
|
const scopes = hasActions ? [{ allowed_actions: splitCsv(opts.actions) }] : void 0;
|
|
26706
26842
|
await client.auth.createAccessProfile({
|
|
@@ -26711,7 +26847,9 @@ async function runJoin(opts, deps = {}) {
|
|
|
26711
26847
|
// Always send the plural `roleIds`, even for a single
|
|
26712
26848
|
// `--role`: a strict superset of the deprecated singular `roleId`,
|
|
26713
26849
|
// read uniformly by the platform regardless of how many compose.
|
|
26714
|
-
|
|
26850
|
+
// With `--actions` there is no role: omit `roleIds` (the platform
|
|
26851
|
+
// refuses an empty list next to inline `scopes`).
|
|
26852
|
+
roleIds: hasRole ? opts.role : void 0,
|
|
26715
26853
|
scopes,
|
|
26716
26854
|
identityOverrides
|
|
26717
26855
|
}
|
|
@@ -26798,9 +26936,9 @@ function emitJoined(opts) {
|
|
|
26798
26936
|
|
|
26799
26937
|
// src/build-info.ts
|
|
26800
26938
|
var BUILD_INFO = {
|
|
26801
|
-
cli: true ? "0.
|
|
26802
|
-
sdk: true ? "0.
|
|
26803
|
-
blueprints: true ? "0.20.
|
|
26939
|
+
cli: true ? "0.25.0" : "dev",
|
|
26940
|
+
sdk: true ? "0.47.0" : "dev",
|
|
26941
|
+
blueprints: true ? "0.20.2" : "dev"
|
|
26804
26942
|
};
|
|
26805
26943
|
function formatBuildInfo() {
|
|
26806
26944
|
return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
|
|
@@ -26837,18 +26975,6 @@ function parseTenantFlag(raw) {
|
|
|
26837
26975
|
}
|
|
26838
26976
|
return raw;
|
|
26839
26977
|
}
|
|
26840
|
-
var NUMBER_FLAG_INVALID = /* @__PURE__ */ Symbol("parseOptionalNumberFlag:invalid");
|
|
26841
|
-
function parseOptionalNumberFlag(flag, raw) {
|
|
26842
|
-
if (raw === void 0) return void 0;
|
|
26843
|
-
const n = Number(raw);
|
|
26844
|
-
if (!Number.isFinite(n)) {
|
|
26845
|
-
process.stderr.write(` \u2716 ${flag} must be a number (got '${raw}')
|
|
26846
|
-
`);
|
|
26847
|
-
process.exitCode = 2;
|
|
26848
|
-
return NUMBER_FLAG_INVALID;
|
|
26849
|
-
}
|
|
26850
|
-
return n;
|
|
26851
|
-
}
|
|
26852
26978
|
function toCommonOptions(flags) {
|
|
26853
26979
|
return {
|
|
26854
26980
|
env: flags.env,
|
|
@@ -27020,7 +27146,7 @@ function registerContextCommands(program, deps) {
|
|
|
27020
27146
|
);
|
|
27021
27147
|
});
|
|
27022
27148
|
const destroy = context.command("destroy").description(
|
|
27023
|
-
"Permanently destroy an app context and ALL of its data (irreversible; asks for confirmation)."
|
|
27149
|
+
"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'."
|
|
27024
27150
|
).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)");
|
|
27025
27151
|
addCommonAuthOptions(destroy).action(
|
|
27026
27152
|
async (contextId, flags) => {
|
|
@@ -27175,15 +27301,11 @@ function registerIssuersCommands(program, deps) {
|
|
|
27175
27301
|
}
|
|
27176
27302
|
function registerProviderConfigCommands(program, deps) {
|
|
27177
27303
|
const providerConfig = program.command("provider-config").description("Manage BYOM (bring-your-own-model) tenant-level provider configs.");
|
|
27178
|
-
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
|
|
27304
|
+
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)");
|
|
27179
27305
|
addCommonAuthOptions(create).action(
|
|
27180
27306
|
async (flags) => {
|
|
27181
27307
|
const tenant = parseTenantFlag(flags.tenant);
|
|
27182
27308
|
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27183
|
-
const platformTokenFactor = parseOptionalNumberFlag("--platform-token-factor", flags.platformTokenFactor);
|
|
27184
|
-
if (platformTokenFactor === NUMBER_FLAG_INVALID) return;
|
|
27185
|
-
const platformOutputWeight = parseOptionalNumberFlag("--platform-output-weight", flags.platformOutputWeight);
|
|
27186
|
-
if (platformOutputWeight === NUMBER_FLAG_INVALID) return;
|
|
27187
27309
|
process.exitCode = await runProviderConfigCreate(
|
|
27188
27310
|
{
|
|
27189
27311
|
...toCommonOptions(flags),
|
|
@@ -27195,8 +27317,6 @@ function registerProviderConfigCommands(program, deps) {
|
|
|
27195
27317
|
apiKeySecretFile: flags.apiKeySecretFile,
|
|
27196
27318
|
waiverType: flags.waiverType,
|
|
27197
27319
|
billingMode: flags.billingMode,
|
|
27198
|
-
platformTokenFactor,
|
|
27199
|
-
platformOutputWeight,
|
|
27200
27320
|
status: flags.status,
|
|
27201
27321
|
tenant
|
|
27202
27322
|
},
|
|
@@ -27214,13 +27334,9 @@ function registerProviderConfigCommands(program, deps) {
|
|
|
27214
27334
|
addCommonAuthOptions(get).action(async (id, flags) => {
|
|
27215
27335
|
process.exitCode = await runProviderConfigGet({ ...toCommonOptions(flags), id }, deps);
|
|
27216
27336
|
});
|
|
27217
|
-
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 (
|
|
27337
|
+
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");
|
|
27218
27338
|
addCommonAuthOptions(update).action(
|
|
27219
27339
|
async (id, flags) => {
|
|
27220
|
-
const platformTokenFactor = parseOptionalNumberFlag("--platform-token-factor", flags.platformTokenFactor);
|
|
27221
|
-
if (platformTokenFactor === NUMBER_FLAG_INVALID) return;
|
|
27222
|
-
const platformOutputWeight = parseOptionalNumberFlag("--platform-output-weight", flags.platformOutputWeight);
|
|
27223
|
-
if (platformOutputWeight === NUMBER_FLAG_INVALID) return;
|
|
27224
27340
|
process.exitCode = await runProviderConfigUpdate(
|
|
27225
27341
|
{
|
|
27226
27342
|
...toCommonOptions(flags),
|
|
@@ -27233,8 +27349,6 @@ function registerProviderConfigCommands(program, deps) {
|
|
|
27233
27349
|
apiKeySecretFile: flags.apiKeySecretFile,
|
|
27234
27350
|
waiverType: flags.waiverType,
|
|
27235
27351
|
billingMode: flags.billingMode,
|
|
27236
|
-
platformTokenFactor,
|
|
27237
|
-
platformOutputWeight,
|
|
27238
27352
|
status: flags.status
|
|
27239
27353
|
},
|
|
27240
27354
|
deps
|
|
@@ -27502,12 +27616,15 @@ function dimensionOption() {
|
|
|
27502
27616
|
).makeOptionMandatory();
|
|
27503
27617
|
}
|
|
27504
27618
|
function registerIdentityCommands(program, deps) {
|
|
27505
|
-
const identity = program.command("identity").description("Manage tenant-wide
|
|
27506
|
-
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");
|
|
27619
|
+
const identity = program.command("identity").description("Manage principals: users (tenant-wide) and namespaced entities (an entity lives with its namespace) \u2014 reference, never embed.");
|
|
27620
|
+
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)");
|
|
27507
27621
|
addCommonAuthOptions(create).action(
|
|
27508
27622
|
async (flags) => {
|
|
27623
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27624
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27509
27625
|
process.exitCode = await runIdentityCreate({
|
|
27510
27626
|
...toCommonOptions(flags),
|
|
27627
|
+
tenant,
|
|
27511
27628
|
type: flags.type,
|
|
27512
27629
|
externalId: flags.externalId,
|
|
27513
27630
|
name: flags.name,
|
|
@@ -27518,11 +27635,14 @@ function registerIdentityCommands(program, deps) {
|
|
|
27518
27635
|
}, deps);
|
|
27519
27636
|
}
|
|
27520
27637
|
);
|
|
27521
|
-
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));
|
|
27638
|
+
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)");
|
|
27522
27639
|
addCommonAuthOptions(list).action(
|
|
27523
27640
|
async (flags) => {
|
|
27641
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27642
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27524
27643
|
process.exitCode = await runIdentityList({
|
|
27525
27644
|
...toCommonOptions(flags),
|
|
27645
|
+
tenant,
|
|
27526
27646
|
type: flags.type,
|
|
27527
27647
|
externalId: flags.externalId,
|
|
27528
27648
|
limit: flags.limit
|
|
@@ -27542,20 +27662,30 @@ function registerIdentityCommands(program, deps) {
|
|
|
27542
27662
|
);
|
|
27543
27663
|
}
|
|
27544
27664
|
);
|
|
27545
|
-
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)");
|
|
27665
|
+
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)");
|
|
27546
27666
|
addCommonAuthOptions(get).action(async (flags) => {
|
|
27547
|
-
|
|
27667
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27668
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27669
|
+
process.exitCode = await runIdentityGet({ ...toCommonOptions(flags), tenant, type: flags.type, id: flags.id }, deps);
|
|
27548
27670
|
});
|
|
27549
|
-
const del = identity.command("delete").description("Delete a principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id");
|
|
27671
|
+
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)");
|
|
27550
27672
|
addAuthOptions(del).action(async (flags) => {
|
|
27551
|
-
|
|
27673
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27674
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27675
|
+
process.exitCode = await runIdentityDelete(
|
|
27676
|
+
{ ...toCommonOptions(flags), tenant, type: flags.type, id: flags.id },
|
|
27677
|
+
deps
|
|
27678
|
+
);
|
|
27552
27679
|
});
|
|
27553
|
-
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");
|
|
27680
|
+
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)");
|
|
27554
27681
|
addCommonAuthOptions(invite).action(
|
|
27555
27682
|
async (flags) => {
|
|
27683
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27684
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27556
27685
|
process.exitCode = await runIdentityInvite(
|
|
27557
27686
|
{
|
|
27558
27687
|
...toCommonOptions(flags),
|
|
27688
|
+
tenant,
|
|
27559
27689
|
context: flags.context,
|
|
27560
27690
|
role: flags.role,
|
|
27561
27691
|
email: flags.email,
|