@vectros-ai/cli 0.21.0 → 0.22.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 +41 -8
- package/CHANGELOG.md +187 -1
- package/README.md +6 -8
- package/THIRD-PARTY-NOTICES.md +2 -3
- package/dist/cli.js +728 -385
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +728 -385
- 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
|
-
//
|
|
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)
|
|
@@ -5739,7 +5739,7 @@ var AuthClient = class {
|
|
|
5739
5739
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/issuers/{issuerId}");
|
|
5740
5740
|
}
|
|
5741
5741
|
/**
|
|
5742
|
-
* Updates the mutable fields of a registered issuer: `subClaim`, `emailClaim`, `status` (`active`/`suspended` — 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. 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.
|
|
5742
|
+
* Updates the mutable fields of a registered issuer: `subClaim`, `emailClaim`, `userinfoUri`, `capturedClaims`, `restrictedToDomain` (#1250 Tier 1a — a new non-blank value must already be a VERIFIED domain for your account; an empty string clears it back to domain-less; see `IssuerUpdateRequest.restrictedToDomain` for the full semantics), `status` (`active`/`suspended` — 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.
|
|
5743
5743
|
*
|
|
5744
5744
|
* @param {Vectros.IssuerUpdateRequest} request
|
|
5745
5745
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5806,7 +5806,7 @@ var AuthClient = class {
|
|
|
5806
5806
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/auth/issuers/{issuerId}");
|
|
5807
5807
|
}
|
|
5808
5808
|
/**
|
|
5809
|
-
* Deregisters a trusted third-party IdP issuer. Requires a root API key or the bootstrap's provisioning capability. A credential confined to one app context may only deregister an issuer registered in that context; naming one registered in another context returns 404, identically to a nonexistent issuerId. A root API key may deregister any issuer. Refused if any user account was ever created or matched via this issuer (by a prior self-signup or accepted invite, through `POST /v1/auth/token/exchange`) — that access cannot be silently orphaned.
|
|
5809
|
+
* Deregisters a trusted third-party IdP issuer. Requires a root API key or the bootstrap's provisioning capability. A credential confined to one app context may only deregister an issuer registered in that context; naming one registered in another context returns 404, identically to a nonexistent issuerId. A root API key may deregister any issuer. Refused if any user account was ever created or matched via this issuer (by a prior self-signup or accepted invite, through `POST /v1/auth/token/exchange`) — that access cannot be silently orphaned. This is unconditional on the affected users' `status`: suspending them first does not lift the refusal. **A bound registration cannot be replaced, self-service, within its own app context**: registering a replacement issuer under a different issuerId in the SAME context also fails, because the context's one-active-issuer claim is released only when THIS registration is deleted. Two real options: suspend this issuer (stops new exchanges immediately) and register a replacement under a DIFFERENT app context — note that targets a different context, so this context's existing users/roles/access profiles are not carried over; or contact your platform operator, who can force-release this registration so a replacement may be registered in the SAME context under a new issuerId. **The operator path is not a lighter-weight alternative to the first — it PERMANENTLY RETIRES this issuerId and leaves every bound user unable to ever exchange through it again; it does not preserve continuity for them any better than registering under a different context does.** An issuer that has never been used for an exchange (no bound users yet) can always be deregistered.
|
|
5810
5810
|
*
|
|
5811
5811
|
* @param {Vectros.DeleteIssuerRequest} request
|
|
5812
5812
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5932,7 +5932,7 @@ var AuthClient = class {
|
|
|
5932
5932
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/issuers");
|
|
5933
5933
|
}
|
|
5934
5934
|
/**
|
|
5935
|
-
* Registers a trusted third-party IdP issuer that BYO-IdP token exchange (`POST /v1/auth/token/exchange`) may accept a `subject_token` from. Requires a root API key or the CLI bootstrap's provisioning capability — never an ordinary partner-grantable scope. A credential authorized only via the provisioning capability may register only against the app context it is bound to; naming a different one returns 403. A root API key is unaffected and may register against any of its contexts. Idempotent by `issuerId` within your tenant; the `(issuer, audience)` pair must not already be registered by a different issuerId/tenant. If `issuerId` collides with a registration owned by a different app context than the one you're confined to, the request fails with 400 rather than returning that context's configuration. An app context may have at most one active issuer — deregister the existing one first if you need to replace it. One issuer MAY serve several contexts today, each via its own registration row with a distinct `audience`.
|
|
5935
|
+
* Registers a trusted third-party IdP issuer that BYO-IdP token exchange (`POST /v1/auth/token/exchange`) may accept a `subject_token` from. Requires a root API key or the CLI bootstrap's provisioning capability — never an ordinary partner-grantable scope. A credential authorized only via the provisioning capability may register only against the app context it is bound to; naming a different one returns 403. A root API key is unaffected and may register against any of its contexts. Idempotent by `issuerId` within your tenant; the `(issuer, audience)` pair must not already be registered by a different issuerId/tenant. If `issuerId` collides with a registration owned by a different app context than the one you're confined to, the request fails with 400 rather than returning that context's configuration. An app context may have at most one active issuer — deregister the existing one first if you need to replace it. One issuer MAY serve several contexts today, each via its own registration row with a distinct `audience`. Optionally name `restrictedToDomain` (#1250 Tier 1a) to scope this registration's (issuer, audience) uniqueness to a specific VERIFIED domain rather than the bare pair — see that field's own description for the full contract, including why it does NOT protect a shared/consumer-IdP registration with no company-domain population.
|
|
5936
5936
|
*
|
|
5937
5937
|
* @param {Vectros.IssuerRequest} request
|
|
5938
5938
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -7430,7 +7430,7 @@ var DocumentsClient = class {
|
|
|
7430
7430
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/versions");
|
|
7431
7431
|
}
|
|
7432
7432
|
/**
|
|
7433
|
-
* Starts a file-based document by returning a short-lived presigned S3 PUT URL. Upload the file bytes directly to `uploadUrl`; the document is then automatically queued for text extraction and asynchronous indexing. Supplying an `externalId` makes this idempotent — re-initiating an upload with the same `externalId` re-issues a fresh presigned URL
|
|
7433
|
+
* Starts a file-based document by returning a short-lived presigned S3 PUT URL. Upload the file bytes directly to `uploadUrl`; the document is then automatically queued for text extraction and asynchronous indexing. Supplying an `externalId` makes this idempotent — re-initiating an upload with the same `externalId` re-issues a fresh presigned URL for the SAME existing document, targeting a newly-staged object rather than the document's current one — your PUT lands there first, and the document only adopts it once the upload is validated (so a re-upload inherently replaces the file body) rather than creating a duplicate. The response's `created` field (and the HTTP status — 201 when a new document was minted, 200 when an existing one was matched) tells the two apart. With `?upsert=true`, the submitted `payload`/`title` are also applied to the matched document (file-body divergence cannot be diffed at upload-init — the bytes have not arrived yet — so the re-upload itself replaces the body). Creating a NEW document requires the `documents:c` scope. Re-uploading over an EXISTING document overwrites (and re-indexes) its body, so it is an update: it requires the `documents:u` scope (as does `?upsert=true` for the metadata). **Your PUT to `uploadUrl` MUST include the header named in the response's `requiredHeaderName`, set to `requiredHeaderValue` exactly.** It is a signed part of the presigned request — the PUT never reaches this service either way, so neither failure uses this API's JSON error shape. Omitting it, or sending a different value, invalidates the request's signature and S3 rejects it with a 403 `SignatureDoesNotMatch`. Sending it correctly makes the URL single-use: `requiredHeaderName` is always `If-None-Match` and `requiredHeaderValue` is always `*` — a create, a `#559` heal recovery, and a re-upload ALL target a freshly-minted, never-written key, so the same S3 conditional-write precondition applies uniformly (S3's own documented conditional-write behavior): a replay after the object already exists (from your own completed PUT, or anyone else's) fails with `412 Precondition Failed`; a replay after the object was deleted and never re-created finds an absent key again and can succeed — single-use is a property of the object's existence, not a one-time token. **Either precondition can also fail with `409 Conflict` if your PUT genuinely races a concurrent delete on the same object.** Whichever of `412` or `409` you receive, the remedy is the same: call this endpoint again for a fresh URL rather than retrying the same PUT — a fresh call always targets a new key, so it is never subject to whatever raced the previous one.
|
|
7434
7434
|
*
|
|
7435
7435
|
* @param {Vectros.FileUploadRequest} request
|
|
7436
7436
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -9196,7 +9196,7 @@ var IdentityClient = class {
|
|
|
9196
9196
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/users/exists-by-email");
|
|
9197
9197
|
}
|
|
9198
9198
|
/**
|
|
9199
|
-
* Returns the audit trail of changes to a user, most recent first. Identity history is always recorded
|
|
9199
|
+
* Returns the audit trail of changes to a user, most recent first, including after the user has been deleted. Identity history is always recorded. Post-delete, this is readable by an account-level (root) API key only — a context-confined credential 404s after the delete, the same response it would get for a nonexistent id. Sensitive field values are redacted in every historical version. Returns a page in the `{data, nextCursor}` envelope. Requires the `users:r` scope.
|
|
9200
9200
|
*
|
|
9201
9201
|
* @param {Vectros.GetUserVersionsRequest} request
|
|
9202
9202
|
* @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -10673,7 +10673,7 @@ var SchemasClient = class {
|
|
|
10673
10673
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/schemas/{id}");
|
|
10674
10674
|
}
|
|
10675
10675
|
/**
|
|
10676
|
-
* Permanently deletes a record schema. The request is refused with 409 if records of this type still exist — delete those records first, since every record must reference a live schema. A lineage base (a schema other schemas declare `basedOn`) also cannot be deleted while any such variant still exists — delete the variant schema(s) first. It is likewise refused while any trigger rule fires off this schema — delete those trigger rules first. Requires the `schemas:d` scope.
|
|
10676
|
+
* Permanently deletes a record schema. The request is refused with 409 if records of this type still exist — delete those records first, since every record must reference a live schema. The same applies to documents bound to this schema (for a schema declaring the `document` surface) — delete those documents first. A lineage base (a schema other schemas declare `basedOn`) also cannot be deleted while any such variant still exists — delete the variant schema(s) first. It is likewise refused while any trigger rule fires off this schema — delete those trigger rules first. Requires the `schemas:d` scope.
|
|
10677
10677
|
*
|
|
10678
10678
|
* @param {Vectros.DeleteSchemaRequest} request
|
|
10679
10679
|
* @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -10808,7 +10808,7 @@ var ScriptsClient = class {
|
|
|
10808
10808
|
*
|
|
10809
10809
|
* **Budget.** Seconds of useful work: the script has 15 s of wall clock (never more than 16) and a bounded number of statements and host calls; a runaway script is stopped early with `RESOURCE_LIMIT_EXCEEDED`. The transaction is bounded by the number of underlying storage rows it writes — 100 rows after merging — rather than by the number of entities: creating a folder costs 3 rows; creating a plain text document or a record costs 1 row plus one per range-indexed lookup field plus one per `reference` field. So "create a folder and file N documents" holds up to 97 plain documents, 48 with one range field or one reference, 32 with both; if a script exceeds the limit the error says so (`WRITE_BUFFER_CAP_EXCEEDED`, with `total` and `limit`) and nothing is committed. This request takes the write-path limits (burst, credit ceiling, principal quota) once, before the script runs — there is no free-read allowance here, so an account over its credit ceiling is refused even for a read-only script. Reads the script performs are metered as reads, writes as writes, and execution time beyond what those operations include is charged as script execution time (see `GET /v1/usage`).
|
|
10810
10810
|
*
|
|
10811
|
-
* **Reads inside the script.** A `get` sees the script's own staged writes. A list, query or lookup never shows a row the script has deleted, shows a row it re-saved with the updated content, and does not
|
|
10811
|
+
* **Reads inside the script.** A `get` sees the script's own staged writes. A list, query or lookup never shows a row the script has deleted, shows a row it re-saved with the updated content, and does not show a row it created — you hold that row's id from `create()`.
|
|
10812
10812
|
*
|
|
10813
10813
|
* **Retries.** Send an optional `Idempotency-Key` header (1–128 characters of letters, digits, `.`, `_`, `:` or `-`) to make a retry safe: a request repeated under the same key within 24 hours receives the same response as the first attempt without executing again — when that first attempt SUCCEEDED (a duplicate still in flight is refused with `409 IDEMPOTENCY_IN_PROGRESS`; the same key on a different request is refused with `422 IDEMPOTENCY_KEY_REUSED`). A first attempt that FAILED with any other status is not remembered: the same key is free to try again, so a retry after a `400` or a `429` executes. If an execution's outcome cannot be determined — the script did not finish inside the budget while its writes may have committed — the response is `500 EXECUTION_OUTCOME_UNKNOWN` carrying `execution.id`, and a keyed retry receives that same answer: inspect what the script would have written, then use a NEW key for a new attempt. When a key is present the serialised `result` is capped at 16 KB instead of 256 KB. Without a key, a lost response cannot be distinguished from a failed one; re-read before retrying.
|
|
10814
10814
|
*
|
|
@@ -10903,11 +10903,14 @@ var ScriptsClient = class {
|
|
|
10903
10903
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/scripts/execute");
|
|
10904
10904
|
}
|
|
10905
10905
|
/**
|
|
10906
|
-
* Returns a paginated list of your scripts. Pass `name` to list every version of one script name (oldest first); omit it for a flat list across every name in your account (newest first). Results are returned as a `{data, nextCursor}` envelope — pass `nextCursor` as `startFrom` to fetch the next page. Requires the `scripts:r` scope.
|
|
10906
|
+
* Returns a paginated list of your scripts. Pass `name` to list every version of one script name (oldest first); omit it for a flat list across every name in your account (newest first). Results are returned as a `{data, nextCursor}` envelope — pass `nextCursor` as `startFrom` to fetch the next page. Requires the `scripts:r` scope. **Each row's `source` is omitted by default** (`sourceOmitted: true`) — scripts are immutable per version, so a name's version history can otherwise carry every version's complete source in one response. Pass `?includeSource=true` to get it back inline, or fetch one version's full source with a by-id GET. **`?name=<n>&latest=true`** answers "what is the current version of this name" in one bounded call — a single `ScriptResponse` object (not a page), instead of draining every page of the name's version history to find the newest yourself.
|
|
10907
10907
|
*
|
|
10908
10908
|
* @param {Vectros.ListScriptsRequest} request
|
|
10909
10909
|
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
10910
10910
|
*
|
|
10911
|
+
* @throws {@link Vectros.BadRequestError}
|
|
10912
|
+
* @throws {@link Vectros.NotFoundError}
|
|
10913
|
+
*
|
|
10911
10914
|
* @example
|
|
10912
10915
|
* await client.scripts.listScripts({
|
|
10913
10916
|
* name: "notify-assignee",
|
|
@@ -10918,11 +10921,13 @@ var ScriptsClient = class {
|
|
|
10918
10921
|
return HttpResponsePromise.fromPromise(this.__listScripts(request, requestOptions));
|
|
10919
10922
|
}
|
|
10920
10923
|
async __listScripts(request = {}, requestOptions) {
|
|
10921
|
-
const { name, startFrom, limit } = request;
|
|
10924
|
+
const { name, startFrom, limit, includeSource, latest } = request;
|
|
10922
10925
|
const _queryParams = {
|
|
10923
10926
|
name,
|
|
10924
10927
|
startFrom,
|
|
10925
|
-
limit
|
|
10928
|
+
limit,
|
|
10929
|
+
includeSource,
|
|
10930
|
+
latest
|
|
10926
10931
|
};
|
|
10927
10932
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
10928
10933
|
const _headers = mergeHeaders(
|
|
@@ -10948,16 +10953,23 @@ var ScriptsClient = class {
|
|
|
10948
10953
|
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
10949
10954
|
}
|
|
10950
10955
|
if (_response.error.reason === "status-code") {
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
+
switch (_response.error.statusCode) {
|
|
10957
|
+
case 400:
|
|
10958
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
10959
|
+
case 404:
|
|
10960
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
10961
|
+
default:
|
|
10962
|
+
throw new VectrosError({
|
|
10963
|
+
statusCode: _response.error.statusCode,
|
|
10964
|
+
body: _response.error.body,
|
|
10965
|
+
rawResponse: _response.rawResponse
|
|
10966
|
+
});
|
|
10967
|
+
}
|
|
10956
10968
|
}
|
|
10957
10969
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/scripts");
|
|
10958
10970
|
}
|
|
10959
10971
|
/**
|
|
10960
|
-
* Stores a new version of a script object. Every POST creates a genuinely new version — this is NOT idempotent-by-name: pushing `name` again always creates a new row with `scriptVersion` auto-incremented (the first version pushed for a name is 1). `source` is stored as-is: nothing parses or validates it at push time. A version runs when a trigger rule references it (`POST /v1/triggers`) or when `POST /v1/scripts/execute` names it. Requires the `scripts:c` scope.
|
|
10972
|
+
* Stores a new version of a script object. Every POST creates a genuinely new version — this is NOT idempotent-by-name: pushing `name` again always creates a new row with `scriptVersion` auto-incremented (the first version pushed for a name is 1). `source` is stored as-is: nothing parses or validates it at push time. A version runs when a trigger rule references it (`POST /v1/triggers`) or when `POST /v1/scripts/execute` names it. `provisionedBy` MAY BE SET ON ABSENT, NEVER CHANGED, evaluated against the CURRENT LATEST version of this `name` at push time: if the latest version has no marker recorded, this push may set any value; if the latest version already has one, an omitted value inherits it forward and a DIFFERENT value is refused — a live comparison against whichever version is newest at push time, not a value fixed for the name's whole history. Pushing onto an EXISTING name with no `provisionedBy` recorded on its current latest version additionally requires that you were the caller who pushed that latest version — a name carrying a `provisionedBy` marker is exempt from this check (proving you can supply or inherit the matching marker is itself the ownership proof there). Requires the `scripts:c` scope.
|
|
10961
10973
|
*
|
|
10962
10974
|
* @param {Vectros.ScriptRequest} request
|
|
10963
10975
|
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -11144,12 +11156,13 @@ var ScriptsClient = class {
|
|
|
11144
11156
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/scripts/{id}");
|
|
11145
11157
|
}
|
|
11146
11158
|
/**
|
|
11147
|
-
* Permanently deletes one script version by ID. Deleting one version does not affect any other version of the same name. Requires the `scripts:d` scope.
|
|
11159
|
+
* Permanently deletes one script version by ID. Deleting one version does not affect any other version of the same name. Requires the `scripts:d` scope. Refused with 409 if a live trigger rule still references this version — either directly (a pinned `scriptRef`), or via `"latest"` when this IS the newest version of its name. Delete or re-point those rules first.
|
|
11148
11160
|
*
|
|
11149
11161
|
* @param {Vectros.DeleteScriptRequest} request
|
|
11150
11162
|
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11151
11163
|
*
|
|
11152
11164
|
* @throws {@link Vectros.NotFoundError}
|
|
11165
|
+
* @throws {@link Vectros.ConflictError}
|
|
11153
11166
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
11154
11167
|
*
|
|
11155
11168
|
* @example
|
|
@@ -11189,6 +11202,8 @@ var ScriptsClient = class {
|
|
|
11189
11202
|
switch (_response.error.statusCode) {
|
|
11190
11203
|
case 404:
|
|
11191
11204
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
11205
|
+
case 409:
|
|
11206
|
+
throw new ConflictError(_response.error.body, _response.rawResponse);
|
|
11192
11207
|
case 429:
|
|
11193
11208
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11194
11209
|
default:
|
|
@@ -11734,6 +11749,25 @@ var logging;
|
|
|
11734
11749
|
import { z } from "zod";
|
|
11735
11750
|
import { z as z2 } from "zod";
|
|
11736
11751
|
var CONTEXT_ID_RE = /^[a-z][a-z0-9-]{2,30}$/;
|
|
11752
|
+
var FORGERY_CHARS_RE = /[\x00-\x1f\x7f-\x9f\u00ad\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2060\u2066-\u2069\ufeff\u{e0000}-\u{e007f}]/u;
|
|
11753
|
+
function safeDisplayString(base) {
|
|
11754
|
+
return base.refine((v) => !FORGERY_CHARS_RE.test(v), {
|
|
11755
|
+
message: "must not contain control characters, invisible/zero-width characters, line/paragraph separators, or bidi marks/overrides \u2014 these can forge, hide, or reorder text in a rendered plan preview"
|
|
11756
|
+
});
|
|
11757
|
+
}
|
|
11758
|
+
function safeUrlString(base) {
|
|
11759
|
+
return base.refine(
|
|
11760
|
+
(v) => {
|
|
11761
|
+
if (!/^[\x20-\x7e]*$/.test(v)) return false;
|
|
11762
|
+
const authorityMatch = /^https:\/\/([^/?#]*)/i.exec(v);
|
|
11763
|
+
if (!authorityMatch) return false;
|
|
11764
|
+
return /^[A-Za-z0-9.-]+(:\d+)?$/.test(authorityMatch[1]);
|
|
11765
|
+
},
|
|
11766
|
+
{
|
|
11767
|
+
message: "must be a printable-ASCII https:// URL whose authority is only letters, digits, dot, dash, and an optional port \u2014 no userinfo ('@'), percent-encoding, or non-ASCII host (including a look-alike/homoglyph domain) is accepted; supply an internationalized domain in its punycode (xn--\u2026) form"
|
|
11768
|
+
}
|
|
11769
|
+
);
|
|
11770
|
+
}
|
|
11737
11771
|
var MAX_SCOPE_DIMENSIONS = 2;
|
|
11738
11772
|
var ValidationRulesSchema = z.object({
|
|
11739
11773
|
required: z.boolean().optional(),
|
|
@@ -11763,7 +11797,7 @@ var RenderHintsSchema = z.object({
|
|
|
11763
11797
|
displayField: z.boolean().optional()
|
|
11764
11798
|
}).strict();
|
|
11765
11799
|
var BlueprintFieldDefSchema = z.object({
|
|
11766
|
-
fieldId: z.string().min(1),
|
|
11800
|
+
fieldId: safeDisplayString(z.string().min(1)),
|
|
11767
11801
|
fieldType: z.string().min(1),
|
|
11768
11802
|
required: z.boolean().optional(),
|
|
11769
11803
|
searchable: z.boolean().optional(),
|
|
@@ -11854,13 +11888,13 @@ var BlueprintFieldDefSchema = z.object({
|
|
|
11854
11888
|
}
|
|
11855
11889
|
});
|
|
11856
11890
|
var BlueprintLookupFieldSchema = z.union([
|
|
11857
|
-
z.string().min(1),
|
|
11891
|
+
safeDisplayString(z.string().min(1)),
|
|
11858
11892
|
z.object({
|
|
11859
11893
|
// A lookup field declares either ONE field (fieldName) or 2-3 fields
|
|
11860
11894
|
// matched together (fieldNames) — never both, never neither. `.strict()`
|
|
11861
11895
|
// below only rejects unknown KEYS; it does not enforce this combination,
|
|
11862
11896
|
// so the `.superRefine()` after it carries the actual rule.
|
|
11863
|
-
fieldName: z.string().min(1).optional(),
|
|
11897
|
+
fieldName: safeDisplayString(z.string().min(1)).optional(),
|
|
11864
11898
|
// Composite (conjunctive) lookup: match on all of these fields at once —
|
|
11865
11899
|
// "every record where `status` is `open` AND `area` is `billing`", exact,
|
|
11866
11900
|
// in a stable order. Order is significant and locked at create: a query
|
|
@@ -11869,7 +11903,7 @@ var BlueprintLookupFieldSchema = z.union([
|
|
|
11869
11903
|
// lookup for that. Record-only: the schema's `allowedSurfaces` must be
|
|
11870
11904
|
// exactly `['record']` (checked at the schema level below) — the
|
|
11871
11905
|
// platform's composite index has no document/user/entity reader yet.
|
|
11872
|
-
fieldNames: z.array(z.string().min(1)).min(2).max(3).optional(),
|
|
11906
|
+
fieldNames: z.array(safeDisplayString(z.string().min(1))).min(2).max(3).optional(),
|
|
11873
11907
|
// Not available on a composite (`fieldNames`) lookup — checked below.
|
|
11874
11908
|
unique: z.boolean().optional(),
|
|
11875
11909
|
// Opt this field into ordered range + prefix lookups (from/to/prefix) on
|
|
@@ -11919,7 +11953,14 @@ var BlueprintCapabilitiesSchema = z.object({
|
|
|
11919
11953
|
triggersEnabled: z.boolean().optional()
|
|
11920
11954
|
}).strict();
|
|
11921
11955
|
var BlueprintSchemaSchema = z.object({
|
|
11922
|
-
|
|
11956
|
+
// safeDisplayString: buildPlan's schema step renders this raw into a single summary
|
|
11957
|
+
// line (`schema '${s.typeName}' (...)`) — a gap found after the rest of this file's
|
|
11958
|
+
// forgery-character pass had already shipped, the same "unconstrained free-text field
|
|
11959
|
+
// reaches a rendered plan line" shape as issuer/allowedActions/etc. `displayName`
|
|
11960
|
+
// here is NOT wrapped: unlike
|
|
11961
|
+
// servicePrincipal's own displayName, this one is never interpolated into a rendered
|
|
11962
|
+
// summary anywhere in loader.ts today (only forwarded to the schema-upsert payload).
|
|
11963
|
+
typeName: safeDisplayString(z.string().min(1)),
|
|
11923
11964
|
displayName: z.string().min(1),
|
|
11924
11965
|
description: z.string().optional(),
|
|
11925
11966
|
// Default search-index mode for instances of this type. HYBRID, SEMANTIC and
|
|
@@ -12056,7 +12097,7 @@ var BlueprintAccessProfileSchema = z.object({
|
|
|
12056
12097
|
// `.superRefine` on this schema enforces the XOR, mirroring the platform's
|
|
12057
12098
|
// own access-profile scopes/role-composition XOR check). Exactly one of
|
|
12058
12099
|
// the two authors this profile's grant.
|
|
12059
|
-
allowedActions: z.array(z.string().min(1)).min(1).optional(),
|
|
12100
|
+
allowedActions: z.array(safeDisplayString(z.string().min(1))).min(1).optional(),
|
|
12060
12101
|
// Additive role composition: this profile's effective grant is
|
|
12061
12102
|
// each named role's own clauses, concatenated in the order listed, instead of
|
|
12062
12103
|
// an inline clause authored here. Every id must name a role this SAME
|
|
@@ -12085,7 +12126,14 @@ var BlueprintAccessProfileSchema = z.object({
|
|
|
12085
12126
|
// NOT see tenant-level/seed records). Every KEY is validated below
|
|
12086
12127
|
// (`lintDataScopeKeys`) — `userId` or a grammar-valid `scope:<ns>`, no
|
|
12087
12128
|
// other shape.
|
|
12088
|
-
|
|
12129
|
+
// safeDisplayString on the value: not currently rendered by VALUE anywhere in buildPlan
|
|
12130
|
+
// (only `Object.keys(...)` of a profile's dataScope is shown today, unlike a role clause's
|
|
12131
|
+
// dataScope, which `renderPlanDataScope` prints values for — the reason that sibling field
|
|
12132
|
+
// WAS wrapped). Wrapped anyway for consistency with every other dataScope-shaped field in
|
|
12133
|
+
// this file, and so a future preview enhancement that starts showing these values (a very
|
|
12134
|
+
// plausible one, given the existing renderPlanDataScope precedent) doesn't silently reopen
|
|
12135
|
+
// this exact class on a field that looks, byte-for-byte, like every other one already closed.
|
|
12136
|
+
dataScope: z.record(z.array(z.union([safeDisplayString(z.string().min(1)), z.null()]))).optional(),
|
|
12089
12137
|
// Optional identity overrides for the service principal's profile — scope
|
|
12090
12138
|
// values its key STAMPS onto everything it writes (a credential can only
|
|
12091
12139
|
// stamp ownership its identity carries). Keys are the namespaced `scope:<ns>`
|
|
@@ -12161,8 +12209,8 @@ var BlueprintAccessProfileSchema = z.object({
|
|
|
12161
12209
|
}
|
|
12162
12210
|
});
|
|
12163
12211
|
var BlueprintRoleClauseSchema = z.object({
|
|
12164
|
-
allowedActions: z.array(z.string().min(1)).min(1),
|
|
12165
|
-
dataScope: z.record(z.array(z.union([z.string().min(1), z.null()]))).optional(),
|
|
12212
|
+
allowedActions: z.array(safeDisplayString(z.string().min(1))).min(1),
|
|
12213
|
+
dataScope: z.record(z.array(z.union([safeDisplayString(z.string().min(1)), z.null()]))).optional(),
|
|
12166
12214
|
dataScopeRef: z.string().min(1).optional(),
|
|
12167
12215
|
capabilities: GrantedCapabilitiesSchema,
|
|
12168
12216
|
assignableRoles: AssignableRolesSchema
|
|
@@ -12176,10 +12224,10 @@ var BlueprintRoleClauseSchema = z.object({
|
|
|
12176
12224
|
}
|
|
12177
12225
|
});
|
|
12178
12226
|
var BlueprintFragmentsSchema = z.record(
|
|
12179
|
-
z.record(z.array(z.union([z.string().min(1), z.null()])))
|
|
12227
|
+
z.record(z.array(z.union([safeDisplayString(z.string().min(1)), z.null()])))
|
|
12180
12228
|
);
|
|
12181
|
-
var BlueprintRolesSchema = z.record(z.array(BlueprintRoleClauseSchema).min(1));
|
|
12182
|
-
var BlueprintRoleAssumableSchema = z.record(z.array(z.string().min(1)).min(1));
|
|
12229
|
+
var BlueprintRolesSchema = z.record(safeDisplayString(z.string().min(1)), z.array(BlueprintRoleClauseSchema).min(1));
|
|
12230
|
+
var BlueprintRoleAssumableSchema = z.record(z.array(safeDisplayString(z.string().min(1))).min(1));
|
|
12183
12231
|
var BlueprintRoleAssumableMapSchema = z.record(BlueprintRoleAssumableSchema);
|
|
12184
12232
|
var BlueprintTriggerFiringSourceSchema = z.object({
|
|
12185
12233
|
schemaName: z.string().min(1),
|
|
@@ -12187,7 +12235,12 @@ var BlueprintTriggerFiringSourceSchema = z.object({
|
|
|
12187
12235
|
}).strict();
|
|
12188
12236
|
var SCRIPT_VERSION_INTEGER_RE = /^[1-9][0-9]*$/;
|
|
12189
12237
|
var BlueprintTriggerScriptRefSchema = z.object({
|
|
12190
|
-
|
|
12238
|
+
// safeDisplayString: rendered raw into buildPlan's trigger step (`${scriptRef.name}@...`).
|
|
12239
|
+
// When naming a script THIS blueprint ships, `name` is transitively safe already (it must
|
|
12240
|
+
// equal a `scripts` map key, itself grammar-pinned by SCRIPT_NAME_RE) — but a scriptRef
|
|
12241
|
+
// naming a script this blueprint does NOT ship is, by design, "entirely unrestricted" (see
|
|
12242
|
+
// this file's own doc on that case), so the wrap is load-bearing for exactly that path.
|
|
12243
|
+
name: safeDisplayString(z.string().min(1)),
|
|
12191
12244
|
version: z.string().min(1)
|
|
12192
12245
|
}).strict().superRefine((ref, ctx) => {
|
|
12193
12246
|
if (ref.version !== "latest" && !SCRIPT_VERSION_INTEGER_RE.test(ref.version)) {
|
|
@@ -12207,9 +12260,9 @@ var BlueprintTriggerManifestSchema = z.array(
|
|
|
12207
12260
|
var BlueprintTriggerEntrySchema = z.object({
|
|
12208
12261
|
firingSource: BlueprintTriggerFiringSourceSchema,
|
|
12209
12262
|
scriptRef: BlueprintTriggerScriptRefSchema,
|
|
12210
|
-
allowedActions: z.array(z.string().min(1)).min(1).optional(),
|
|
12263
|
+
allowedActions: z.array(safeDisplayString(z.string().min(1))).min(1).optional(),
|
|
12211
12264
|
roleIds: z.array(z.string().min(1)).min(1).optional(),
|
|
12212
|
-
dataScope: z.record(z.array(z.union([z.string().min(1), z.null()]))).optional(),
|
|
12265
|
+
dataScope: z.record(z.array(z.union([safeDisplayString(z.string().min(1)), z.null()]))).optional(),
|
|
12213
12266
|
capabilities: GrantedCapabilitiesSchema,
|
|
12214
12267
|
manifest: BlueprintTriggerManifestSchema,
|
|
12215
12268
|
/**
|
|
@@ -12233,7 +12286,7 @@ var BlueprintTriggerEntrySchema = z.object({
|
|
|
12233
12286
|
* together are capped at 224 KB serialised — a firing whose projected input exceeds that is not
|
|
12234
12287
|
* run, and is recorded as an `INPUT_TOO_LARGE` failure naming the size.
|
|
12235
12288
|
*/
|
|
12236
|
-
fields: z.array(z.string().min(1))
|
|
12289
|
+
fields: z.array(safeDisplayString(z.string().min(1)))
|
|
12237
12290
|
}).strict().superRefine((entry, ctx) => {
|
|
12238
12291
|
const hasInline = entry.allowedActions !== void 0;
|
|
12239
12292
|
const hasRoleIds = entry.roleIds !== void 0;
|
|
@@ -12262,7 +12315,7 @@ var BlueprintTriggerEntrySchema = z.object({
|
|
|
12262
12315
|
}
|
|
12263
12316
|
}
|
|
12264
12317
|
});
|
|
12265
|
-
var BlueprintTriggersSchema = z.record(BlueprintTriggerEntrySchema);
|
|
12318
|
+
var BlueprintTriggersSchema = z.record(safeDisplayString(z.string().min(1)), BlueprintTriggerEntrySchema);
|
|
12266
12319
|
var SCRIPT_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
12267
12320
|
var SCRIPT_SOURCE_MAX_BYTES = 3e5;
|
|
12268
12321
|
var SCRIPT_INPUT_CONTRACT_MAX_BYTES = 5e4;
|
|
@@ -12308,8 +12361,15 @@ var IdentityKindSchema = z.string().min(1).superRefine((val, ctx) => {
|
|
|
12308
12361
|
});
|
|
12309
12362
|
var IdentityDeclSchema = z.object({
|
|
12310
12363
|
kind: IdentityKindSchema,
|
|
12311
|
-
|
|
12312
|
-
|
|
12364
|
+
// safeDisplayString: `@vectros-ai/cli`'s `runBootstrapCli` and `blueprint plan` both print
|
|
12365
|
+
// this RAW to stdout ("~ <name> (<kind>) — external id '<externalId>'"), immediately before
|
|
12366
|
+
// the "Apply this plan? [y/N]" confirmation gate itself — the highest-severity gap found in
|
|
12367
|
+
// this pass, since it sits at the confirmation prompt's own disclosure text, not merely
|
|
12368
|
+
// somewhere upstream of it. `displayName` is wrapped too though not currently
|
|
12369
|
+
// rendered in any preview (defense-in-depth, matching this file's practice for its adjacent
|
|
12370
|
+
// sibling fields) — it does reach a platform API payload as an entity's `name`.
|
|
12371
|
+
externalId: safeDisplayString(z.string().min(1)),
|
|
12372
|
+
displayName: safeDisplayString(z.string().min(1)).optional(),
|
|
12313
12373
|
metadata: z.record(z.unknown()).optional()
|
|
12314
12374
|
}).strict();
|
|
12315
12375
|
var IDENTITY_NAME_RE = /^[A-Za-z_]\w*$/;
|
|
@@ -12320,8 +12380,8 @@ var IdentitiesDeclSchema = z.record(
|
|
|
12320
12380
|
IdentityDeclSchema
|
|
12321
12381
|
);
|
|
12322
12382
|
var BlueprintServicePrincipalSchema = z.object({
|
|
12323
|
-
externalId: z.string().min(1),
|
|
12324
|
-
displayName: z.string().min(1)
|
|
12383
|
+
externalId: safeDisplayString(z.string().min(1)),
|
|
12384
|
+
displayName: safeDisplayString(z.string().min(1))
|
|
12325
12385
|
}).strict();
|
|
12326
12386
|
var ISSUER_ID_RE = CONTEXT_ID_RE;
|
|
12327
12387
|
var MAX_CAPTURED_CLAIMS = 20;
|
|
@@ -12347,10 +12407,17 @@ var BlueprintIssuerSchema = z.object({
|
|
|
12347
12407
|
issuerId: z.string().regex(ISSUER_ID_RE, {
|
|
12348
12408
|
message: "issuerId must be 3-31 chars, start with a lowercase letter, then lowercase letters/digits/dashes (e.g. 'auth0-prod')"
|
|
12349
12409
|
}),
|
|
12350
|
-
/**
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12410
|
+
/**
|
|
12411
|
+
* The IdP's `iss` claim value, exactly as it appears in tokens it issues. `safeUrlString`,
|
|
12412
|
+
* not `safeDisplayString`: this is the trust-anchor field the plan preview exists to
|
|
12413
|
+
* disclose, so it gets the stronger positive check (see that function's own doc) rather
|
|
12414
|
+
* than the blocklist every OTHER free-text field uses — a homoglyph host here is exactly
|
|
12415
|
+
* what an operator approving a plan must be able to trust reading. Virtually every real
|
|
12416
|
+
* OIDC issuer is URL-shaped in practice, even though the spec does not strictly require it.
|
|
12417
|
+
*/
|
|
12418
|
+
issuer: safeUrlString(z.string().min(1)),
|
|
12419
|
+
/** The IdP's remote JWKS endpoint, used to verify presented tokens' signatures. `safeUrlString` — see `issuer`'s own comment just above. */
|
|
12420
|
+
jwksUri: safeUrlString(z.string().min(1)),
|
|
12354
12421
|
/**
|
|
12355
12422
|
* The `aud` claim value a presented subject_token must carry. Must be globally
|
|
12356
12423
|
* unique in combination with `issuer` — use a distinct audience per
|
|
@@ -12378,7 +12445,9 @@ var BlueprintIssuerSchema = z.object({
|
|
|
12378
12445
|
* back to calling this endpoint (with the presented token as the bearer credential) and reads
|
|
12379
12446
|
* `emailClaim` from its JSON response instead. Omit to leave the fallback disabled.
|
|
12380
12447
|
*/
|
|
12381
|
-
|
|
12448
|
+
// `safeUrlString` — see `issuer`'s own comment above for why these three trust-anchor
|
|
12449
|
+
// fields get the stronger positive check instead of `safeDisplayString`'s blocklist.
|
|
12450
|
+
userinfoUri: safeUrlString(z.string().min(1)).optional(),
|
|
12382
12451
|
/**
|
|
12383
12452
|
* Opt-in self-service signup: a list of {signupType, roleId} pairs. When a first-time exchange
|
|
12384
12453
|
* caller presents no invite token but names a signupType matching one of these (or omits it and
|
|
@@ -12418,9 +12487,18 @@ var BlueprintIssuerSchema = z.object({
|
|
|
12418
12487
|
* separate opt-ins, and capturing a claim does not by itself expose it on a profile.
|
|
12419
12488
|
*/
|
|
12420
12489
|
capturedClaims: z.array(
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
|
|
12490
|
+
// `safeDisplayString` (a `.refine`) then the blank-name `.refine` — both ordered AFTER
|
|
12491
|
+
// `.max`, since `.refine` returns a ZodEffects, which has no `.max` (`.refine` itself
|
|
12492
|
+
// chains fine onto a ZodEffects — it's on the ZodType base, not ZodString-specific).
|
|
12493
|
+
// `.min(1)` would be a LENGTH check and would admit a whitespace-only name while the
|
|
12494
|
+
// message claims "non-blank"; the platform rejects on isBlank(). Note the value is
|
|
12495
|
+
// REJECTED, not trimmed — it reaches the wire exactly as authored, matching how the
|
|
12496
|
+
// platform stores it.
|
|
12497
|
+
safeDisplayString(
|
|
12498
|
+
z.string().max(MAX_CAPTURED_CLAIM_NAME_LENGTH, {
|
|
12499
|
+
message: `a capturedClaims entry must be at most ${MAX_CAPTURED_CLAIM_NAME_LENGTH} characters`
|
|
12500
|
+
})
|
|
12501
|
+
).refine((name) => name.trim().length > 0, {
|
|
12424
12502
|
message: "a capturedClaims entry must be a non-blank OIDC claim name"
|
|
12425
12503
|
})
|
|
12426
12504
|
).max(MAX_CAPTURED_CLAIMS, {
|
|
@@ -12591,14 +12669,24 @@ function lintNamespaces(value, ctx) {
|
|
|
12591
12669
|
});
|
|
12592
12670
|
}
|
|
12593
12671
|
var SeedCommonShape = {
|
|
12594
|
-
/**
|
|
12595
|
-
|
|
12672
|
+
/**
|
|
12673
|
+
* The schema typeName this seed instantiates (should match a declared schema).
|
|
12674
|
+
* `safeDisplayString`, matching `BlueprintSchemaSchema.typeName`: NOT currently
|
|
12675
|
+
* rendered into any plan-preview line (the seed step only counts records/documents),
|
|
12676
|
+
* but it is the same field concept and costs nothing to close now rather than leave
|
|
12677
|
+
* as a silent gap the next preview enhancement reopens.
|
|
12678
|
+
*/
|
|
12679
|
+
typeName: safeDisplayString(z.string().min(1)),
|
|
12596
12680
|
/**
|
|
12597
12681
|
* Stable, caller-supplied id — the loader's idempotency key AND the value other
|
|
12598
12682
|
* seeds resolve a `reference` against (across surfaces: a record seed may
|
|
12599
12683
|
* reference a document seed by its externalId, and vice versa).
|
|
12684
|
+
* `safeDisplayString`: not currently rendered in a plan-preview line (same as this schema's
|
|
12685
|
+
* `typeName` just above until this pass), but the same "costs nothing to close now" standard
|
|
12686
|
+
* applies — a short caller-supplied id, not free-form prose, so no YAML block-scalar
|
|
12687
|
+
* collision risk like `description`'s.
|
|
12600
12688
|
*/
|
|
12601
|
-
externalId: z.string().min(1),
|
|
12689
|
+
externalId: safeDisplayString(z.string().min(1)),
|
|
12602
12690
|
/**
|
|
12603
12691
|
* Optional scope ownership for the seeded item, as `namespace:value` entries
|
|
12604
12692
|
* (≤2, e.g. `['org:${{ identities.team }}']`; tokens substitute at apply
|
|
@@ -13257,15 +13345,31 @@ var BLUEPRINT_FIELD_PHASES = Object.freeze({
|
|
|
13257
13345
|
});
|
|
13258
13346
|
var BlueprintSchema = z.object({
|
|
13259
13347
|
/** Stable blueprint id (the `--blueprint <name>` selector + idempotency key). */
|
|
13260
|
-
name: z.string().min(1),
|
|
13261
|
-
version: z.string().min(1),
|
|
13348
|
+
name: safeDisplayString(z.string().min(1)),
|
|
13349
|
+
version: safeDisplayString(z.string().min(1)),
|
|
13350
|
+
// NOT safeDisplayString — deliberately, after that wrap broke a real, multi-line-authored
|
|
13351
|
+
// blueprint's `description` in review. `description` is never interpolated into any
|
|
13352
|
+
// rendered plan-preview line (`buildPlan`'s header prints
|
|
13353
|
+
// `name`/`version`/`contextId`, never `description` — confirmed by grep, not assumed), so
|
|
13354
|
+
// wrapping it protected a sink that does not exist. It also actively broke real content:
|
|
13355
|
+
// this is exactly the field authors reach for a YAML folded (`>`) or literal (`|`) block
|
|
13356
|
+
// scalar to write, and clip chomping (the YAML default) always leaves exactly one trailing
|
|
13357
|
+
// `\n` — which `safeDisplayString` rejects as a C0 control, indistinguishable from a
|
|
13358
|
+
// forgery attempt. See `safeDisplayString`'s own doc for why this is a property of the
|
|
13359
|
+
// wrapper, not a one-off carve-out for this field alone.
|
|
13262
13360
|
description: z.string().min(1),
|
|
13263
13361
|
/** The app-context the profile + scoped key bind to (e.g. "mcp"). */
|
|
13264
13362
|
contextId: z.string().regex(CONTEXT_ID_RE, {
|
|
13265
13363
|
message: "contextId must be 3-31 chars, start with a lowercase letter, then lowercase letters/digits/dashes (e.g. 'mcp')"
|
|
13266
13364
|
}),
|
|
13267
|
-
/**
|
|
13268
|
-
|
|
13365
|
+
/**
|
|
13366
|
+
* Human-readable app-context name; defaults to `MCP — <name>` (see {@link contextNameOf})
|
|
13367
|
+
* when absent. `safeDisplayString`: `contextNameOf` renders this raw into the FIRST line of
|
|
13368
|
+
* every `buildPlan` summary (the `context '...' (...)` step) — a gap found after the rest
|
|
13369
|
+
* of this file's forgery-character pass had already shipped; same shape as
|
|
13370
|
+
* `name`/`version`/`description` right above it.
|
|
13371
|
+
*/
|
|
13372
|
+
contextName: safeDisplayString(z.string().min(1)).optional(),
|
|
13269
13373
|
/**
|
|
13270
13374
|
* The deploying organization's own display name — distinct from {@link contextName}.
|
|
13271
13375
|
* `contextName` is the blueprint author's fixed identity for the app (the same for every
|
|
@@ -13273,8 +13377,13 @@ var BlueprintSchema = z.object({
|
|
|
13273
13377
|
* deployer-supplied `${{ inputs.x }}` value rather than hardcoded. Used for branding on
|
|
13274
13378
|
* platform-sent correspondence (e.g. invite emails) alongside `contextName` — not a
|
|
13275
13379
|
* replacement for it. See {@link companyNameOf}.
|
|
13380
|
+
*
|
|
13381
|
+
* `safeDisplayString`: not interpolated into any CLI plan-preview line today (confirmed —
|
|
13382
|
+
* `loader.ts` never reads `companyName`), but it IS forwarded to the platform for the
|
|
13383
|
+
* invite-email correspondence described above, a second class of human-read surface this
|
|
13384
|
+
* value reaches. Wrapped for the same reason as every sibling scalar identity field here.
|
|
13276
13385
|
*/
|
|
13277
|
-
companyName: z.string().min(1).optional(),
|
|
13386
|
+
companyName: safeDisplayString(z.string().min(1)).optional(),
|
|
13278
13387
|
schemas: z.array(BlueprintSchemaSchema).default([]),
|
|
13279
13388
|
accessProfile: BlueprintAccessProfileSchema,
|
|
13280
13389
|
servicePrincipal: BlueprintServicePrincipalSchema,
|
|
@@ -13351,9 +13460,16 @@ var BlueprintSchema = z.object({
|
|
|
13351
13460
|
* context's name and needs a broader scope — see `@vectros-ai/cli`'s own notes.)
|
|
13352
13461
|
*/
|
|
13353
13462
|
identityProjectionClaims: z.array(
|
|
13354
|
-
|
|
13355
|
-
|
|
13356
|
-
|
|
13463
|
+
// safeDisplayString: rendered raw into buildPlan's context step (`identity projection:
|
|
13464
|
+
// [...]`) — same rendered-plan-preview shape as `capturedClaims` (its structural pair,
|
|
13465
|
+
// and fixed alongside it originally), missed here in the first pass; same ordering rule
|
|
13466
|
+
// applies (safeDisplayString's own `.refine` before the blank-name `.refine` below,
|
|
13467
|
+
// both after `.max` since `.refine` returns a ZodEffects with no `.max`).
|
|
13468
|
+
safeDisplayString(
|
|
13469
|
+
z.string().max(MAX_CAPTURED_CLAIM_NAME_LENGTH, {
|
|
13470
|
+
message: `an identityProjectionClaims entry must be at most ${MAX_CAPTURED_CLAIM_NAME_LENGTH} characters`
|
|
13471
|
+
})
|
|
13472
|
+
).refine((name) => name.trim().length > 0, {
|
|
13357
13473
|
message: "an identityProjectionClaims entry must be a non-blank claim name"
|
|
13358
13474
|
})
|
|
13359
13475
|
).max(MAX_CAPTURED_CLAIMS, {
|
|
@@ -15568,6 +15684,9 @@ function validateBaseUrl(rawUrl, opts = {}) {
|
|
|
15568
15684
|
if (host === ALLOWED_HOST_APEX || host.endsWith(ALLOWED_HOST_SUFFIX)) {
|
|
15569
15685
|
return rawUrl;
|
|
15570
15686
|
}
|
|
15687
|
+
if ((opts.additionalAllowedHosts ?? []).some((h) => host === h.toLowerCase())) {
|
|
15688
|
+
return rawUrl;
|
|
15689
|
+
}
|
|
15571
15690
|
throw new InvalidBaseUrlError(
|
|
15572
15691
|
`Refusing base URL host "${url.host}" \u2014 not an official Vectros host (expected vectros.ai or a *.vectros.ai subdomain). Set ${INSECURE_BASE_URL_ENV}=1 to override (e.g. for a trusted local proxy); never point the CLI at an untrusted host while authenticated.`
|
|
15573
15692
|
);
|
|
@@ -15755,6 +15874,9 @@ var PER_ENV = {
|
|
|
15755
15874
|
staging: { domainPrefix: "vectros-developer-staging", region: "us-east-1" },
|
|
15756
15875
|
production: { domainPrefix: "vectros-developer", region: "us-east-1" }
|
|
15757
15876
|
};
|
|
15877
|
+
var COGNITO_ALLOWED_HOSTS = Object.values(PER_ENV).map(
|
|
15878
|
+
(p) => `${p.domainPrefix}.auth.${p.region}.amazoncognito.com`
|
|
15879
|
+
);
|
|
15758
15880
|
var BAKED_CLIENT_IDS = {
|
|
15759
15881
|
staging: "61hjvm25kkjb1eei8t2d3jkcl8",
|
|
15760
15882
|
// validated against deployed staging hosted UI (2026-06-05)
|
|
@@ -15769,13 +15891,30 @@ var MissingCognitoClientIdError = class extends Error {
|
|
|
15769
15891
|
this.name = "MissingCognitoClientIdError";
|
|
15770
15892
|
}
|
|
15771
15893
|
};
|
|
15894
|
+
var REGION_SHAPE = /^[a-z]{2}(?:-[a-z]+)+-\d+$/;
|
|
15895
|
+
var InvalidCognitoRegionError = class extends Error {
|
|
15896
|
+
constructor(region) {
|
|
15897
|
+
super(
|
|
15898
|
+
`Invalid VECTROS_COGNITO_REGION ${JSON.stringify(region)} \u2014 expected an AWS region shape (e.g. "us-east-1").`
|
|
15899
|
+
);
|
|
15900
|
+
this.name = "InvalidCognitoRegionError";
|
|
15901
|
+
}
|
|
15902
|
+
};
|
|
15772
15903
|
function resolveCognitoConfig(env) {
|
|
15773
15904
|
const per = PER_ENV[env];
|
|
15774
|
-
const
|
|
15775
|
-
const
|
|
15905
|
+
const domainOverride = process.env.VECTROS_COGNITO_DOMAIN;
|
|
15906
|
+
const regionOverride = process.env.VECTROS_COGNITO_REGION;
|
|
15907
|
+
if (domainOverride === void 0 && regionOverride !== void 0 && !REGION_SHAPE.test(regionOverride)) {
|
|
15908
|
+
throw new InvalidCognitoRegionError(regionOverride);
|
|
15909
|
+
}
|
|
15910
|
+
const region = regionOverride ?? per.region;
|
|
15911
|
+
const rawDomain = domainOverride ?? `https://${per.domainPrefix}.auth.${region}.amazoncognito.com`;
|
|
15912
|
+
const domain = validateBaseUrl(rawDomain, {
|
|
15913
|
+
additionalAllowedHosts: COGNITO_ALLOWED_HOSTS
|
|
15914
|
+
}).replace(/\/$/, "");
|
|
15776
15915
|
const clientId = process.env.VECTROS_COGNITO_CLIENT_ID ?? BAKED_CLIENT_IDS[env];
|
|
15777
15916
|
if (!clientId) throw new MissingCognitoClientIdError(env);
|
|
15778
|
-
return { domain
|
|
15917
|
+
return { domain, clientId, region };
|
|
15779
15918
|
}
|
|
15780
15919
|
var LOOPBACK_PORTS = [8976, 8977, 8978];
|
|
15781
15920
|
var OAUTH_SCOPES = ["openid", "email", "profile"];
|
|
@@ -16573,7 +16712,7 @@ async function withAuth(opts, deps, body) {
|
|
|
16573
16712
|
log
|
|
16574
16713
|
});
|
|
16575
16714
|
} catch (err) {
|
|
16576
|
-
if (err instanceof MissingCredentialError) {
|
|
16715
|
+
if (err instanceof MissingCredentialError || err instanceof InvalidBaseUrlError || err instanceof InvalidCognitoRegionError) {
|
|
16577
16716
|
process.stderr.write(` \u2716 ${err.message}
|
|
16578
16717
|
`);
|
|
16579
16718
|
return 2;
|
|
@@ -16660,15 +16799,42 @@ var SCRIPT_CONFLICT_RETRY_DELAY_MS = 750;
|
|
|
16660
16799
|
function isVersionConflict(err) {
|
|
16661
16800
|
return typeof err === "object" && err !== null && err.statusCode === 409;
|
|
16662
16801
|
}
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16802
|
+
function isForbidden(err) {
|
|
16803
|
+
return typeof err === "object" && err !== null && err.statusCode === 403;
|
|
16804
|
+
}
|
|
16805
|
+
function withAdoptionContext(err, name) {
|
|
16806
|
+
if (!(err instanceof Error) || !isForbidden(err)) return err;
|
|
16807
|
+
return new Error(
|
|
16808
|
+
`${err.message}
|
|
16809
|
+
|
|
16810
|
+
(This push was adopting '${name}' into this blueprint's management, via --allow-script-adoption \u2014 this CLI's own ownership check cleared it, but the platform refused the push itself. The platform requires EITHER the credential pushing this scoped-token bootstrap to hold 'scripts:c', OR \u2014 if '${name}' already has an unmarked version \u2014 that this same apply is running as the SAME caller identity that originally pushed it. An account OWNER always resolves to the same identity and can adopt any unmarked name; a Dev Portal SUB-USER can only adopt a name they themselves hand-pushed. If neither applies here, ask the original pusher to run this apply, push the account owner's own login first, or push it directly via 'vectros scripts push'.)`
|
|
16666
16811
|
);
|
|
16667
|
-
|
|
16668
|
-
|
|
16669
|
-
|
|
16670
|
-
|
|
16671
|
-
|
|
16812
|
+
}
|
|
16813
|
+
function isScriptNameNotFound(err) {
|
|
16814
|
+
return typeof err === "object" && err !== null && err.statusCode === 404;
|
|
16815
|
+
}
|
|
16816
|
+
function isScriptPageEnvelope(result) {
|
|
16817
|
+
return "data" in result;
|
|
16818
|
+
}
|
|
16819
|
+
async function readLatestScriptVersion(ctxClient, name) {
|
|
16820
|
+
let result;
|
|
16821
|
+
try {
|
|
16822
|
+
result = await ctxClient.scripts.listScripts({ name, latest: true, includeSource: true });
|
|
16823
|
+
} catch (err) {
|
|
16824
|
+
if (isScriptNameNotFound(err)) return void 0;
|
|
16825
|
+
throw err;
|
|
16826
|
+
}
|
|
16827
|
+
if (result == null) {
|
|
16828
|
+
throw new Error(
|
|
16829
|
+
`Script '${name}': a bounded '?latest=true' read returned an empty response body instead of a version object. This is a platform/SDK contract change this code does not understand \u2014 refusing to guess whether the name is unclaimed or a version was silently omitted.`
|
|
16830
|
+
);
|
|
16831
|
+
}
|
|
16832
|
+
if (isScriptPageEnvelope(result)) {
|
|
16833
|
+
throw new Error(
|
|
16834
|
+
`Script '${name}': a bounded '?latest=true' read unexpectedly returned a page envelope (a \`data\` array) instead of a single version. This is a platform/SDK contract change this code does not understand \u2014 refusing to guess which row is actually current.`
|
|
16835
|
+
);
|
|
16836
|
+
}
|
|
16837
|
+
return result;
|
|
16672
16838
|
}
|
|
16673
16839
|
function requireScriptId(row, name) {
|
|
16674
16840
|
if (!row.id) {
|
|
@@ -16679,8 +16845,34 @@ function requireScriptId(row, name) {
|
|
|
16679
16845
|
function scriptMatches(stored, declared) {
|
|
16680
16846
|
return stored.source === declared.source && (stored.declaredInputContract ?? void 0) === declared.declaredInputContract;
|
|
16681
16847
|
}
|
|
16848
|
+
var ScriptOwnershipConflictError = class extends Error {
|
|
16849
|
+
constructor(name, owner, blueprintName) {
|
|
16850
|
+
super(
|
|
16851
|
+
`Script '${name}' is already provisioned by a different blueprint ('${owner}', this blueprint is '${blueprintName}') \u2014 refusing to push over it. No script version is pushed (anything already provisioned earlier in this same apply is unaffected). If you intended to replace that blueprint's script, do it under ITS name; if two blueprints legitimately need distinct code, give this one a different script name.`
|
|
16852
|
+
);
|
|
16853
|
+
this.name = "ScriptOwnershipConflictError";
|
|
16854
|
+
}
|
|
16855
|
+
};
|
|
16856
|
+
var ScriptAdoptionNotAllowedError = class extends Error {
|
|
16857
|
+
constructor(name) {
|
|
16858
|
+
super(
|
|
16859
|
+
`Script '${name}' already exists in this context and carries no blueprint provenance marker \u2014 it is either hand-authored or was pushed before this blueprint managed it. Refusing to push a new version onto it without an explicit opt-in: if a pre-existing trigger rule references '${name}' via 'version: latest', that rule's own operator-authored grant would run this blueprint's code from its very next firing \u2014 the blueprint supplying the code, someone else's rule supplying the authority. No script version is pushed (anything already provisioned earlier in this same apply is unaffected). Re-run with --allow-script-adoption once you've confirmed this blueprint should take over '${name}' going forward.`
|
|
16860
|
+
);
|
|
16861
|
+
this.name = "ScriptAdoptionNotAllowedError";
|
|
16862
|
+
}
|
|
16863
|
+
};
|
|
16864
|
+
function assertScriptPushOwnable(name, latest, blueprintName, allowScriptAdoption) {
|
|
16865
|
+
if (latest === void 0) return { adopting: false };
|
|
16866
|
+
const owner = latest.provisionedBy?.trim() || void 0;
|
|
16867
|
+
if (owner === void 0) {
|
|
16868
|
+
if (!allowScriptAdoption) throw new ScriptAdoptionNotAllowedError(name);
|
|
16869
|
+
return { adopting: true };
|
|
16870
|
+
}
|
|
16871
|
+
if (owner !== blueprintName) throw new ScriptOwnershipConflictError(name, owner, blueprintName);
|
|
16872
|
+
return { adopting: false };
|
|
16873
|
+
}
|
|
16682
16874
|
async function ensureScripts(args) {
|
|
16683
|
-
const { ctxClient, scripts, log } = args;
|
|
16875
|
+
const { ctxClient, scripts, blueprintName, allowScriptAdoption, log } = args;
|
|
16684
16876
|
const actions = [];
|
|
16685
16877
|
for (const [name, entry] of Object.entries(scripts)) {
|
|
16686
16878
|
let latest = await readLatestScriptVersion(ctxClient, name);
|
|
@@ -16691,18 +16883,32 @@ async function ensureScripts(args) {
|
|
|
16691
16883
|
id: requireScriptId(latest, name),
|
|
16692
16884
|
scriptVersion: latest.scriptVersion ?? 0
|
|
16693
16885
|
});
|
|
16694
|
-
|
|
16695
|
-
|
|
16696
|
-
|
|
16697
|
-
|
|
16886
|
+
const foreignOwner = latest.provisionedBy?.trim() || void 0;
|
|
16887
|
+
if (foreignOwner !== void 0 && foreignOwner !== blueprintName) {
|
|
16888
|
+
log.warn(
|
|
16889
|
+
{ component: "loader", scriptName: name, provisionedBy: foreignOwner },
|
|
16890
|
+
`script '${name}' already matches the declared source, but is provisioned by a DIFFERENT blueprint ('${foreignOwner}') \u2014 if this blueprint also declares a 'version: latest' trigger on this name, a future push by that other blueprint will change what that trigger runs`
|
|
16891
|
+
);
|
|
16892
|
+
} else {
|
|
16893
|
+
log.debug(
|
|
16894
|
+
{ component: "loader", scriptName: name, scriptVersion: latest.scriptVersion },
|
|
16895
|
+
"script already matches the declared source \u2014 no version pushed"
|
|
16896
|
+
);
|
|
16897
|
+
}
|
|
16698
16898
|
continue;
|
|
16699
16899
|
}
|
|
16700
|
-
|
|
16900
|
+
let { adopting } = assertScriptPushOwnable(name, latest, blueprintName, allowScriptAdoption);
|
|
16901
|
+
const body = {
|
|
16902
|
+
name,
|
|
16903
|
+
source: entry.source,
|
|
16904
|
+
declaredInputContract: entry.declaredInputContract,
|
|
16905
|
+
provisionedBy: blueprintName
|
|
16906
|
+
};
|
|
16701
16907
|
let pushed;
|
|
16702
16908
|
try {
|
|
16703
16909
|
pushed = await ctxClient.scripts.createScript(body);
|
|
16704
16910
|
} catch (err) {
|
|
16705
|
-
if (!isVersionConflict(err)) throw err;
|
|
16911
|
+
if (!isVersionConflict(err)) throw adopting ? withAdoptionContext(err, name) : err;
|
|
16706
16912
|
log.debug(
|
|
16707
16913
|
{ component: "loader", scriptName: name },
|
|
16708
16914
|
"concurrent push detected (409) \u2014 pausing, then re-reading the current version"
|
|
@@ -16728,10 +16934,11 @@ async function ensureScripts(args) {
|
|
|
16728
16934
|
);
|
|
16729
16935
|
continue;
|
|
16730
16936
|
}
|
|
16937
|
+
({ adopting } = assertScriptPushOwnable(name, latest, blueprintName, allowScriptAdoption));
|
|
16731
16938
|
try {
|
|
16732
16939
|
pushed = await ctxClient.scripts.createScript(body);
|
|
16733
16940
|
} catch (retryErr) {
|
|
16734
|
-
if (!isVersionConflict(retryErr)) throw retryErr;
|
|
16941
|
+
if (!isVersionConflict(retryErr)) throw adopting ? withAdoptionContext(retryErr, name) : retryErr;
|
|
16735
16942
|
throw new Error(
|
|
16736
16943
|
`Script '${name}': still conflicting (409) after a re-read and retry. Another apply is pushing this script name concurrently \u2014 re-run once it has finished.`
|
|
16737
16944
|
);
|
|
@@ -16743,13 +16950,13 @@ async function ensureScripts(args) {
|
|
|
16743
16950
|
const firstVersion = latest === void 0;
|
|
16744
16951
|
actions.push({
|
|
16745
16952
|
name,
|
|
16746
|
-
action: firstVersion ? "created" : "superseded",
|
|
16953
|
+
action: firstVersion ? "created" : adopting ? "adopted" : "superseded",
|
|
16747
16954
|
id: pushed.id,
|
|
16748
16955
|
scriptVersion: pushed.scriptVersion ?? 0
|
|
16749
16956
|
});
|
|
16750
16957
|
log.debug(
|
|
16751
16958
|
{ component: "loader", scriptName: name, scriptVersion: pushed.scriptVersion },
|
|
16752
|
-
firstVersion ? "script pushed \u2014 first version for this name in this context" : "script source changed \u2014 new version pushed over an existing name"
|
|
16959
|
+
firstVersion ? "script pushed \u2014 first version for this name in this context" : adopting ? "script pushed \u2014 adopted a previously-unmarked name into this blueprint" : "script source changed \u2014 new version pushed over an existing name"
|
|
16753
16960
|
);
|
|
16754
16961
|
}
|
|
16755
16962
|
return actions;
|
|
@@ -16920,9 +17127,10 @@ function describeNamespaceMismatch(decl, existing) {
|
|
|
16920
17127
|
return mismatches.length > 0 ? mismatches.join("; ") : null;
|
|
16921
17128
|
}
|
|
16922
17129
|
var LostKeyError = class extends Error {
|
|
16923
|
-
constructor(keyName) {
|
|
17130
|
+
constructor(keyName, rotateAttempted = false, revokedKeyId) {
|
|
17131
|
+
const revokedDisplay = revokedKeyId?.map((id) => `'${id}'`).join(", ");
|
|
16924
17132
|
super(
|
|
16925
|
-
`Scoped key '${keyName}' already exists for this machine but its local copy (~/.vectros) is missing. The raw key is shown only once and cannot be re-read. Re-run with --rotate to revoke + re-mint this machine's key.`
|
|
17133
|
+
`Scoped key '${keyName}' already exists for this machine but its local copy (~/.vectros) is missing. The raw key is shown only once and cannot be re-read. ` + (revokedKeyId && revokedKeyId.length > 0 ? `--rotate just revoked ${revokedKeyId.length === 1 ? "key" : "keys"} ${revokedDisplay}, but this call still sees the scoped key as already existing \u2014 most likely a brief propagation delay right after the revoke, not a failed rotate. Wait a few seconds and re-run, or run \`vectros key list --context <contextId>\` to confirm ${revokedKeyId.length === 1 ? "it is" : "they are"} actually gone before retrying.` : rotateAttempted ? `--rotate ran and found no matching active key to revoke for this context/principal \u2014 either it was already revoked elsewhere or something else holds that name. Run \`vectros key list --context <contextId>\` to find the key actually blocking this, then \`vectros key revoke <id>\` it directly before re-running.` : `Re-run with --rotate to revoke + re-mint this machine's key.`)
|
|
16926
17134
|
);
|
|
16927
17135
|
this.keyName = keyName;
|
|
16928
17136
|
this.name = "LostKeyError";
|
|
@@ -17130,6 +17338,7 @@ async function runLoader(client, input) {
|
|
|
17130
17338
|
provisioned,
|
|
17131
17339
|
readExistingKey,
|
|
17132
17340
|
persistMintedKey,
|
|
17341
|
+
allowScriptAdoption = false,
|
|
17133
17342
|
log
|
|
17134
17343
|
} = input;
|
|
17135
17344
|
assertNoUnresolvedIdentities(blueprint);
|
|
@@ -17280,7 +17489,13 @@ async function runLoader(client, input) {
|
|
|
17280
17489
|
body: profileBody
|
|
17281
17490
|
});
|
|
17282
17491
|
log.debug({ component: "loader", principalId: `usr_${userId}`, accessProfileId: profile.id, profileAction }, "access profile ensured + reconciled to declared scopes");
|
|
17283
|
-
const scriptActions = blueprint.scripts ? await ensureScripts({
|
|
17492
|
+
const scriptActions = blueprint.scripts ? await ensureScripts({
|
|
17493
|
+
ctxClient,
|
|
17494
|
+
scripts: blueprint.scripts,
|
|
17495
|
+
blueprintName: blueprint.name,
|
|
17496
|
+
allowScriptAdoption,
|
|
17497
|
+
log
|
|
17498
|
+
}) : [];
|
|
17284
17499
|
const triggerActions = [];
|
|
17285
17500
|
for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
|
|
17286
17501
|
const schemaId = schemaIds[entry.firingSource.schemaName];
|
|
@@ -17391,9 +17606,45 @@ async function runLoader(client, input) {
|
|
|
17391
17606
|
if (existing?.keyId) {
|
|
17392
17607
|
await ctxClient.auth.revokeScopedKey({ keyId: existing.keyId });
|
|
17393
17608
|
log.debug({ component: "loader", keyId: existing.keyId }, "revoked prior key for rotate");
|
|
17394
|
-
revokedKeyId = existing.keyId;
|
|
17609
|
+
revokedKeyId = [existing.keyId];
|
|
17610
|
+
} else {
|
|
17611
|
+
let activeKeys;
|
|
17612
|
+
try {
|
|
17613
|
+
activeKeys = await drainPages(() => ctxClient.auth.listScopedKeys());
|
|
17614
|
+
} catch (err) {
|
|
17615
|
+
throw new Error(
|
|
17616
|
+
`--rotate could not check for a server-side key to revoke, and no local record of one exists on this machine: ${err instanceof Error ? err.message : String(err)}. Run \`vectros key list --context ${blueprint.contextId}\` and \`vectros key revoke <id>\` manually to find and remove the key blocking this, then re-run without --rotate.`,
|
|
17617
|
+
{ cause: err }
|
|
17618
|
+
);
|
|
17619
|
+
}
|
|
17620
|
+
const matches = activeKeys.filter(
|
|
17621
|
+
(k) => k.contextId === blueprint.contextId && k.userId === userId && k.keyName === keyName && k.status === "active" && k.keyId
|
|
17622
|
+
);
|
|
17623
|
+
if (matches.length > 1) {
|
|
17624
|
+
log.warn(
|
|
17625
|
+
{ component: "loader", keyName, contextId: blueprint.contextId, count: matches.length },
|
|
17626
|
+
"rotate: more than one active key matched this context/user/name tuple server-side \u2014 revoking all of them"
|
|
17627
|
+
);
|
|
17628
|
+
}
|
|
17629
|
+
if (matches.length > 0) {
|
|
17630
|
+
const revokedIds = [];
|
|
17631
|
+
for (const match of matches) {
|
|
17632
|
+
await ctxClient.auth.revokeScopedKey({ keyId: match.keyId });
|
|
17633
|
+
revokedIds.push(match.keyId);
|
|
17634
|
+
}
|
|
17635
|
+
log.debug(
|
|
17636
|
+
{ component: "loader", keyIds: revokedIds },
|
|
17637
|
+
"revoked prior key(s) for rotate \u2014 resolved server-side, no local record existed"
|
|
17638
|
+
);
|
|
17639
|
+
revokedKeyId = revokedIds;
|
|
17640
|
+
} else {
|
|
17641
|
+
log.warn(
|
|
17642
|
+
{ component: "loader", keyName, contextId: blueprint.contextId },
|
|
17643
|
+
"rotate: no local record and no matching active key found server-side \u2014 nothing to revoke"
|
|
17644
|
+
);
|
|
17645
|
+
}
|
|
17395
17646
|
}
|
|
17396
|
-
keyAction = "rotated";
|
|
17647
|
+
if (revokedKeyId) keyAction = "rotated";
|
|
17397
17648
|
}
|
|
17398
17649
|
const keyResp = await ctxClient.auth.createScopedKey({ keyName, tenantId, contextId: blueprint.contextId, userId });
|
|
17399
17650
|
let rawKey;
|
|
@@ -17408,7 +17659,7 @@ async function runLoader(client, input) {
|
|
|
17408
17659
|
} else {
|
|
17409
17660
|
const existing = await readExistingKey(blueprint.contextId);
|
|
17410
17661
|
if (!existing) {
|
|
17411
|
-
throw new LostKeyError(keyName);
|
|
17662
|
+
throw new LostKeyError(keyName, rotate, revokedKeyId);
|
|
17412
17663
|
}
|
|
17413
17664
|
rawKey = existing.rawKey;
|
|
17414
17665
|
keyId = existing.keyId;
|
|
@@ -17521,6 +17772,10 @@ async function runLoader(client, input) {
|
|
|
17521
17772
|
seedIndexFailures
|
|
17522
17773
|
};
|
|
17523
17774
|
}
|
|
17775
|
+
function sanitizeForPlanDisplay(value, maxLen = 200) {
|
|
17776
|
+
const stripped = value.replace(/[\x00-\x1f\x7f]/g, "");
|
|
17777
|
+
return stripped.length > maxLen ? `${stripped.slice(0, maxLen)}\u2026` : stripped;
|
|
17778
|
+
}
|
|
17524
17779
|
function renderPlanDataScope(ds) {
|
|
17525
17780
|
const entries = Object.entries(ds ?? {});
|
|
17526
17781
|
if (entries.length === 0) return "(none \u2014 unconstrained)";
|
|
@@ -17547,11 +17802,14 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
17547
17802
|
});
|
|
17548
17803
|
}
|
|
17549
17804
|
for (const iss of blueprint.issuers ?? []) {
|
|
17550
|
-
const
|
|
17551
|
-
const
|
|
17805
|
+
const issuerUrl = sanitizeForPlanDisplay(iss.issuer);
|
|
17806
|
+
const jwksNote = `, jwksUri: ${sanitizeForPlanDisplay(iss.jwksUri)}`;
|
|
17807
|
+
const userinfoNote = iss.userinfoUri ? `, userinfoUri: ${sanitizeForPlanDisplay(iss.userinfoUri)}` : "";
|
|
17808
|
+
const selfSignup = iss.selfSignupPolicies?.length ? `, self-signup: [${iss.selfSignupPolicies.map((p) => `${p.signupType}\u2192${p.roleId}`).join(", ")}]` : "";
|
|
17809
|
+
const captured = iss.capturedClaims?.length ? `, captured claims: [${iss.capturedClaims.map((c) => sanitizeForPlanDisplay(c)).join(", ")}]` : "";
|
|
17552
17810
|
steps.push({
|
|
17553
17811
|
kind: "issuer",
|
|
17554
|
-
summary: `issuer '${iss.issuerId}' (${
|
|
17812
|
+
summary: `issuer '${iss.issuerId}' (${issuerUrl} \u2192 context '${iss.contextId}'${jwksNote}${userinfoNote}${selfSignup}${captured}) [${BLUEPRINT_FIELD_PHASES.issuers}-token phase]`
|
|
17555
17813
|
});
|
|
17556
17814
|
}
|
|
17557
17815
|
for (const s of blueprint.schemas) {
|
|
@@ -17672,9 +17930,11 @@ function renderPlan(blueprint, steps) {
|
|
|
17672
17930
|
` immutable version and every earlier one persists, which is also how a 'latest' rule`,
|
|
17673
17931
|
` picks the change up. And scripts are NOT reconciled by absence: a script this blueprint`,
|
|
17674
17932
|
` previously shipped and no longer declares is LEFT IN PLACE, including every stored`,
|
|
17675
|
-
` version.
|
|
17676
|
-
`
|
|
17677
|
-
`
|
|
17933
|
+
` version. Each version does carry this blueprint's own provenance marker, and a push`,
|
|
17934
|
+
` ONTO an already-marked-or-existing name is gated on it (see 'script' steps above) \u2014 but`,
|
|
17935
|
+
` that gate has no bearing on DELETION: there is no second, principal-shaped factor to`,
|
|
17936
|
+
` pair the marker with the way trigger rows have one, so a wrong delete stays unrecoverable`,
|
|
17937
|
+
` and would break any trigger still referencing it. Remove one deliberately with`,
|
|
17678
17938
|
` 'vectros scripts delete', after 'vectros scripts list' shows you what is there.`
|
|
17679
17939
|
);
|
|
17680
17940
|
if (steps.some((s) => s.kind === "issuer")) {
|
|
@@ -17801,19 +18061,37 @@ import { createCipheriv, createDecipheriv, randomBytes as randomBytes2, scryptSy
|
|
|
17801
18061
|
// src/os-keychain.ts
|
|
17802
18062
|
var KEYCHAIN_SERVICE = "vectros-cli";
|
|
17803
18063
|
var PROBE_ACCOUNT = "__vectros_probe__";
|
|
17804
|
-
async function loadKeychainBinding() {
|
|
18064
|
+
async function loadKeychainBinding(tryLoad = () => import("@napi-rs/keyring")) {
|
|
17805
18065
|
try {
|
|
17806
|
-
const mod = await
|
|
18066
|
+
const mod = await tryLoad();
|
|
17807
18067
|
if (typeof mod?.Entry === "function") return { binding: mod };
|
|
17808
|
-
return {
|
|
18068
|
+
return {
|
|
18069
|
+
reason: "the @napi-rs/keyring package loaded but exposes no Entry \u2014 expected on no platform",
|
|
18070
|
+
installAccident: true
|
|
18071
|
+
};
|
|
17809
18072
|
} catch (err) {
|
|
17810
|
-
return { reason: loadFailureReason(err) };
|
|
18073
|
+
return { reason: loadFailureReason(err), installAccident: isInstallAccident(err) };
|
|
17811
18074
|
}
|
|
17812
18075
|
}
|
|
17813
|
-
|
|
18076
|
+
var PREBUILT_PLATFORMS = {
|
|
18077
|
+
darwin: ["arm64", "x64"],
|
|
18078
|
+
freebsd: ["x64"],
|
|
18079
|
+
linux: ["arm", "arm64", "riscv64", "x64"],
|
|
18080
|
+
win32: ["arm64", "ia32", "x64"]
|
|
18081
|
+
};
|
|
18082
|
+
function prebuiltShouldExist(platform = process.platform, arch = process.arch) {
|
|
18083
|
+
return PREBUILT_PLATFORMS[platform]?.includes(arch) ?? false;
|
|
18084
|
+
}
|
|
18085
|
+
function isInstallAccident(_err, platform = process.platform, arch = process.arch) {
|
|
18086
|
+
return prebuiltShouldExist(platform, arch);
|
|
18087
|
+
}
|
|
18088
|
+
function loadFailureReason(err, platform = process.platform, arch = process.arch) {
|
|
18089
|
+
if (!prebuiltShouldExist(platform, arch)) {
|
|
18090
|
+
return "the optional @napi-rs/keyring package is not installed for this platform";
|
|
18091
|
+
}
|
|
17814
18092
|
const code = err.code;
|
|
17815
18093
|
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
|
|
17816
|
-
return
|
|
18094
|
+
return `the optional @napi-rs/keyring package is missing even though a prebuilt exists for this platform (${platform}/${arch}) \u2014 most likely skipped during install`;
|
|
17817
18095
|
}
|
|
17818
18096
|
return `the optional @napi-rs/keyring package is installed but would not load (${describeErr(err)})`;
|
|
17819
18097
|
}
|
|
@@ -17937,8 +18215,8 @@ var OsKeychainSecretStore = class {
|
|
|
17937
18215
|
* deleteCredential() -> false
|
|
17938
18216
|
* => deleteSecret() -> false. The gate holds; `remove` refuses.
|
|
17939
18217
|
*
|
|
17940
|
-
* Reproduce
|
|
17941
|
-
*
|
|
18218
|
+
* Reproduce by locking the credential store's collection while the CLI still holds a
|
|
18219
|
+
* cached handle to it. CI cannot pin this half — it unlocks the collection so the rest of
|
|
17942
18220
|
* the suite can use it — so the evidence lives here rather than in a test.
|
|
17943
18221
|
*
|
|
17944
18222
|
* The WINDOWS half of the same contract was assumed for seven review rounds and
|
|
@@ -18380,7 +18658,11 @@ var KEYRING_BACKENDS = ["auto", "file"];
|
|
|
18380
18658
|
async function resolveSecretStore(deps = {}) {
|
|
18381
18659
|
const platform = deps.platform ?? process.platform;
|
|
18382
18660
|
const env = deps.env ?? process.env;
|
|
18383
|
-
const file = (reason2) => ({
|
|
18661
|
+
const file = (reason2, installAccident2) => ({
|
|
18662
|
+
kind: "crypto-file",
|
|
18663
|
+
reason: reason2,
|
|
18664
|
+
...installAccident2 ? { installAccident: installAccident2 } : {}
|
|
18665
|
+
});
|
|
18384
18666
|
const requested = env[KEYRING_BACKEND_ENV]?.trim().toLowerCase();
|
|
18385
18667
|
if (requested !== void 0 && requested !== "") {
|
|
18386
18668
|
if (!KEYRING_BACKENDS.includes(requested)) {
|
|
@@ -18397,9 +18679,9 @@ async function resolveSecretStore(deps = {}) {
|
|
|
18397
18679
|
return file("the macOS Keychain can prompt on unattended reads, which would break MCP startup");
|
|
18398
18680
|
}
|
|
18399
18681
|
const loadBinding0 = deps.loadBinding ?? loadKeychainBinding;
|
|
18400
|
-
const { binding, reason } = await loadBinding0();
|
|
18682
|
+
const { binding, reason, installAccident } = await loadBinding0();
|
|
18401
18683
|
if (!binding) {
|
|
18402
|
-
return file(reason ?? "the optional @napi-rs/keyring package is not usable on this platform");
|
|
18684
|
+
return file(reason ?? "the optional @napi-rs/keyring package is not usable on this platform", installAccident);
|
|
18403
18685
|
}
|
|
18404
18686
|
const probe = deps.probe ?? probeKeychain;
|
|
18405
18687
|
if (!probe(binding)) {
|
|
@@ -18428,6 +18710,9 @@ var Keyring = class _Keyring {
|
|
|
18428
18710
|
stores;
|
|
18429
18711
|
/** Where NEW secrets go, and why — surfaced by `keyring doctor`. */
|
|
18430
18712
|
backend;
|
|
18713
|
+
warn;
|
|
18714
|
+
/** Fires at most once per instance — see {@link storeSecretSomewhere}. */
|
|
18715
|
+
installAccidentWarned = false;
|
|
18431
18716
|
/**
|
|
18432
18717
|
* Construct against EXPLICIT stores. The file store defaults to a
|
|
18433
18718
|
* {@link CryptoFileSecretStore} over `dir` — no native binding, nothing outside
|
|
@@ -18442,6 +18727,8 @@ var Keyring = class _Keyring {
|
|
|
18442
18727
|
"os-keychain": deps.keychainStore
|
|
18443
18728
|
};
|
|
18444
18729
|
this.backend = deps.selection ?? { kind: "crypto-file" };
|
|
18730
|
+
this.warn = deps.warn ?? ((msg) => process.stderr.write(`${msg}
|
|
18731
|
+
`));
|
|
18445
18732
|
}
|
|
18446
18733
|
/**
|
|
18447
18734
|
* The keyring a command should use: the stores resolved for THIS machine per
|
|
@@ -18452,7 +18739,7 @@ var Keyring = class _Keyring {
|
|
|
18452
18739
|
static async open(deps = {}) {
|
|
18453
18740
|
if (deps.fileStore ?? deps.keychainStore ?? deps.selection) return new _Keyring(deps);
|
|
18454
18741
|
const { keychain, ...selection } = await resolveSecretStore(deps);
|
|
18455
|
-
return new _Keyring({ dir: deps.dir, keychainStore: keychain, selection });
|
|
18742
|
+
return new _Keyring({ dir: deps.dir, keychainStore: keychain, selection, warn: deps.warn });
|
|
18456
18743
|
}
|
|
18457
18744
|
/**
|
|
18458
18745
|
* The store holding this entry's secret, or undefined when the stamp does not say
|
|
@@ -18569,6 +18856,28 @@ The key itself WAS created server-side and is live \u2014 its id is ${entry.keyI
|
|
|
18569
18856
|
}
|
|
18570
18857
|
return { landedIn, stranded: swept ? void 0 : prior, vetoedBy };
|
|
18571
18858
|
}
|
|
18859
|
+
/**
|
|
18860
|
+
* Warn, at most once per instance, when this machine is about to write a secret to
|
|
18861
|
+
* the file store because of an INSTALL ACCIDENT — not because of an ordinary,
|
|
18862
|
+
* nothing-to-fix absence (unsupported platform, `VECTROS_KEYRING_BACKEND=file`,
|
|
18863
|
+
* macOS's own design choice, or "loaded fine but no OS credential store answers").
|
|
18864
|
+
*
|
|
18865
|
+
* Before this, the CLI degraded to the file store with ZERO signal at write
|
|
18866
|
+
* time, indistinguishable from every OTHER reason a machine ends up there — a
|
|
18867
|
+
* real `@napi-rs/keyring` install failure looked identical to a platform that
|
|
18868
|
+
* was never going to have one, unless you separately ran `keyring doctor`.
|
|
18869
|
+
* This is the signal that was missing, at the moment it matters (a real
|
|
18870
|
+
* secret is about to land somewhere weaker than intended), naming the one
|
|
18871
|
+
* remedy that actually applies to THIS case and no other.
|
|
18872
|
+
*/
|
|
18873
|
+
warnOnceIfInstallAccident() {
|
|
18874
|
+
if (this.installAccidentWarned) return;
|
|
18875
|
+
if (this.backend.kind !== "crypto-file" || !this.backend.installAccident) return;
|
|
18876
|
+
this.installAccidentWarned = true;
|
|
18877
|
+
this.warn(
|
|
18878
|
+
`WARNING: storing this credential in the encrypted file store, not your OS credential store \u2014 ${this.backend.reason ?? "the optional @napi-rs/keyring package failed to load"}. This may be an install accident rather than an unsupported platform: try reinstalling (npm install -g @vectros-ai/cli, or add @napi-rs/keyring as a direct dependency alongside it) to restore OS-credential-store protection. Run \`vectros keyring doctor\` for the full picture.`
|
|
18879
|
+
);
|
|
18880
|
+
}
|
|
18572
18881
|
/**
|
|
18573
18882
|
* Persist a secret, preferring this machine's chosen store but NEVER losing the
|
|
18574
18883
|
* secret if that store refuses. Returns where it actually landed.
|
|
@@ -18581,8 +18890,15 @@ The key itself WAS created server-side and is live \u2014 its id is ${entry.keyI
|
|
|
18581
18890
|
* credential and leave it orphaned server-side with no local record of its
|
|
18582
18891
|
* `keyId`. Degrading to the encrypted file is strictly better than that, and the
|
|
18583
18892
|
* entry's stamp keeps the result honest rather than silently wrong.
|
|
18893
|
+
*
|
|
18894
|
+
* `warnIfDegraded` defaults to true and exists ONLY for {@link probeSecretStore}
|
|
18895
|
+
* to suppress with `false`: the once-per-instance install-accident warning is a
|
|
18896
|
+
* scarce resource (fires at most once per `Keyring`), and the probe's marker
|
|
18897
|
+
* value is never the credential the warning describes. Spending it on the probe
|
|
18898
|
+
* left the REAL write — the very next call, same instance — silent.
|
|
18584
18899
|
*/
|
|
18585
|
-
async storeSecretSomewhere(ref, secret) {
|
|
18900
|
+
async storeSecretSomewhere(ref, secret, { warnIfDegraded = true } = {}) {
|
|
18901
|
+
if (warnIfDegraded) this.warnOnceIfInstallAccident();
|
|
18586
18902
|
const preferred = this.preferred;
|
|
18587
18903
|
if (preferred) {
|
|
18588
18904
|
try {
|
|
@@ -18634,7 +18950,9 @@ The key itself WAS created server-side and is live \u2014 its id is ${entry.keyI
|
|
|
18634
18950
|
async probeSecretStore() {
|
|
18635
18951
|
const fileStore = this.stores["crypto-file"];
|
|
18636
18952
|
const dirWasPresent = fileStore instanceof CryptoFileSecretStore ? await fileStore.secretsDirPresent() : true;
|
|
18637
|
-
const landedIn = await this.storeSecretSomewhere(PROBE_SECRET_REF, PROBE_SECRET_VALUE
|
|
18953
|
+
const landedIn = await this.storeSecretSomewhere(PROBE_SECRET_REF, PROBE_SECRET_VALUE, {
|
|
18954
|
+
warnIfDegraded: false
|
|
18955
|
+
});
|
|
18638
18956
|
await this.stores[landedIn]?.deleteSecret(PROBE_SECRET_REF).catch(() => {
|
|
18639
18957
|
});
|
|
18640
18958
|
if (!dirWasPresent && fileStore instanceof CryptoFileSecretStore) {
|
|
@@ -18935,7 +19253,12 @@ The key itself WAS created server-side and is live \u2014 its id is ${entry.keyI
|
|
|
18935
19253
|
async readSecret(entry) {
|
|
18936
19254
|
const from = storeKindOf(entry);
|
|
18937
19255
|
if (from === void 0) return void 0;
|
|
18938
|
-
const
|
|
19256
|
+
const store = this.stores[from];
|
|
19257
|
+
if (!store) {
|
|
19258
|
+
this.warnOnceIfInstallAccident();
|
|
19259
|
+
return void 0;
|
|
19260
|
+
}
|
|
19261
|
+
const secret = await store.getSecret(entry.secretRef);
|
|
18939
19262
|
if (secret === void 0) return void 0;
|
|
18940
19263
|
if (from !== this.backend.kind) await this.migrateSecret(entry, from, secret);
|
|
18941
19264
|
return secret;
|
|
@@ -19656,7 +19979,7 @@ function nowIso() {
|
|
|
19656
19979
|
}
|
|
19657
19980
|
function warnReplacingAlias(existing, alias, revokedKeyId) {
|
|
19658
19981
|
if (!existing) return;
|
|
19659
|
-
const stillLive = existing.keyId !== void 0 && existing.keyId
|
|
19982
|
+
const stillLive = existing.keyId !== void 0 && !(revokedKeyId ?? []).includes(existing.keyId);
|
|
19660
19983
|
process.stdout.write(
|
|
19661
19984
|
`
|
|
19662
19985
|
\u26A0 Replacing keyring entry '${alias}' (was ${existing.principalId || "<unknown principal>"} in '${existing.context}' @ ${existing.env}, key ${existing.keyId}). Pass --alias to keep both.
|
|
@@ -19667,13 +19990,14 @@ function warnReplacingAlias(existing, alias, revokedKeyId) {
|
|
|
19667
19990
|
}
|
|
19668
19991
|
async function warnOtherAliasesNaming(keyring, revokedKeyId, exceptAlias) {
|
|
19669
19992
|
const affected = (await keyring.list()).filter(
|
|
19670
|
-
(e) => e.alias !== exceptAlias && e.keyId
|
|
19993
|
+
(e) => e.alias !== exceptAlias && e.keyId !== void 0 && revokedKeyId.includes(e.keyId)
|
|
19671
19994
|
);
|
|
19672
19995
|
if (affected.length === 0) return;
|
|
19673
19996
|
const names = affected.map((e) => `'${e.alias}'`).join(", ");
|
|
19997
|
+
const revokedDisplay = revokedKeyId.length === 1 ? `Key ${revokedKeyId[0]}` : `Keys ${revokedKeyId.join(", ")}`;
|
|
19674
19998
|
process.stderr.write(
|
|
19675
19999
|
`
|
|
19676
|
-
\u26A0
|
|
20000
|
+
\u26A0 ${revokedDisplay} ${revokedKeyId.length === 1 ? "was" : "were"} just revoked, but ${affected.length === 1 ? "entry" : "entries"} ${names} still
|
|
19677
20001
|
${affected.length === 1 ? "names" : "name"} it. That secret is now dead \u2014 it keeps reading fine locally and
|
|
19678
20002
|
will simply stop authenticating. Re-point ${affected.length === 1 ? "it" : "them"} with
|
|
19679
20003
|
\`vectros key rotate --to-keyring --alias <name>\`, or drop with \`vectros keyring remove <name>\`.
|
|
@@ -19703,7 +20027,7 @@ async function storeMintedKey(keyring, binding, secret, opts = {}) {
|
|
|
19703
20027
|
was kept too` : `the ${noun} did not
|
|
19704
20028
|
confirm the delete`;
|
|
19705
20029
|
const tidyUp = vetoNoun !== void 0 ? `Clear the copies by hand from your ${vetoNoun} and your ${noun}` : `Clear it by hand from your ${noun}`;
|
|
19706
|
-
if (opts.revokedKeyId
|
|
20030
|
+
if (opts.revokedKeyId?.includes(stranded.keyId)) {
|
|
19707
20031
|
process.stderr.write(
|
|
19708
20032
|
`
|
|
19709
20033
|
\u26A0 The previous key for '${entry.alias}' (${stranded.keyId}) was revoked and replaced, but
|
|
@@ -19774,6 +20098,7 @@ function parseBootstrapArgs(argv) {
|
|
|
19774
20098
|
blankSlate: false,
|
|
19775
20099
|
noSeed: false,
|
|
19776
20100
|
allowTenantWideNamespaces: false,
|
|
20101
|
+
allowScriptAdoption: false,
|
|
19777
20102
|
client: "desktop",
|
|
19778
20103
|
tenant: "live",
|
|
19779
20104
|
help: false
|
|
@@ -19838,6 +20163,9 @@ function parseBootstrapArgs(argv) {
|
|
|
19838
20163
|
case "--allow-tenant-wide-namespaces":
|
|
19839
20164
|
args.allowTenantWideNamespaces = true;
|
|
19840
20165
|
break;
|
|
20166
|
+
case "--allow-script-adoption":
|
|
20167
|
+
args.allowScriptAdoption = true;
|
|
20168
|
+
break;
|
|
19841
20169
|
case "--tenant": {
|
|
19842
20170
|
const v = argv[++i];
|
|
19843
20171
|
if (v !== "test" && v !== "live") {
|
|
@@ -19866,7 +20194,7 @@ function parseBootstrapArgs(argv) {
|
|
|
19866
20194
|
return args;
|
|
19867
20195
|
}
|
|
19868
20196
|
function shouldOfferSeedOptOut(args, blueprint, isTty) {
|
|
19869
|
-
return !args.noSeed && (blueprint.seed?.length ?? 0) > 0 && !args.yes &&
|
|
20197
|
+
return !args.noSeed && (blueprint.seed?.length ?? 0) > 0 && !args.yes && isTty;
|
|
19870
20198
|
}
|
|
19871
20199
|
function seedDeclined(answer) {
|
|
19872
20200
|
const a = answer.trim().toLowerCase();
|
|
@@ -19932,12 +20260,16 @@ function scriptTally(items) {
|
|
|
19932
20260
|
if (items.length === 0) return null;
|
|
19933
20261
|
const created = items.filter((i) => i.action === "created");
|
|
19934
20262
|
const superseded = items.filter((i) => i.action === "superseded");
|
|
19935
|
-
const
|
|
20263
|
+
const adopted = items.filter((i) => i.action === "adopted");
|
|
20264
|
+
const unchanged = items.length - created.length - superseded.length - adopted.length;
|
|
19936
20265
|
const seg = [];
|
|
19937
20266
|
if (created.length) seg.push(`${created.length} first version${created.length === 1 ? "" : "s"} pushed`);
|
|
19938
20267
|
if (superseded.length) {
|
|
19939
20268
|
seg.push(`${superseded.map((i) => `'${i.name}'`).join(", ")} superseded (a new version now current)`);
|
|
19940
20269
|
}
|
|
20270
|
+
if (adopted.length) {
|
|
20271
|
+
seg.push(`${adopted.map((i) => `'${i.name}'`).join(", ")} adopted (previously unmarked, now managed by this blueprint)`);
|
|
20272
|
+
}
|
|
19941
20273
|
if (unchanged) seg.push(`${unchanged} already current \u2014 nothing written`);
|
|
19942
20274
|
return `${items.length} script${items.length === 1 ? "" : "s"} (${seg.join("; ")})`;
|
|
19943
20275
|
}
|
|
@@ -20009,6 +20341,14 @@ Options:
|
|
|
20009
20341
|
credential can actually complete the registration either way \u2014
|
|
20010
20342
|
this flag doesn't change who's allowed, only whether this run
|
|
20011
20343
|
tries. Never needed for a blueprint with no tenantWide namespace.
|
|
20344
|
+
--allow-script-adoption
|
|
20345
|
+
Required to push a script version onto a name that already exists in
|
|
20346
|
+
this context but carries no blueprint provenance marker (hand-authored,
|
|
20347
|
+
or pushed before this marker existed) \u2014 without it, bootstrap refuses
|
|
20348
|
+
before minting anything for that name, naming it. Never needed for a
|
|
20349
|
+
brand-new script name, or for re-applying a blueprint that already owns
|
|
20350
|
+
the name it's pushing to. Does NOT help if the name is already owned by
|
|
20351
|
+
a DIFFERENT blueprint \u2014 that is refused unconditionally, no flag past it.
|
|
20012
20352
|
--tenant test|live Which tenant the minted key scopes to (default: live).
|
|
20013
20353
|
'test' provisions into your isolated test tenant \u2014 a
|
|
20014
20354
|
dry-run before pointing real data at the same blueprint.
|
|
@@ -20031,7 +20371,8 @@ Options:
|
|
|
20031
20371
|
--yes, -y Skip confirmations (agent / CI path).
|
|
20032
20372
|
--no-merge Still mints/reconciles \u2014 this only changes where the result goes:
|
|
20033
20373
|
print the config snippet instead of merging it into your client's
|
|
20034
|
-
config file. Not a dry run; see 'blueprint plan' for that.
|
|
20374
|
+
config file. Not a dry run; see 'blueprint plan' for that. Does NOT
|
|
20375
|
+
skip the 'Apply this plan?' confirmation \u2014 pass --yes for that.
|
|
20035
20376
|
--rotate Revoke + re-mint THIS machine's key.
|
|
20036
20377
|
--confirm-existing-principal
|
|
20037
20378
|
Required (with --yes, or with no TTY) to mint a NEW key bound to a
|
|
@@ -20231,7 +20572,7 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
20231
20572
|
`);
|
|
20232
20573
|
}
|
|
20233
20574
|
}
|
|
20234
|
-
if (!args.yes &&
|
|
20575
|
+
if (!args.yes && isTty) {
|
|
20235
20576
|
const ans = (await promptCtx.prompt("\n Apply this plan? [y/N]: ")).trim().toLowerCase();
|
|
20236
20577
|
if (ans !== "y" && ans !== "yes") {
|
|
20237
20578
|
process.stdout.write(" Aborted \u2014 nothing minted.\n");
|
|
@@ -20365,6 +20706,7 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
20365
20706
|
machineId,
|
|
20366
20707
|
rotate: args.rotate,
|
|
20367
20708
|
skipSeed: noSeed,
|
|
20709
|
+
allowScriptAdoption: args.allowScriptAdoption,
|
|
20368
20710
|
readExistingKey,
|
|
20369
20711
|
persistMintedKey,
|
|
20370
20712
|
provisioned,
|
|
@@ -21578,7 +21920,7 @@ ${list}
|
|
|
21578
21920
|
return storeAndReport(dest, key, auth, {
|
|
21579
21921
|
...opts,
|
|
21580
21922
|
principalId: `usr_${userId}`,
|
|
21581
|
-
revokedKeyId: existing?.keyId
|
|
21923
|
+
revokedKeyId: existing?.keyId ? [existing.keyId] : void 0
|
|
21582
21924
|
});
|
|
21583
21925
|
emitMintedKey(key, opts.format);
|
|
21584
21926
|
return 0;
|
|
@@ -23250,6 +23592,7 @@ async function runScriptsList(opts, deps = {}) {
|
|
|
23250
23592
|
const rows = await drainPages(
|
|
23251
23593
|
(startFrom) => client.scripts.listScripts({
|
|
23252
23594
|
limit: PAGE_LIMIT,
|
|
23595
|
+
includeSource: true,
|
|
23253
23596
|
...opts.name ? { name: opts.name } : {},
|
|
23254
23597
|
...startFrom ? { startFrom } : {}
|
|
23255
23598
|
})
|
|
@@ -23310,10 +23653,9 @@ async function runScriptsDelete(opts, deps = {}) {
|
|
|
23310
23653
|
`
|
|
23311
23654
|
\u26A0 Deleting script version '${opts.id}' is permanent \u2014 the stored source is the only copy.
|
|
23312
23655
|
Other versions of the same name are unaffected.
|
|
23313
|
-
|
|
23314
|
-
|
|
23315
|
-
|
|
23316
|
-
against the API or the portal before removing a version a rule may still use.
|
|
23656
|
+
The platform refuses this delete if a trigger rule still references this version \u2014
|
|
23657
|
+
directly, or via 'latest' if this is the name's current newest version \u2014 and names the
|
|
23658
|
+
offending rule instead of deleting. If no rule references it, the delete proceeds.
|
|
23317
23659
|
And if this is the name's LATEST version, its identity is REUSED: the next push of that
|
|
23318
23660
|
name recomputes the same version number and the same id, so anything holding a pinned
|
|
23319
23661
|
'${opts.id}' or a name@version reference will silently resolve to different source.
|
|
@@ -23482,7 +23824,7 @@ async function runLogin(opts, deps = {}) {
|
|
|
23482
23824
|
try {
|
|
23483
23825
|
config = resolveCognitoConfig(opts.env);
|
|
23484
23826
|
} catch (err) {
|
|
23485
|
-
if (err instanceof MissingCognitoClientIdError) {
|
|
23827
|
+
if (err instanceof MissingCognitoClientIdError || err instanceof InvalidBaseUrlError || err instanceof InvalidCognitoRegionError) {
|
|
23486
23828
|
process.stderr.write(` \u2716 ${err.message}
|
|
23487
23829
|
`);
|
|
23488
23830
|
return 2;
|
|
@@ -24075,6 +24417,11 @@ async function runBlueprintHarness(client, input, deps) {
|
|
|
24075
24417
|
// recording that it happened and nothing able to restore it. Opt out rather than grow the
|
|
24076
24418
|
// snapshot a leg teardown could not honour anyway.
|
|
24077
24419
|
pruneTriggers: false
|
|
24420
|
+
// `allowScriptAdoption` is left at its default `false` — the SAME created-only contract above
|
|
24421
|
+
// applies here: this harness may run against a shared tenant, and adopting a pre-existing,
|
|
24422
|
+
// unmarked script name would start this run's teardown treating (and, worse, later deleting)
|
|
24423
|
+
// versions on a name it does not own. A test blueprint that needs `scripts:` should ship a
|
|
24424
|
+
// fresh, collision-free name, exactly as the harness already expects for schemas/roles/issuers.
|
|
24078
24425
|
});
|
|
24079
24426
|
log.debug({ component: "harness", contextId: result.contextId, keyName: result.keyName }, "applied");
|
|
24080
24427
|
assertions = await runAssertions(
|
|
@@ -24288,7 +24635,7 @@ async function issueOrRotateKey(client, tenantId, context, userId, keyName) {
|
|
|
24288
24635
|
"The key already exists and its secret could not be re-minted (the prior key may not have cleared yet). Retry shortly."
|
|
24289
24636
|
);
|
|
24290
24637
|
}
|
|
24291
|
-
return { key: rotated, revokedKeyId: existing?.keyId };
|
|
24638
|
+
return { key: rotated, revokedKeyId: existing?.keyId ? [existing.keyId] : void 0 };
|
|
24292
24639
|
}
|
|
24293
24640
|
async function runJoin(opts, deps = {}) {
|
|
24294
24641
|
const hasRole = Boolean(opts.role?.length);
|
|
@@ -24435,9 +24782,9 @@ function emitJoined(opts) {
|
|
|
24435
24782
|
|
|
24436
24783
|
// src/build-info.ts
|
|
24437
24784
|
var BUILD_INFO = {
|
|
24438
|
-
cli: true ? "0.
|
|
24439
|
-
sdk: true ? "0.
|
|
24440
|
-
blueprints: true ? "0.
|
|
24785
|
+
cli: true ? "0.22.0" : "dev",
|
|
24786
|
+
sdk: true ? "0.44.0" : "dev",
|
|
24787
|
+
blueprints: true ? "0.19.0" : "dev"
|
|
24441
24788
|
};
|
|
24442
24789
|
function formatBuildInfo() {
|
|
24443
24790
|
return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
|
|
@@ -24464,11 +24811,13 @@ function addCommonAuthOptions(cmd) {
|
|
|
24464
24811
|
"emit machine-readable JSON instead of the human-readable view"
|
|
24465
24812
|
);
|
|
24466
24813
|
}
|
|
24814
|
+
var TENANT_FLAG_INVALID = /* @__PURE__ */ Symbol("parseTenantFlag:invalid");
|
|
24467
24815
|
function parseTenantFlag(raw) {
|
|
24468
24816
|
if (raw !== void 0 && raw !== "test" && raw !== "live") {
|
|
24469
24817
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${raw}')
|
|
24470
24818
|
`);
|
|
24471
|
-
process.
|
|
24819
|
+
process.exitCode = 2;
|
|
24820
|
+
return TENANT_FLAG_INVALID;
|
|
24472
24821
|
}
|
|
24473
24822
|
return raw;
|
|
24474
24823
|
}
|
|
@@ -24489,14 +24838,14 @@ function buildProgram(deps = {}) {
|
|
|
24489
24838
|
);
|
|
24490
24839
|
const login = program.command("login").description("Sign in via your browser (Cognito PKCE) and store a session.").addOption(new Option("--env <env>", "target environment").choices(["staging", "production"]).default("production").hideHelp()).option("--no-browser", "print the sign-in URL instead of opening a browser (headless/CI/WSL)");
|
|
24491
24840
|
login.action(async (flags) => {
|
|
24492
|
-
process.
|
|
24841
|
+
process.exitCode = await runLogin({ env: flags.env, json: false, noBrowser: flags.browser === false });
|
|
24493
24842
|
});
|
|
24494
24843
|
program.command("logout").description("Clear the stored login session.").action(async () => {
|
|
24495
|
-
process.
|
|
24844
|
+
process.exitCode = await runLogout({ env: "production", json: false });
|
|
24496
24845
|
});
|
|
24497
24846
|
const whoami = program.command("whoami").description("Show the authenticated session (tenant, env, principal).");
|
|
24498
24847
|
addCommonAuthOptions(whoami).action(async (flags) => {
|
|
24499
|
-
process.
|
|
24848
|
+
process.exitCode = await runWhoami(toCommonOptions(flags), deps);
|
|
24500
24849
|
});
|
|
24501
24850
|
const blueprintTest = program.command("blueprint-test").description("Apply a blueprint to a live env, assert it provisioned a working credential, then tear it down.").argument("<file>", "blueprint file path (./my.yaml) or a bundled blueprint name").option("--keep", "leave the provisioned graph in place (skip teardown) for inspection").option("--tenant <tenant>", "target tenant for the run: 'test' or 'live' (default live)");
|
|
24502
24851
|
addCommonAuthOptions(blueprintTest).action(
|
|
@@ -24504,14 +24853,13 @@ function buildProgram(deps = {}) {
|
|
|
24504
24853
|
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
24505
24854
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
24506
24855
|
`);
|
|
24507
|
-
process.
|
|
24508
|
-
|
|
24509
|
-
|
|
24510
|
-
|
|
24511
|
-
|
|
24512
|
-
|
|
24513
|
-
|
|
24514
|
-
)
|
|
24856
|
+
process.exitCode = 2;
|
|
24857
|
+
return;
|
|
24858
|
+
}
|
|
24859
|
+
process.exitCode = await runBlueprintTest(
|
|
24860
|
+
file,
|
|
24861
|
+
{ ...toCommonOptions(flags), keep: Boolean(flags.keep), tenant: flags.tenant },
|
|
24862
|
+
deps
|
|
24515
24863
|
);
|
|
24516
24864
|
}
|
|
24517
24865
|
);
|
|
@@ -24532,9 +24880,7 @@ function registerAssumeCommand(program, deps) {
|
|
|
24532
24880
|
const assume = program.command("assume").description("Re-mint your own verb token with one or more identity.<namespace> values switched (needs a role's assumable grant).").requiredOption("--context <c>", "app context to operate in").option("--scope <ns:value>", "namespace value to assume, repeatable (e.g. org:orgB)", collect, []);
|
|
24533
24881
|
addCommonAuthOptions(assume).action(
|
|
24534
24882
|
async (flags) => {
|
|
24535
|
-
process.
|
|
24536
|
-
await runAssume({ ...toCommonOptions(flags), context: flags.context, scope: flags.scope ?? [] }, deps)
|
|
24537
|
-
);
|
|
24883
|
+
process.exitCode = await runAssume({ ...toCommonOptions(flags), context: flags.context, scope: flags.scope ?? [] }, deps);
|
|
24538
24884
|
}
|
|
24539
24885
|
);
|
|
24540
24886
|
}
|
|
@@ -24548,23 +24894,22 @@ function registerJoinCommand(program, deps) {
|
|
|
24548
24894
|
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
24549
24895
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
24550
24896
|
`);
|
|
24551
|
-
process.
|
|
24897
|
+
process.exitCode = 2;
|
|
24898
|
+
return;
|
|
24552
24899
|
}
|
|
24553
|
-
process.
|
|
24554
|
-
|
|
24555
|
-
|
|
24556
|
-
|
|
24557
|
-
|
|
24558
|
-
|
|
24559
|
-
|
|
24560
|
-
|
|
24561
|
-
|
|
24562
|
-
|
|
24563
|
-
|
|
24564
|
-
|
|
24565
|
-
|
|
24566
|
-
deps
|
|
24567
|
-
)
|
|
24900
|
+
process.exitCode = await runJoin(
|
|
24901
|
+
{
|
|
24902
|
+
...toCommonOptions(flags),
|
|
24903
|
+
contextArg,
|
|
24904
|
+
role: flags.role,
|
|
24905
|
+
actions: flags.actions,
|
|
24906
|
+
as: flags.as,
|
|
24907
|
+
scope: flags.scope,
|
|
24908
|
+
alias: flags.alias,
|
|
24909
|
+
tenant: flags.tenant,
|
|
24910
|
+
activate: flags.activate
|
|
24911
|
+
},
|
|
24912
|
+
deps
|
|
24568
24913
|
);
|
|
24569
24914
|
}
|
|
24570
24915
|
);
|
|
@@ -24573,29 +24918,27 @@ function registerKeyringCommands(program) {
|
|
|
24573
24918
|
const KEYRING_SHOW_FORMATS = ["human", "env", "raw"];
|
|
24574
24919
|
const keyring = program.command("keyring").description("Inspect and manage the local multi-principal keyring.");
|
|
24575
24920
|
keyring.command("list").description("List keyring entries and which one is active.").option("--json", "emit machine-readable JSON").action(async (flags) => {
|
|
24576
|
-
process.
|
|
24921
|
+
process.exitCode = await runKeyringList({ json: Boolean(flags.json) });
|
|
24577
24922
|
});
|
|
24578
24923
|
keyring.command("use").description("Make an entry the active identity.").argument("<alias>", "the keyring entry to activate").option("--json", "emit machine-readable JSON").action(async (alias, flags) => {
|
|
24579
|
-
process.
|
|
24924
|
+
process.exitCode = await runKeyringUse(alias, { json: Boolean(flags.json) });
|
|
24580
24925
|
});
|
|
24581
24926
|
keyring.command("show").description("Reveal the active (or a named) entry\u2019s key. `--format raw` prints just the secret \u2014 the form scripts and the MCP server resolve a key through.").option("--alias <name>", "show this entry instead of the active one").addOption(new Option("--format <fmt>", "how to emit the key").choices(KEYRING_SHOW_FORMATS).default("human")).action(async (flags) => {
|
|
24582
|
-
process.
|
|
24927
|
+
process.exitCode = await runKeyringShow({ alias: flags.alias, format: flags.format });
|
|
24583
24928
|
});
|
|
24584
24929
|
keyring.command("doctor").description("Health-check the keyring: which entry is active, whether each secret is readable, which entries hold live keys (the ones that act on real data), and which store is holding your secrets.").option("--json", "emit machine-readable JSON").action(async (flags) => {
|
|
24585
|
-
process.
|
|
24930
|
+
process.exitCode = await runKeyringDoctor({ json: Boolean(flags.json) });
|
|
24586
24931
|
});
|
|
24587
24932
|
keyring.command("prune").description("Drop orphaned entries that have no stored secret. Entries whose secret will not decrypt, or is held in the OS credential store (which cannot report absence), are reported and never removed.").option("--dry-run", "report what would be removed without removing anything").option("--force", "skip the interactive confirmation (required when stdin is not a TTY)").option("--json", "emit machine-readable JSON").action(async (flags) => {
|
|
24588
|
-
process.
|
|
24589
|
-
await runKeyringPrune({ dryRun: Boolean(flags.dryRun), force: Boolean(flags.force), json: Boolean(flags.json) })
|
|
24590
|
-
);
|
|
24933
|
+
process.exitCode = await runKeyringPrune({ dryRun: Boolean(flags.dryRun), force: Boolean(flags.force), json: Boolean(flags.json) });
|
|
24591
24934
|
});
|
|
24592
24935
|
keyring.command("remove").description(
|
|
24593
24936
|
"Drop an entry and its stored secret. Refuses if the secret cannot be confirmed deleted \u2014 removing the entry would strand a live key by discarding the only record of its id."
|
|
24594
24937
|
).argument("<alias>", "the keyring entry to remove").option("--json", "emit machine-readable JSON").action(async (alias, flags) => {
|
|
24595
|
-
process.
|
|
24938
|
+
process.exitCode = await runKeyringRemove(alias, { json: Boolean(flags.json) });
|
|
24596
24939
|
});
|
|
24597
24940
|
program.command("switch").description("Shortcut for `keyring use` \u2014 flip the active identity.").argument("<alias>", "the keyring entry to activate").option("--json", "emit machine-readable JSON").action(async (alias, flags) => {
|
|
24598
|
-
process.
|
|
24941
|
+
process.exitCode = await runKeyringUse(alias, { json: Boolean(flags.json) });
|
|
24599
24942
|
});
|
|
24600
24943
|
}
|
|
24601
24944
|
function registerContextCommands(program, deps) {
|
|
@@ -24606,18 +24949,17 @@ function registerContextCommands(program, deps) {
|
|
|
24606
24949
|
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
24607
24950
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
24608
24951
|
`);
|
|
24609
|
-
process.
|
|
24952
|
+
process.exitCode = 2;
|
|
24953
|
+
return;
|
|
24610
24954
|
}
|
|
24611
|
-
process.
|
|
24612
|
-
|
|
24613
|
-
|
|
24614
|
-
|
|
24615
|
-
|
|
24616
|
-
|
|
24617
|
-
|
|
24618
|
-
|
|
24619
|
-
deps
|
|
24620
|
-
)
|
|
24955
|
+
process.exitCode = await runContextCreate(
|
|
24956
|
+
{
|
|
24957
|
+
...toCommonOptions(flags),
|
|
24958
|
+
contextId,
|
|
24959
|
+
name: flags.name,
|
|
24960
|
+
tenant: flags.tenant
|
|
24961
|
+
},
|
|
24962
|
+
deps
|
|
24621
24963
|
);
|
|
24622
24964
|
}
|
|
24623
24965
|
);
|
|
@@ -24626,13 +24968,12 @@ function registerContextCommands(program, deps) {
|
|
|
24626
24968
|
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
24627
24969
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
24628
24970
|
`);
|
|
24629
|
-
process.
|
|
24971
|
+
process.exitCode = 2;
|
|
24972
|
+
return;
|
|
24630
24973
|
}
|
|
24631
|
-
process.
|
|
24632
|
-
|
|
24633
|
-
|
|
24634
|
-
deps
|
|
24635
|
-
)
|
|
24974
|
+
process.exitCode = await runContextList(
|
|
24975
|
+
{ ...toCommonOptions(flags), mine: Boolean(flags.mine), tenant: flags.tenant },
|
|
24976
|
+
deps
|
|
24636
24977
|
);
|
|
24637
24978
|
});
|
|
24638
24979
|
const get = context.command("get").description("Show one app context.").argument("<contextId>").option("--tenant <tenant>", "tenant the context lives in: 'test' or 'live' (default live)");
|
|
@@ -24640,13 +24981,12 @@ function registerContextCommands(program, deps) {
|
|
|
24640
24981
|
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
24641
24982
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
24642
24983
|
`);
|
|
24643
|
-
process.
|
|
24984
|
+
process.exitCode = 2;
|
|
24985
|
+
return;
|
|
24644
24986
|
}
|
|
24645
|
-
process.
|
|
24646
|
-
|
|
24647
|
-
|
|
24648
|
-
deps
|
|
24649
|
-
)
|
|
24987
|
+
process.exitCode = await runContextGet(
|
|
24988
|
+
{ ...toCommonOptions(flags), contextId, tenant: flags.tenant },
|
|
24989
|
+
deps
|
|
24650
24990
|
);
|
|
24651
24991
|
});
|
|
24652
24992
|
const destroy = context.command("destroy").description(
|
|
@@ -24657,18 +24997,17 @@ function registerContextCommands(program, deps) {
|
|
|
24657
24997
|
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
24658
24998
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
24659
24999
|
`);
|
|
24660
|
-
process.
|
|
25000
|
+
process.exitCode = 2;
|
|
25001
|
+
return;
|
|
24661
25002
|
}
|
|
24662
|
-
process.
|
|
24663
|
-
|
|
24664
|
-
|
|
24665
|
-
|
|
24666
|
-
|
|
24667
|
-
|
|
24668
|
-
|
|
24669
|
-
|
|
24670
|
-
deps
|
|
24671
|
-
)
|
|
25003
|
+
process.exitCode = await runContextDestroy(
|
|
25004
|
+
{
|
|
25005
|
+
...toCommonOptions(flags),
|
|
25006
|
+
contextId,
|
|
25007
|
+
force: Boolean(flags.force),
|
|
25008
|
+
tenant: flags.tenant
|
|
25009
|
+
},
|
|
25010
|
+
deps
|
|
24672
25011
|
);
|
|
24673
25012
|
}
|
|
24674
25013
|
);
|
|
@@ -24679,18 +25018,16 @@ function registerIssuersCommands(program, deps) {
|
|
|
24679
25018
|
addCommonAuthOptions(list).action(
|
|
24680
25019
|
async (flags) => {
|
|
24681
25020
|
const tenant = parseTenantFlag(flags.tenant);
|
|
24682
|
-
|
|
24683
|
-
|
|
24684
|
-
);
|
|
25021
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25022
|
+
process.exitCode = await runIssuersList({ ...toCommonOptions(flags), context: flags.context, tenant }, deps);
|
|
24685
25023
|
}
|
|
24686
25024
|
);
|
|
24687
25025
|
const get = issuers.command("get").description("Show one registered issuer.").argument("<issuerId>").requiredOption("--context <c>", "app context the issuer is registered under").option("--tenant <tenant>", "tenant to read: 'test' or 'live' (default live)");
|
|
24688
25026
|
addCommonAuthOptions(get).action(
|
|
24689
25027
|
async (issuerId, flags) => {
|
|
24690
25028
|
const tenant = parseTenantFlag(flags.tenant);
|
|
24691
|
-
|
|
24692
|
-
|
|
24693
|
-
);
|
|
25029
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25030
|
+
process.exitCode = await runIssuersGet({ ...toCommonOptions(flags), context: flags.context, issuerId, tenant }, deps);
|
|
24694
25031
|
}
|
|
24695
25032
|
);
|
|
24696
25033
|
const register = issuers.command("register").description("Register (idempotent by issuerId) a trusted third-party IdP issuer.").requiredOption("--context <c>", "app context an exchanged token targets").requiredOption("--issuer-id <id>", "short slug for this issuer (3-31 chars; lowercase letter + lowercase/digit/dash)").requiredOption("--issuer <url>", "the IdP's iss claim value").requiredOption("--jwks-uri <url>", "the IdP's remote JWKS endpoint").requiredOption("--audience <aud>", "the required aud claim value (globally unique with --issuer)").option("--sub-claim <claim>", "claim carrying the subject identifier (default 'sub')").option("--email-claim <claim>", "claim carrying the subject's email (default 'email')").option("--userinfo-uri <url>", "OIDC userinfo endpoint, used as an email-resolution fallback").option(
|
|
@@ -24706,24 +25043,23 @@ function registerIssuersCommands(program, deps) {
|
|
|
24706
25043
|
addCommonAuthOptions(register).action(
|
|
24707
25044
|
async (flags) => {
|
|
24708
25045
|
const tenant = parseTenantFlag(flags.tenant);
|
|
24709
|
-
|
|
24710
|
-
|
|
24711
|
-
|
|
24712
|
-
|
|
24713
|
-
|
|
24714
|
-
|
|
24715
|
-
|
|
24716
|
-
|
|
24717
|
-
|
|
24718
|
-
|
|
24719
|
-
|
|
24720
|
-
|
|
24721
|
-
|
|
24722
|
-
|
|
24723
|
-
|
|
24724
|
-
|
|
24725
|
-
|
|
24726
|
-
)
|
|
25046
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25047
|
+
process.exitCode = await runIssuersRegister(
|
|
25048
|
+
{
|
|
25049
|
+
...toCommonOptions(flags),
|
|
25050
|
+
context: flags.context,
|
|
25051
|
+
issuerId: flags.issuerId,
|
|
25052
|
+
issuer: flags.issuer,
|
|
25053
|
+
jwksUri: flags.jwksUri,
|
|
25054
|
+
audience: flags.audience,
|
|
25055
|
+
subClaim: flags.subClaim,
|
|
25056
|
+
emailClaim: flags.emailClaim,
|
|
25057
|
+
userinfoUri: flags.userinfoUri,
|
|
25058
|
+
selfSignupPolicy: flags.selfSignupPolicy,
|
|
25059
|
+
capturedClaim: flags.capturedClaim,
|
|
25060
|
+
tenant
|
|
25061
|
+
},
|
|
25062
|
+
deps
|
|
24727
25063
|
);
|
|
24728
25064
|
}
|
|
24729
25065
|
);
|
|
@@ -24740,24 +25076,23 @@ function registerIssuersCommands(program, deps) {
|
|
|
24740
25076
|
addCommonAuthOptions(update).action(
|
|
24741
25077
|
async (issuerId, flags) => {
|
|
24742
25078
|
const tenant = parseTenantFlag(flags.tenant);
|
|
24743
|
-
|
|
24744
|
-
|
|
24745
|
-
|
|
24746
|
-
|
|
24747
|
-
|
|
24748
|
-
|
|
24749
|
-
|
|
24750
|
-
|
|
24751
|
-
|
|
24752
|
-
|
|
24753
|
-
|
|
24754
|
-
|
|
24755
|
-
|
|
24756
|
-
|
|
24757
|
-
|
|
24758
|
-
|
|
24759
|
-
|
|
24760
|
-
)
|
|
25079
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25080
|
+
process.exitCode = await runIssuersUpdate(
|
|
25081
|
+
{
|
|
25082
|
+
...toCommonOptions(flags),
|
|
25083
|
+
context: flags.context,
|
|
25084
|
+
issuerId,
|
|
25085
|
+
subClaim: flags.subClaim,
|
|
25086
|
+
emailClaim: flags.emailClaim,
|
|
25087
|
+
userinfoUri: flags.userinfoUri,
|
|
25088
|
+
status: flags.status,
|
|
25089
|
+
selfSignupPolicy: flags.selfSignupPolicy,
|
|
25090
|
+
clearSelfSignupPolicies: Boolean(flags.clearSelfSignupPolicies),
|
|
25091
|
+
capturedClaim: flags.capturedClaim,
|
|
25092
|
+
clearCapturedClaims: Boolean(flags.clearCapturedClaims),
|
|
25093
|
+
tenant
|
|
25094
|
+
},
|
|
25095
|
+
deps
|
|
24761
25096
|
);
|
|
24762
25097
|
}
|
|
24763
25098
|
);
|
|
@@ -24765,11 +25100,10 @@ function registerIssuersCommands(program, deps) {
|
|
|
24765
25100
|
addCommonAuthOptions(del).action(
|
|
24766
25101
|
async (issuerId, flags) => {
|
|
24767
25102
|
const tenant = parseTenantFlag(flags.tenant);
|
|
24768
|
-
|
|
24769
|
-
|
|
24770
|
-
|
|
24771
|
-
|
|
24772
|
-
)
|
|
25103
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25104
|
+
process.exitCode = await runIssuersDelete(
|
|
25105
|
+
{ ...toCommonOptions(flags), context: flags.context, issuerId, force: Boolean(flags.force), tenant },
|
|
25106
|
+
deps
|
|
24773
25107
|
);
|
|
24774
25108
|
}
|
|
24775
25109
|
);
|
|
@@ -24785,33 +25119,30 @@ function registerNamespaceCommands(program, deps) {
|
|
|
24785
25119
|
` \u2716 --specificity-rank must be an integer between 0 and 1000000 (got '${flags.specificityRank}').
|
|
24786
25120
|
`
|
|
24787
25121
|
);
|
|
24788
|
-
process.
|
|
25122
|
+
process.exitCode = 2;
|
|
25123
|
+
return;
|
|
24789
25124
|
}
|
|
24790
|
-
process.
|
|
24791
|
-
|
|
24792
|
-
|
|
24793
|
-
|
|
24794
|
-
|
|
24795
|
-
|
|
24796
|
-
|
|
24797
|
-
|
|
24798
|
-
|
|
24799
|
-
|
|
24800
|
-
deps
|
|
24801
|
-
)
|
|
25125
|
+
process.exitCode = await runNamespaceRegister(
|
|
25126
|
+
{
|
|
25127
|
+
...toCommonOptions(flags),
|
|
25128
|
+
namespace: name,
|
|
25129
|
+
context: flags.context,
|
|
25130
|
+
specificityRank,
|
|
25131
|
+
entityBacked: Boolean(flags.entityBacked),
|
|
25132
|
+
defaultSchemaId: flags.defaultSchemaId
|
|
25133
|
+
},
|
|
25134
|
+
deps
|
|
24802
25135
|
);
|
|
24803
25136
|
}
|
|
24804
25137
|
);
|
|
24805
25138
|
const list = namespace.command("list").description("List scope-namespace registrations \u2014 tenant-wide, or one context\u2019s own.").option("--context <c>", "list one app context\u2019s own registrations instead of the tenant-wide set");
|
|
24806
25139
|
addCommonAuthOptions(list).action(async (flags) => {
|
|
24807
|
-
process.
|
|
25140
|
+
process.exitCode = await runNamespaceList({ ...toCommonOptions(flags), context: flags.context }, deps);
|
|
24808
25141
|
});
|
|
24809
25142
|
const get = namespace.command("get").description("Show one namespace registration \u2014 tenant-wide, or one context\u2019s own.").argument("<name>").option("--context <c>", "resolve for one app context\u2019s own registration instead of the tenant-wide one");
|
|
24810
25143
|
addCommonAuthOptions(get).action(
|
|
24811
25144
|
async (name, flags) => {
|
|
24812
|
-
process.
|
|
24813
|
-
await runNamespaceGet({ ...toCommonOptions(flags), namespace: name, context: flags.context }, deps)
|
|
24814
|
-
);
|
|
25145
|
+
process.exitCode = await runNamespaceGet({ ...toCommonOptions(flags), namespace: name, context: flags.context }, deps);
|
|
24815
25146
|
}
|
|
24816
25147
|
);
|
|
24817
25148
|
}
|
|
@@ -24820,59 +25151,59 @@ function registerScriptsCommands(program, deps) {
|
|
|
24820
25151
|
const push = scripts.command("push").description("Store a new version of a script name (always creates a version unless --if-changed).").argument("<name>", "script name: 1-64 chars of letters, digits, '_' and '-'").requiredOption("--context <c>", "app context to push into").requiredOption("--source-file <path>", "file holding the script source (max 300,000 UTF-8 bytes)").option("--input-contract <text>", "free-text description of the expected input shape (descriptive only)").option("--input-contract-file <path>", "read the input contract from a file instead").option("--if-changed", "push nothing when the current version already carries this exact content").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)");
|
|
24821
25152
|
addCommonAuthOptions(push).action(
|
|
24822
25153
|
async (name, flags) => {
|
|
24823
|
-
|
|
24824
|
-
|
|
24825
|
-
|
|
24826
|
-
|
|
24827
|
-
|
|
24828
|
-
|
|
24829
|
-
|
|
24830
|
-
|
|
24831
|
-
|
|
24832
|
-
|
|
24833
|
-
|
|
24834
|
-
|
|
24835
|
-
|
|
24836
|
-
|
|
25154
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25155
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25156
|
+
process.exitCode = await runScriptsPush(
|
|
25157
|
+
{
|
|
25158
|
+
...toCommonOptions(flags),
|
|
25159
|
+
name,
|
|
25160
|
+
context: flags.context,
|
|
25161
|
+
sourceFile: flags.sourceFile,
|
|
25162
|
+
inputContract: flags.inputContract,
|
|
25163
|
+
inputContractFile: flags.inputContractFile,
|
|
25164
|
+
ifChanged: Boolean(flags.ifChanged),
|
|
25165
|
+
tenant
|
|
25166
|
+
},
|
|
25167
|
+
deps
|
|
24837
25168
|
);
|
|
24838
25169
|
}
|
|
24839
25170
|
);
|
|
24840
25171
|
const list = scripts.command("list").description("List script versions \u2014 every version of one --name (oldest first), or all scripts (newest first).").requiredOption("--context <c>", "app context to read").option("--name <name>", "list every version of this script name instead of a flat cross-name list").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)");
|
|
24841
25172
|
addCommonAuthOptions(list).action(
|
|
24842
25173
|
async (flags) => {
|
|
24843
|
-
|
|
24844
|
-
|
|
24845
|
-
|
|
24846
|
-
|
|
24847
|
-
|
|
25174
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25175
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25176
|
+
process.exitCode = await runScriptsList(
|
|
25177
|
+
{ ...toCommonOptions(flags), context: flags.context, name: flags.name, tenant },
|
|
25178
|
+
deps
|
|
24848
25179
|
);
|
|
24849
25180
|
}
|
|
24850
25181
|
);
|
|
24851
25182
|
const get = scripts.command("get").description("Show one script version, including its full source.").argument("<id>", "the script VERSION id (each version has its own)").requiredOption("--context <c>", "app context to read").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)");
|
|
24852
25183
|
addCommonAuthOptions(get).action(
|
|
24853
25184
|
async (id, flags) => {
|
|
24854
|
-
|
|
24855
|
-
|
|
24856
|
-
|
|
24857
|
-
|
|
24858
|
-
|
|
25185
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25186
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25187
|
+
process.exitCode = await runScriptsGet(
|
|
25188
|
+
{ ...toCommonOptions(flags), id, context: flags.context, tenant },
|
|
25189
|
+
deps
|
|
24859
25190
|
);
|
|
24860
25191
|
}
|
|
24861
25192
|
);
|
|
24862
25193
|
const del = scripts.command("delete").description("Permanently delete ONE script version (other versions of the name are unaffected).").argument("<id>", "the script VERSION id").requiredOption("--context <c>", "app context to delete from").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)").option("--force", "skip the interactive confirmation (required in a non-interactive shell)");
|
|
24863
25194
|
addCommonAuthOptions(del).action(
|
|
24864
25195
|
async (id, flags) => {
|
|
24865
|
-
|
|
24866
|
-
|
|
24867
|
-
|
|
24868
|
-
|
|
24869
|
-
|
|
24870
|
-
|
|
24871
|
-
|
|
24872
|
-
|
|
24873
|
-
|
|
24874
|
-
|
|
24875
|
-
|
|
25196
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25197
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25198
|
+
process.exitCode = await runScriptsDelete(
|
|
25199
|
+
{
|
|
25200
|
+
...toCommonOptions(flags),
|
|
25201
|
+
id,
|
|
25202
|
+
context: flags.context,
|
|
25203
|
+
tenant,
|
|
25204
|
+
force: Boolean(flags.force)
|
|
25205
|
+
},
|
|
25206
|
+
deps
|
|
24876
25207
|
);
|
|
24877
25208
|
}
|
|
24878
25209
|
);
|
|
@@ -24892,9 +25223,11 @@ function registerRoleCommands(program, deps) {
|
|
|
24892
25223
|
).option("--description <text>", "free-text description").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24893
25224
|
addCommonAuthOptions(create).action(
|
|
24894
25225
|
async (flags) => {
|
|
25226
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25227
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24895
25228
|
process.exitCode = await runRoleCreate({
|
|
24896
25229
|
...toCommonOptions(flags),
|
|
24897
|
-
tenant
|
|
25230
|
+
tenant,
|
|
24898
25231
|
context: flags.context,
|
|
24899
25232
|
roleId: flags.roleId,
|
|
24900
25233
|
name: flags.name,
|
|
@@ -24908,16 +25241,20 @@ function registerRoleCommands(program, deps) {
|
|
|
24908
25241
|
);
|
|
24909
25242
|
const list = role.command("list").description("List the roles defined in a context.").requiredOption("--context <c>", "app context").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24910
25243
|
addCommonAuthOptions(list).action(async (flags) => {
|
|
25244
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25245
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24911
25246
|
process.exitCode = await runRoleList(
|
|
24912
|
-
{ ...toCommonOptions(flags), tenant
|
|
25247
|
+
{ ...toCommonOptions(flags), tenant, context: flags.context },
|
|
24913
25248
|
deps
|
|
24914
25249
|
);
|
|
24915
25250
|
});
|
|
24916
25251
|
const get = role.command("get").description("Show one role by id.").requiredOption("--context <c>", "app context").requiredOption("--role-id <id>", "role id").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24917
25252
|
addCommonAuthOptions(get).action(
|
|
24918
25253
|
async (flags) => {
|
|
25254
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25255
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24919
25256
|
process.exitCode = await runRoleGet(
|
|
24920
|
-
{ ...toCommonOptions(flags), tenant
|
|
25257
|
+
{ ...toCommonOptions(flags), tenant, context: flags.context, roleId: flags.roleId },
|
|
24921
25258
|
deps
|
|
24922
25259
|
);
|
|
24923
25260
|
}
|
|
@@ -24925,8 +25262,10 @@ function registerRoleCommands(program, deps) {
|
|
|
24925
25262
|
const del = role.command("delete").description("Delete a role (referencing profiles must be reassigned).").requiredOption("--context <c>", "app context").requiredOption("--role-id <id>", "role id").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24926
25263
|
addAuthOptions(del).action(
|
|
24927
25264
|
async (flags) => {
|
|
25265
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25266
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24928
25267
|
process.exitCode = await runRoleDelete(
|
|
24929
|
-
{ ...toCommonOptions(flags), tenant
|
|
25268
|
+
{ ...toCommonOptions(flags), tenant, context: flags.context, roleId: flags.roleId },
|
|
24930
25269
|
deps
|
|
24931
25270
|
);
|
|
24932
25271
|
}
|
|
@@ -24937,9 +25276,11 @@ function registerAccessCommands(program, deps) {
|
|
|
24937
25276
|
const grant = access2.command("grant").description("Bind a principal to a context via a role or inline actions.").requiredOption("--principal <p>", "principal: 'usr_<userId>', 'key_<keyId>', or 'me' (your own user)").requiredOption("--context <c>", "app context").option("--role <roleId>", "reference a Role (repeatable \u2014 2+ compose additively; XOR --actions)", collect, []).option("--actions <csv>", "inline single-clause actions, comma-separated (XOR --role)").option("--scope <ns:value>", "identity override: a scope the principal acts under (repeatable, e.g. org:<id>)", collect, []).option("--identity-overrides <json>", "general identity overrides as a JSON object (merged over --scope)").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24938
25277
|
addCommonAuthOptions(grant).action(
|
|
24939
25278
|
async (flags) => {
|
|
25279
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25280
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24940
25281
|
process.exitCode = await runAccessGrant({
|
|
24941
25282
|
...toCommonOptions(flags),
|
|
24942
|
-
tenant
|
|
25283
|
+
tenant,
|
|
24943
25284
|
principal: flags.principal,
|
|
24944
25285
|
context: flags.context,
|
|
24945
25286
|
role: flags.role,
|
|
@@ -24952,8 +25293,10 @@ function registerAccessCommands(program, deps) {
|
|
|
24952
25293
|
const revoke = access2.command("revoke").description("Remove a principal\u2019s binding in a context.").requiredOption("--principal <p>", "principal: 'usr_<userId>', 'key_<keyId>', or 'me' (your own user)").requiredOption("--context <c>", "app context").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24953
25294
|
addAuthOptions(revoke).action(
|
|
24954
25295
|
async (flags) => {
|
|
25296
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25297
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24955
25298
|
process.exitCode = await runAccessRevoke(
|
|
24956
|
-
{ ...toCommonOptions(flags), tenant
|
|
25299
|
+
{ ...toCommonOptions(flags), tenant, principal: flags.principal, context: flags.context },
|
|
24957
25300
|
deps
|
|
24958
25301
|
);
|
|
24959
25302
|
}
|
|
@@ -24961,8 +25304,10 @@ function registerAccessCommands(program, deps) {
|
|
|
24961
25304
|
const list = access2.command("list").description("List a context\u2019s members (--context) or a principal\u2019s contexts (--principal).").option("--context <c>", "list members of this context").option("--principal <p>", "list contexts this principal is bound to ('me' = your own user)").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24962
25305
|
addCommonAuthOptions(list).action(
|
|
24963
25306
|
async (flags) => {
|
|
25307
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25308
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24964
25309
|
process.exitCode = await runAccessList(
|
|
24965
|
-
{ ...toCommonOptions(flags), tenant
|
|
25310
|
+
{ ...toCommonOptions(flags), tenant, context: flags.context, principal: flags.principal },
|
|
24966
25311
|
deps
|
|
24967
25312
|
);
|
|
24968
25313
|
}
|
|
@@ -24970,8 +25315,10 @@ function registerAccessCommands(program, deps) {
|
|
|
24970
25315
|
const get = access2.command("get").description("Show one principal\u2019s binding in a context.").requiredOption("--principal <p>", "principal: 'usr_<userId>', 'key_<keyId>', or 'me' (your own user)").requiredOption("--context <c>", "app context").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24971
25316
|
addCommonAuthOptions(get).action(
|
|
24972
25317
|
async (flags) => {
|
|
25318
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25319
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24973
25320
|
process.exitCode = await runAccessGet(
|
|
24974
|
-
{ ...toCommonOptions(flags), tenant
|
|
25321
|
+
{ ...toCommonOptions(flags), tenant, principal: flags.principal, context: flags.context },
|
|
24975
25322
|
deps
|
|
24976
25323
|
);
|
|
24977
25324
|
}
|
|
@@ -24979,8 +25326,10 @@ function registerAccessCommands(program, deps) {
|
|
|
24979
25326
|
const explain = access2.command("explain").description("Show what a principal\u2019s binding grants: its clauses + a concrete access matrix.").requiredOption("--principal <p>", "principal: 'usr_<userId>', 'key_<keyId>', or 'me' (your own user)").requiredOption("--context <c>", "app context").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24980
25327
|
addCommonAuthOptions(explain).action(
|
|
24981
25328
|
async (flags) => {
|
|
25329
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25330
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24982
25331
|
process.exitCode = await runAccessExplain(
|
|
24983
|
-
{ ...toCommonOptions(flags), tenant
|
|
25332
|
+
{ ...toCommonOptions(flags), tenant, principal: flags.principal, context: flags.context },
|
|
24984
25333
|
deps
|
|
24985
25334
|
);
|
|
24986
25335
|
}
|
|
@@ -24997,31 +25346,27 @@ function registerIdentityCommands(program, deps) {
|
|
|
24997
25346
|
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");
|
|
24998
25347
|
addCommonAuthOptions(create).action(
|
|
24999
25348
|
async (flags) => {
|
|
25000
|
-
process.
|
|
25001
|
-
|
|
25002
|
-
|
|
25003
|
-
|
|
25004
|
-
|
|
25005
|
-
|
|
25006
|
-
|
|
25007
|
-
|
|
25008
|
-
|
|
25009
|
-
|
|
25010
|
-
}, deps)
|
|
25011
|
-
);
|
|
25349
|
+
process.exitCode = await runIdentityCreate({
|
|
25350
|
+
...toCommonOptions(flags),
|
|
25351
|
+
type: flags.type,
|
|
25352
|
+
externalId: flags.externalId,
|
|
25353
|
+
name: flags.name,
|
|
25354
|
+
email: flags.email,
|
|
25355
|
+
service: flags.service,
|
|
25356
|
+
scope: flags.scope,
|
|
25357
|
+
metadata: flags.metadata
|
|
25358
|
+
}, deps);
|
|
25012
25359
|
}
|
|
25013
25360
|
);
|
|
25014
25361
|
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));
|
|
25015
25362
|
addCommonAuthOptions(list).action(
|
|
25016
25363
|
async (flags) => {
|
|
25017
|
-
process.
|
|
25018
|
-
|
|
25019
|
-
|
|
25020
|
-
|
|
25021
|
-
|
|
25022
|
-
|
|
25023
|
-
}, deps)
|
|
25024
|
-
);
|
|
25364
|
+
process.exitCode = await runIdentityList({
|
|
25365
|
+
...toCommonOptions(flags),
|
|
25366
|
+
type: flags.type,
|
|
25367
|
+
externalId: flags.externalId,
|
|
25368
|
+
limit: flags.limit
|
|
25369
|
+
}, deps);
|
|
25025
25370
|
}
|
|
25026
25371
|
);
|
|
25027
25372
|
const listAll = identity.command("list-all").description(
|
|
@@ -25030,38 +25375,35 @@ function registerIdentityCommands(program, deps) {
|
|
|
25030
25375
|
addCommonAuthOptions(listAll).action(
|
|
25031
25376
|
async (flags) => {
|
|
25032
25377
|
const tenant = parseTenantFlag(flags.tenant);
|
|
25033
|
-
|
|
25034
|
-
|
|
25035
|
-
|
|
25036
|
-
|
|
25037
|
-
)
|
|
25378
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25379
|
+
process.exitCode = await runIdentityListAll(
|
|
25380
|
+
{ ...toCommonOptions(flags), externalId: flags.externalId, limit: flags.limit, tenant },
|
|
25381
|
+
deps
|
|
25038
25382
|
);
|
|
25039
25383
|
}
|
|
25040
25384
|
);
|
|
25041
25385
|
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)");
|
|
25042
25386
|
addCommonAuthOptions(get).action(async (flags) => {
|
|
25043
|
-
process.
|
|
25387
|
+
process.exitCode = await runIdentityGet({ ...toCommonOptions(flags), type: flags.type, id: flags.id }, deps);
|
|
25044
25388
|
});
|
|
25045
25389
|
const del = identity.command("delete").description("Delete a principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id");
|
|
25046
25390
|
addAuthOptions(del).action(async (flags) => {
|
|
25047
|
-
process.
|
|
25391
|
+
process.exitCode = await runIdentityDelete({ ...toCommonOptions(flags), type: flags.type, id: flags.id }, deps);
|
|
25048
25392
|
});
|
|
25049
25393
|
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");
|
|
25050
25394
|
addCommonAuthOptions(invite).action(
|
|
25051
25395
|
async (flags) => {
|
|
25052
|
-
process.
|
|
25053
|
-
|
|
25054
|
-
|
|
25055
|
-
|
|
25056
|
-
|
|
25057
|
-
|
|
25058
|
-
|
|
25059
|
-
|
|
25060
|
-
|
|
25061
|
-
|
|
25062
|
-
|
|
25063
|
-
deps
|
|
25064
|
-
)
|
|
25396
|
+
process.exitCode = await runIdentityInvite(
|
|
25397
|
+
{
|
|
25398
|
+
...toCommonOptions(flags),
|
|
25399
|
+
context: flags.context,
|
|
25400
|
+
role: flags.role,
|
|
25401
|
+
email: flags.email,
|
|
25402
|
+
firstName: flags.firstName,
|
|
25403
|
+
acceptUrl: flags.acceptUrl,
|
|
25404
|
+
noSendEmail: flags.sendEmail === false
|
|
25405
|
+
},
|
|
25406
|
+
deps
|
|
25065
25407
|
);
|
|
25066
25408
|
}
|
|
25067
25409
|
);
|
|
@@ -25087,17 +25429,16 @@ function registerKeyCommands(program, deps) {
|
|
|
25087
25429
|
);
|
|
25088
25430
|
addAuthOptions(issue).action(async (flags) => {
|
|
25089
25431
|
const tenant = parseTenantFlag(flags.tenant);
|
|
25090
|
-
|
|
25091
|
-
|
|
25092
|
-
|
|
25093
|
-
|
|
25094
|
-
|
|
25095
|
-
|
|
25096
|
-
|
|
25097
|
-
|
|
25098
|
-
|
|
25099
|
-
|
|
25100
|
-
);
|
|
25432
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25433
|
+
process.exitCode = await runKeyIssue({
|
|
25434
|
+
...toCommonOptions(flags),
|
|
25435
|
+
principal: flags.principal,
|
|
25436
|
+
context: flags.context,
|
|
25437
|
+
name: flags.name,
|
|
25438
|
+
label: flags.label,
|
|
25439
|
+
tenant,
|
|
25440
|
+
...toKeyDestination(flags)
|
|
25441
|
+
}, deps);
|
|
25101
25442
|
});
|
|
25102
25443
|
const list = key.command("list").description("List active scoped keys in one context (default, or --context <id>); --all-contexts for every context.").option("--principal <p>", "only keys for this principal (usr_<id> or <id>)").option("--context <c>", "only keys in this context (default: the default context)").option(
|
|
25103
25444
|
"--all-contexts",
|
|
@@ -25107,20 +25448,20 @@ function registerKeyCommands(program, deps) {
|
|
|
25107
25448
|
async (flags) => {
|
|
25108
25449
|
if (flags.allContexts && flags.tenant !== void 0) {
|
|
25109
25450
|
process.stderr.write(" \u2716 --tenant is not combinable with --all-contexts (which already spans both tenants).\n");
|
|
25110
|
-
process.
|
|
25451
|
+
process.exitCode = 2;
|
|
25452
|
+
return;
|
|
25111
25453
|
}
|
|
25112
25454
|
const tenant = parseTenantFlag(flags.tenant);
|
|
25113
|
-
|
|
25114
|
-
|
|
25115
|
-
|
|
25116
|
-
|
|
25117
|
-
|
|
25118
|
-
|
|
25119
|
-
|
|
25120
|
-
|
|
25121
|
-
|
|
25122
|
-
|
|
25123
|
-
)
|
|
25455
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25456
|
+
process.exitCode = await runKeyList(
|
|
25457
|
+
{
|
|
25458
|
+
...toCommonOptions(flags),
|
|
25459
|
+
principal: flags.principal,
|
|
25460
|
+
context: flags.context,
|
|
25461
|
+
allContexts: Boolean(flags.allContexts),
|
|
25462
|
+
tenant
|
|
25463
|
+
},
|
|
25464
|
+
deps
|
|
25124
25465
|
);
|
|
25125
25466
|
}
|
|
25126
25467
|
);
|
|
@@ -25128,14 +25469,16 @@ function registerKeyCommands(program, deps) {
|
|
|
25128
25469
|
addCommonAuthOptions(get).action(
|
|
25129
25470
|
async (keyId, flags) => {
|
|
25130
25471
|
const tenant = parseTenantFlag(flags.tenant);
|
|
25131
|
-
|
|
25472
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25473
|
+
process.exitCode = await runKeyGet(keyId, { ...toCommonOptions(flags), context: flags.context, tenant }, deps);
|
|
25132
25474
|
}
|
|
25133
25475
|
);
|
|
25134
25476
|
const revoke = key.command("revoke").description("Revoke a key (soft-delete; ~5 min to take effect).").argument("<keyId>").option("--context <c>", "context the key is bound to (default: the default context)").option("--tenant <tenant>", "tenant the key lives in: 'test' or 'live' (default live)");
|
|
25135
25477
|
addAuthOptions(revoke).action(
|
|
25136
25478
|
async (keyId, flags) => {
|
|
25137
25479
|
const tenant = parseTenantFlag(flags.tenant);
|
|
25138
|
-
|
|
25480
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25481
|
+
process.exitCode = await runKeyRevoke(keyId, { ...toCommonOptions(flags), context: flags.context, tenant }, deps);
|
|
25139
25482
|
}
|
|
25140
25483
|
);
|
|
25141
25484
|
const rotate = addKeyDestinationOptions(
|
|
@@ -25146,17 +25489,16 @@ function registerKeyCommands(program, deps) {
|
|
|
25146
25489
|
);
|
|
25147
25490
|
addAuthOptions(rotate).action(async (flags) => {
|
|
25148
25491
|
const tenant = parseTenantFlag(flags.tenant);
|
|
25149
|
-
|
|
25150
|
-
|
|
25151
|
-
|
|
25152
|
-
|
|
25153
|
-
|
|
25154
|
-
|
|
25155
|
-
|
|
25156
|
-
|
|
25157
|
-
|
|
25158
|
-
|
|
25159
|
-
);
|
|
25492
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25493
|
+
process.exitCode = await runKeyRotate({
|
|
25494
|
+
...toCommonOptions(flags),
|
|
25495
|
+
principal: flags.principal,
|
|
25496
|
+
context: flags.context,
|
|
25497
|
+
name: flags.name,
|
|
25498
|
+
forceNew: flags.forceNew,
|
|
25499
|
+
tenant,
|
|
25500
|
+
...toKeyDestination(flags)
|
|
25501
|
+
}, deps);
|
|
25160
25502
|
});
|
|
25161
25503
|
}
|
|
25162
25504
|
|
|
@@ -25169,7 +25511,8 @@ async function main() {
|
|
|
25169
25511
|
const sub = process.argv[2];
|
|
25170
25512
|
const passthrough = sub ? PASSTHROUGH_COMMANDS[sub] : void 0;
|
|
25171
25513
|
if (passthrough) {
|
|
25172
|
-
process.
|
|
25514
|
+
process.exitCode = await passthrough(process.argv.slice(3));
|
|
25515
|
+
return;
|
|
25173
25516
|
}
|
|
25174
25517
|
await buildProgram().parseAsync(process.argv);
|
|
25175
25518
|
}
|
|
@@ -25180,6 +25523,6 @@ main().catch((err) => {
|
|
|
25180
25523
|
process.stderr.write(`${err.stack}
|
|
25181
25524
|
`);
|
|
25182
25525
|
}
|
|
25183
|
-
process.
|
|
25526
|
+
process.exitCode = 2;
|
|
25184
25527
|
});
|
|
25185
25528
|
//# sourceMappingURL=cli.mjs.map
|