@vectros-ai/cli 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORING.md +12 -8
- package/CHANGELOG.md +98 -0
- package/README.md +119 -23
- package/dist/cli.js +971 -179
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +971 -179
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -523,7 +523,7 @@ var require_graceful_fs = __commonJS({
|
|
|
523
523
|
function patch(fs5) {
|
|
524
524
|
polyfills(fs5);
|
|
525
525
|
fs5.gracefulify = patch;
|
|
526
|
-
fs5.createReadStream =
|
|
526
|
+
fs5.createReadStream = createReadStream3;
|
|
527
527
|
fs5.createWriteStream = createWriteStream;
|
|
528
528
|
var fs$readFile = fs5.readFile;
|
|
529
529
|
fs5.readFile = readFile4;
|
|
@@ -733,7 +733,7 @@ var require_graceful_fs = __commonJS({
|
|
|
733
733
|
}
|
|
734
734
|
});
|
|
735
735
|
}
|
|
736
|
-
function
|
|
736
|
+
function createReadStream3(path4, options) {
|
|
737
737
|
return new fs5.ReadStream(path4, options);
|
|
738
738
|
}
|
|
739
739
|
function createWriteStream(path4, options) {
|
|
@@ -1639,6 +1639,7 @@ __export(api_exports, {
|
|
|
1639
1639
|
ContentTooLargeError: () => ContentTooLargeError,
|
|
1640
1640
|
DocumentContextEvent: () => DocumentContextEvent,
|
|
1641
1641
|
DocumentLookupRequest: () => DocumentLookupRequest,
|
|
1642
|
+
DocumentPatchRequest: () => DocumentPatchRequest,
|
|
1642
1643
|
DocumentRequest: () => DocumentRequest,
|
|
1643
1644
|
DocumentResponse: () => DocumentResponse,
|
|
1644
1645
|
DoneEvent: () => DoneEvent,
|
|
@@ -1982,6 +1983,13 @@ var DocumentLookupRequest;
|
|
|
1982
1983
|
Desc: "desc"
|
|
1983
1984
|
};
|
|
1984
1985
|
})(DocumentLookupRequest || (DocumentLookupRequest = {}));
|
|
1986
|
+
var DocumentPatchRequest;
|
|
1987
|
+
((DocumentPatchRequest2) => {
|
|
1988
|
+
DocumentPatchRequest2.Status = {
|
|
1989
|
+
Active: "ACTIVE",
|
|
1990
|
+
Archived: "ARCHIVED"
|
|
1991
|
+
};
|
|
1992
|
+
})(DocumentPatchRequest || (DocumentPatchRequest = {}));
|
|
1985
1993
|
var FileUploadRequest;
|
|
1986
1994
|
((FileUploadRequest2) => {
|
|
1987
1995
|
FileUploadRequest2.IndexMode = {
|
|
@@ -1994,6 +2002,7 @@ var FileUploadRequest;
|
|
|
1994
2002
|
var documents_exports = {};
|
|
1995
2003
|
__export(documents_exports, {
|
|
1996
2004
|
DocumentLookupRequest: () => DocumentLookupRequest,
|
|
2005
|
+
DocumentPatchRequest: () => DocumentPatchRequest,
|
|
1997
2006
|
FileUploadRequest: () => FileUploadRequest,
|
|
1998
2007
|
LookupDocumentsRequestOrder: () => LookupDocumentsRequestOrder
|
|
1999
2008
|
});
|
|
@@ -4040,7 +4049,7 @@ var AuthClient = class {
|
|
|
4040
4049
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/jwks");
|
|
4041
4050
|
}
|
|
4042
4051
|
/**
|
|
4043
|
-
* Returns a page of per-subject PHI read-access rows: who read which subject's PHI, when, against which record, and whether any sensitive value was actually revealed in plaintext. Metadata only — never the PHI itself.
|
|
4052
|
+
* Returns a page of per-subject PHI read-access rows: who read which subject's PHI, when, against which record, and whether any sensitive value was actually revealed in plaintext. Metadata only — never the PHI itself. Rows exist only where read-access logging is enabled. Provide at least one query axis: a subject (`subjectType` + `subjectId`) within a `contextId` for the primary accounting query; `resourceId` within a `contextId` for 'who read this record'; `callerKeyId` for 'what did this credential read' (account-wide forensic); or `contextId` alone to enumerate a whole context. `from`/`to` bound the time window. Results are scoped to your account, derived from your token — never from input. Requires the `access-log:r` scope.
|
|
4044
4053
|
*
|
|
4045
4054
|
* @param {Vectros.GetAccessLogRequest} request
|
|
4046
4055
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -4658,6 +4667,7 @@ var AuthClient = class {
|
|
|
4658
4667
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
4659
4668
|
*
|
|
4660
4669
|
* @throws {@link Vectros.BadRequestError}
|
|
4670
|
+
* @throws {@link Vectros.PaymentRequiredError}
|
|
4661
4671
|
* @throws {@link Vectros.ForbiddenError}
|
|
4662
4672
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
4663
4673
|
*
|
|
@@ -4707,6 +4717,8 @@ var AuthClient = class {
|
|
|
4707
4717
|
switch (_response.error.statusCode) {
|
|
4708
4718
|
case 400:
|
|
4709
4719
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
4720
|
+
case 402:
|
|
4721
|
+
throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
|
|
4710
4722
|
case 403:
|
|
4711
4723
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
4712
4724
|
case 429:
|
|
@@ -5742,7 +5754,7 @@ var AuthClient = class {
|
|
|
5742
5754
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/issuers/{issuerId}");
|
|
5743
5755
|
}
|
|
5744
5756
|
/**
|
|
5745
|
-
* Updates the mutable fields of a registered issuer: `subClaim`, `emailClaim`, `userinfoUri`, `capturedClaims`, `restrictedToDomain` (a new non-blank value must already be a VERIFIED domain for your account; an empty string clears it
|
|
5757
|
+
* Updates the mutable fields of a registered issuer: `subClaim`, `emailClaim`, `userinfoUri`, `capturedClaims`, `restrictedToDomain` (a new non-blank value must already be a VERIFIED domain for your account; an empty string clears it where permitted; see `IssuerUpdateRequest.restrictedToDomain` for the full semantics), `status` (`active`/`suspended` — a registration awaiting verification (`pending_verification`) accepts no status change until it is verified; a suspended issuer's tokens are rejected identically to an unregistered issuer at exchange time), and `selfSignupPolicies`. Fields omitted from the body are left unchanged (partial update). `issuer`, `jwksUri`, `audience`, and `contextId` are trust-anchor / routing-pin fields and are immutable via this route — supplying a value that differs from the current registration is rejected with 400; supplying the current value back is a no-op. Rotating a trust anchor requires deleting and re-registering the issuer, which is itself refused while any user is bound through it. **`subClaim` is identity-determining, not merely cosmetic**: it names which verified JWT claim is read as the federated user's identifier, so changing it on an issuer that already has bound users would silently re-identify (or, under self-signup, orphan) every one of them — changing it is therefore refused once any user has bound through this issuer, the same guard `DELETE` already applies, and is only free before the first real login. Requires a root API key or the bootstrap's provisioning capability, gated identically to every other operation on this surface. A credential confined to one app context may only update an issuer registered in that context; naming one registered in another context returns 404, identically to a nonexistent issuerId. A root API key may update any issuer.
|
|
5746
5758
|
*
|
|
5747
5759
|
* @param {Vectros.IssuerUpdateRequest} request
|
|
5748
5760
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6552,7 +6564,7 @@ var ComplianceClient = class {
|
|
|
6552
6564
|
this._options = normalizeClientOptionsWithAuth(options);
|
|
6553
6565
|
}
|
|
6554
6566
|
/**
|
|
6555
|
-
* Submits
|
|
6567
|
+
* Submits an erasure request for a single end-subject (a user, or an identity entity in any namespace). Erasure deletes the records, documents, folders and record schemas the subject solely owns across the declared contexts (and, for a user, its access profiles there), plus its identity and lookup rows when all contexts are covered. Rows co-owned with another owner scope or principal are kept; read-access log entries are not erased. The request is asynchronous: it returns 202 with a `requestId`; poll `GET /v1/erasure-requests/{id}` until the job completes to obtain the completion certificate. Requires a root API key — a scoped credential is rejected with 403.
|
|
6556
6568
|
*
|
|
6557
6569
|
* @param {Vectros.ErasureRequest} request
|
|
6558
6570
|
* @param {ComplianceClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6615,7 +6627,7 @@ var ComplianceClient = class {
|
|
|
6615
6627
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/erasure-requests");
|
|
6616
6628
|
}
|
|
6617
6629
|
/**
|
|
6618
|
-
* Polls an erasure request by id. While the job is still running this returns its status only; once it completes, the response also includes the
|
|
6630
|
+
* Polls an erasure request by id. While the job is still running this returns its status only; once it completes, the response also includes the completion certificate (which contexts were swept, per-context deletion counts, whether the identity was kept, and reports of dangling references and shared rows left intact). Requires a root API key.
|
|
6619
6631
|
*
|
|
6620
6632
|
* @param {Vectros.GetErasureRequestRequest} request
|
|
6621
6633
|
* @param {ComplianceClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6858,7 +6870,7 @@ var DocumentsClient = class {
|
|
|
6858
6870
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents");
|
|
6859
6871
|
}
|
|
6860
6872
|
/**
|
|
6861
|
-
* Creates a document from a raw text string and queues it for asynchronous indexing so it becomes searchable. Optionally supply an `externalId` to make the create idempotent — if a document with the same `externalId` already exists in your context, that existing document is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing document was returned) tells the two apart. To overwrite an existing document's content instead of returning it unchanged, set `?upsert=true` (this also requires the `documents:u` scope). Requires the `documents:c` scope to create. Being returned the existing document on a collision is a read of that document's data and additionally requires the `documents:r` scope — a credential holding `documents:c` alone receives a `400` ("already exists") on collision instead of the document.
|
|
6873
|
+
* Creates a document from a raw text string and queues it for asynchronous indexing so it becomes searchable. Optionally supply an `externalId` to make the create idempotent — if a document with the same `externalId` already exists in your context, that existing document is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing document was returned) tells the two apart. To overwrite an existing document's content instead of returning it unchanged, set `?upsert=true` (this also requires the `documents:u` scope). Requires the `documents:c` scope to create. Being returned the existing document on a collision is a read of that document's data and additionally requires the `documents:r` scope — a credential holding `documents:c` alone receives a `400` ("already exists") on collision instead of the document. Supplying `externalId` WITHOUT `schemaId` is refused (`400`) unless you also pass `confirmUntyped=true` — externalId uniqueness is scoped per schema, so omitting `schemaId` by mistake would otherwise silently create a separate untyped document instead of targeting the typed one you likely meant.
|
|
6862
6874
|
*
|
|
6863
6875
|
* @param {Vectros.IngestDocumentRequest} request
|
|
6864
6876
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6878,10 +6890,11 @@ var DocumentsClient = class {
|
|
|
6878
6890
|
return HttpResponsePromise.fromPromise(this.__ingestDocument(request, requestOptions));
|
|
6879
6891
|
}
|
|
6880
6892
|
async __ingestDocument(request, requestOptions) {
|
|
6881
|
-
const { upsert, allowClear, body: _body } = request;
|
|
6893
|
+
const { upsert, allowClear, confirmUntyped, body: _body } = request;
|
|
6882
6894
|
const _queryParams = {
|
|
6883
6895
|
upsert,
|
|
6884
|
-
allowClear
|
|
6896
|
+
allowClear,
|
|
6897
|
+
confirmUntyped
|
|
6885
6898
|
};
|
|
6886
6899
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
6887
6900
|
const _headers = mergeHeaders(
|
|
@@ -7116,7 +7129,7 @@ var DocumentsClient = class {
|
|
|
7116
7129
|
/**
|
|
7117
7130
|
* Partially updates a document using an RFC 7386 JSON Merge Patch. The `payload` object is deep-merged: keys you send overwrite existing values (recursing into nested objects), a key set to `null` is deleted, and keys you omit are preserved — unlike PUT, which replaces the whole payload. Top-level fields (`title`, `folderId`, `schemaId`, ownership) are set when present and left unchanged when omitted; sending a top-level field as `null` is rejected. Supplying `text` re-ingests the document body (same as PUT). `indexMode`, `externalId`, and `storeText` (text retention is fixed at ingest) are immutable and rejected if present. The merged result is validated against the bound schema. Pass `expectedVersion` for optimistic concurrency (409 on conflict). Requires the `documents:u` scope.
|
|
7118
7131
|
*
|
|
7119
|
-
* @param {Vectros.
|
|
7132
|
+
* @param {Vectros.DocumentPatchRequest} request
|
|
7120
7133
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
7121
7134
|
*
|
|
7122
7135
|
* @throws {@link Vectros.BadRequestError}
|
|
@@ -7127,17 +7140,14 @@ var DocumentsClient = class {
|
|
|
7127
7140
|
*
|
|
7128
7141
|
* @example
|
|
7129
7142
|
* await client.documents.patchDocument({
|
|
7130
|
-
* id: "id"
|
|
7131
|
-
* body: {
|
|
7132
|
-
* title: "Patient Intake Form \u2014 Jane Doe"
|
|
7133
|
-
* }
|
|
7143
|
+
* id: "id"
|
|
7134
7144
|
* })
|
|
7135
7145
|
*/
|
|
7136
7146
|
patchDocument(request, requestOptions) {
|
|
7137
7147
|
return HttpResponsePromise.fromPromise(this.__patchDocument(request, requestOptions));
|
|
7138
7148
|
}
|
|
7139
7149
|
async __patchDocument(request, requestOptions) {
|
|
7140
|
-
const { id,
|
|
7150
|
+
const { id, ..._body } = request;
|
|
7141
7151
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
7142
7152
|
const _headers = mergeHeaders(
|
|
7143
7153
|
_authRequest.headers,
|
|
@@ -7509,7 +7519,7 @@ var DocumentsClient = class {
|
|
|
7509
7519
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/versions");
|
|
7510
7520
|
}
|
|
7511
7521
|
/**
|
|
7512
|
-
* 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 recovery upload for a document whose earlier file is missing, 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.
|
|
7522
|
+
* 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 recovery upload for a document whose earlier file is missing, 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. Supplying `externalId` WITHOUT `schemaId` is refused (`400`) unless you also pass `confirmUntyped=true` — externalId uniqueness is scoped per schema, so omitting `schemaId` by mistake would otherwise silently create a separate untyped document instead of targeting the typed one you likely meant.
|
|
7513
7523
|
*
|
|
7514
7524
|
* @param {Vectros.FileUploadRequest} request
|
|
7515
7525
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -7528,9 +7538,10 @@ var DocumentsClient = class {
|
|
|
7528
7538
|
return HttpResponsePromise.fromPromise(this.__uploadDocument(request, requestOptions));
|
|
7529
7539
|
}
|
|
7530
7540
|
async __uploadDocument(request, requestOptions) {
|
|
7531
|
-
const { upsert, ..._body } = request;
|
|
7541
|
+
const { upsert, confirmUntyped, ..._body } = request;
|
|
7532
7542
|
const _queryParams = {
|
|
7533
|
-
upsert
|
|
7543
|
+
upsert,
|
|
7544
|
+
confirmUntyped
|
|
7534
7545
|
};
|
|
7535
7546
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
7536
7547
|
const _headers = mergeHeaders(
|
|
@@ -9435,6 +9446,8 @@ var InferenceClient = class {
|
|
|
9435
9446
|
throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
|
|
9436
9447
|
case 403:
|
|
9437
9448
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
9449
|
+
case 413:
|
|
9450
|
+
throw new ContentTooLargeError(_response.error.body, _response.rawResponse);
|
|
9438
9451
|
case 429:
|
|
9439
9452
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
9440
9453
|
default:
|
|
@@ -9574,6 +9587,8 @@ var InferenceClient = class {
|
|
|
9574
9587
|
throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
|
|
9575
9588
|
case 403:
|
|
9576
9589
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
9590
|
+
case 413:
|
|
9591
|
+
throw new ContentTooLargeError(_response.error.body, _response.rawResponse);
|
|
9577
9592
|
case 429:
|
|
9578
9593
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
9579
9594
|
case 500:
|
|
@@ -11871,7 +11886,7 @@ var RenderHintsSchema = z.object({
|
|
|
11871
11886
|
helpText: z.string().optional(),
|
|
11872
11887
|
// Marks this field as the record's headline (display) field — the linked
|
|
11873
11888
|
// primary column in the records list + the title on the detail view. At most
|
|
11874
|
-
// one per schema (the platform
|
|
11889
|
+
// one per schema (the platform stores the hint and does not enforce one; the reference UI uses the first by order). Format passthrough →
|
|
11875
11890
|
// SchemaRequest.renderHints[fieldId].displayField.
|
|
11876
11891
|
displayField: z.boolean().optional()
|
|
11877
11892
|
}).strict();
|
|
@@ -12546,7 +12561,11 @@ var BlueprintIssuerSchema = z.object({
|
|
|
12546
12561
|
* actually scoped. An unverified domain fails the apply with the platform's `400`.
|
|
12547
12562
|
*
|
|
12548
12563
|
* Omit to register a domain-less issuer. Omitting it on a re-apply leaves an existing scope
|
|
12549
|
-
* unchanged
|
|
12564
|
+
* unchanged. On platform 0.45.0 and later a scope is removed only from a registration that
|
|
12565
|
+
* already holds the unrestricted `(issuer, audience)` pair, which one created with a domain
|
|
12566
|
+
* normally does not (`vectros issuers update --clear-restricted-to-domain` is refused for it):
|
|
12567
|
+
* replace it with a new issuer declared without `restrictedToDomain` and verify that, under a
|
|
12568
|
+
* different app context if the old issuer has served users.
|
|
12550
12569
|
*/
|
|
12551
12570
|
restrictedToDomain: z.string().regex(RESTRICTED_DOMAIN_RE, {
|
|
12552
12571
|
message: "restrictedToDomain must be a lowercase DNS name such as 'acme.com' \u2014 letters, digits and dashes in dot-separated labels, at least two labels, no scheme, path, port or uppercase; write an internationalized domain in its punycode (xn--\u2026) form"
|
|
@@ -13526,7 +13545,8 @@ var BlueprintSchema = z.object({
|
|
|
13526
13545
|
* alongside app-context/service-principal creation and issuers, BEFORE
|
|
13527
13546
|
* schemas/seed (an `entityBacked` namespace's entities, and an
|
|
13528
13547
|
* `allowedSurfaces: ['entity']` schema, both need the registration to
|
|
13529
|
-
* already exist). `org`/`client`
|
|
13548
|
+
* already exist). `org`/`client` are registered the same way as any other
|
|
13549
|
+
* namespace: signup does not register them, so declare an entry for each you use.
|
|
13530
13550
|
*/
|
|
13531
13551
|
namespaces: z.array(BlueprintNamespaceSchema).optional(),
|
|
13532
13552
|
/**
|
|
@@ -15660,7 +15680,7 @@ async function devRouteError(res, action) {
|
|
|
15660
15680
|
}
|
|
15661
15681
|
function formatCliError(err, opts) {
|
|
15662
15682
|
const info = extractServerError(err);
|
|
15663
|
-
const actionable = renderActionableError(info);
|
|
15683
|
+
const actionable = renderActionableError(info, opts.notFoundHint);
|
|
15664
15684
|
const block = actionable ?? ` \u2716 ${opts.action}: ${info.message}
|
|
15665
15685
|
`;
|
|
15666
15686
|
const debug = opts.debug ?? Boolean(process.env.VECTROS_MCP_DEBUG);
|
|
@@ -15674,7 +15694,7 @@ function formatCliError(err, opts) {
|
|
|
15674
15694
|
}
|
|
15675
15695
|
return block;
|
|
15676
15696
|
}
|
|
15677
|
-
function renderActionableError(info) {
|
|
15697
|
+
function renderActionableError(info, notFoundHint) {
|
|
15678
15698
|
const msg = info.message;
|
|
15679
15699
|
switch (info.errorCode) {
|
|
15680
15700
|
case "SESSION_REFRESH_REQUIRED":
|
|
@@ -15712,7 +15732,7 @@ function renderActionableError(info) {
|
|
|
15712
15732
|
}
|
|
15713
15733
|
if (info.statusCode === 404) {
|
|
15714
15734
|
return ` \u2716 ${msg}
|
|
15715
|
-
Check the id \u2014 'vectros context list' enumerates the contexts you can reach.
|
|
15735
|
+
Check the id \u2014 ${notFoundHint ?? "'vectros context list' enumerates the contexts you can reach."}
|
|
15716
15736
|
`;
|
|
15717
15737
|
}
|
|
15718
15738
|
if (/migration-locked|cannot be (changed|modified)|immutable/i.test(msg) && /schema|field|lookup|type/i.test(msg)) {
|
|
@@ -16490,6 +16510,25 @@ async function clearSession(baseDir) {
|
|
|
16490
16510
|
return false;
|
|
16491
16511
|
}
|
|
16492
16512
|
}
|
|
16513
|
+
var TokenEndpointError = class extends Error {
|
|
16514
|
+
status;
|
|
16515
|
+
/** The OAuth `error` field of a JSON error body (e.g. `invalid_grant`), when there is one. */
|
|
16516
|
+
oauthError;
|
|
16517
|
+
constructor(status, text) {
|
|
16518
|
+
super(`token endpoint HTTP ${status}${text ? ` \u2014 ${stripControlCharacters(text, 500)}` : ""}`);
|
|
16519
|
+
this.name = "TokenEndpointError";
|
|
16520
|
+
this.status = status;
|
|
16521
|
+
this.oauthError = oauthErrorOf(text);
|
|
16522
|
+
}
|
|
16523
|
+
};
|
|
16524
|
+
function oauthErrorOf(text) {
|
|
16525
|
+
try {
|
|
16526
|
+
const parsed = JSON.parse(text);
|
|
16527
|
+
return typeof parsed.error === "string" ? parsed.error : void 0;
|
|
16528
|
+
} catch {
|
|
16529
|
+
return void 0;
|
|
16530
|
+
}
|
|
16531
|
+
}
|
|
16493
16532
|
async function postToken(config, body, fetchImpl) {
|
|
16494
16533
|
const res = await fetchImpl(tokenEndpoint(config), {
|
|
16495
16534
|
method: "POST",
|
|
@@ -16497,9 +16536,7 @@ async function postToken(config, body, fetchImpl) {
|
|
|
16497
16536
|
body: body.toString()
|
|
16498
16537
|
});
|
|
16499
16538
|
const text = await res.text().catch(() => "");
|
|
16500
|
-
if (!res.ok)
|
|
16501
|
-
throw new Error(`token endpoint HTTP ${res.status}${text ? ` \u2014 ${stripControlCharacters(text, 500)}` : ""}`);
|
|
16502
|
-
}
|
|
16539
|
+
if (!res.ok) throw new TokenEndpointError(res.status, text);
|
|
16503
16540
|
try {
|
|
16504
16541
|
return JSON.parse(text);
|
|
16505
16542
|
} catch {
|
|
@@ -16515,31 +16552,54 @@ async function refreshTokens(config, refreshToken, fetchImpl = fetch) {
|
|
|
16515
16552
|
|
|
16516
16553
|
// src/auth-context.ts
|
|
16517
16554
|
var COGNITO_TOKEN_ENV = "VECTROS_BOOTSTRAP_TOKEN";
|
|
16555
|
+
function loginCommandFor(env) {
|
|
16556
|
+
return env === "staging" ? "vectros login --env staging" : "vectros login";
|
|
16557
|
+
}
|
|
16518
16558
|
var MissingCredentialError = class extends Error {
|
|
16519
|
-
constructor() {
|
|
16559
|
+
constructor(env) {
|
|
16520
16560
|
super(
|
|
16521
|
-
`No credential found.
|
|
16561
|
+
`No credential found. Run \`${loginCommandFor(env)}\` to sign in, or set ${COGNITO_TOKEN_ENV} or pass --token (generate a token in the developer portal).`
|
|
16522
16562
|
);
|
|
16523
16563
|
this.name = "MissingCredentialError";
|
|
16524
16564
|
}
|
|
16525
16565
|
};
|
|
16566
|
+
var SessionExpiredError = class extends Error {
|
|
16567
|
+
constructor(env) {
|
|
16568
|
+
super(
|
|
16569
|
+
`Your Vectros login session has expired or was revoked. Run \`${loginCommandFor(env)}\` to sign in again.`
|
|
16570
|
+
);
|
|
16571
|
+
this.name = "SessionExpiredError";
|
|
16572
|
+
}
|
|
16573
|
+
};
|
|
16526
16574
|
function resolveCognitoTokenSync(explicit) {
|
|
16527
16575
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
16528
16576
|
const fromEnv = process.env[COGNITO_TOKEN_ENV];
|
|
16529
16577
|
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
16530
16578
|
return void 0;
|
|
16531
16579
|
}
|
|
16532
|
-
async function resolveCognitoToken(env, explicit) {
|
|
16580
|
+
async function resolveCognitoToken(env, explicit, deps = { readSession, refreshTokens, saveSession }) {
|
|
16533
16581
|
const sync = resolveCognitoTokenSync(explicit);
|
|
16534
16582
|
if (sync) return sync;
|
|
16535
|
-
const session = await readSession();
|
|
16583
|
+
const session = await deps.readSession();
|
|
16536
16584
|
if (session && session.env === env) {
|
|
16537
16585
|
const config = resolveCognitoConfig(env);
|
|
16538
|
-
|
|
16586
|
+
let tokens;
|
|
16587
|
+
try {
|
|
16588
|
+
tokens = await deps.refreshTokens(config, session.refreshToken);
|
|
16589
|
+
} catch (err) {
|
|
16590
|
+
if (err instanceof TokenEndpointError && err.oauthError === "invalid_grant") throw new SessionExpiredError(env);
|
|
16591
|
+
throw err;
|
|
16592
|
+
}
|
|
16593
|
+
if (tokens.refresh_token && tokens.refresh_token !== session.refreshToken) {
|
|
16594
|
+
const stored = await deps.readSession().catch(() => void 0);
|
|
16595
|
+
if (stored?.refreshToken === session.refreshToken) {
|
|
16596
|
+
await deps.saveSession({ ...session, refreshToken: tokens.refresh_token }).catch(() => void 0);
|
|
16597
|
+
}
|
|
16598
|
+
}
|
|
16539
16599
|
const bearer = tokens.id_token ?? tokens.access_token;
|
|
16540
16600
|
if (bearer) return bearer;
|
|
16541
16601
|
}
|
|
16542
|
-
throw new MissingCredentialError();
|
|
16602
|
+
throw new MissingCredentialError(env);
|
|
16543
16603
|
}
|
|
16544
16604
|
async function resolveAuthContext(opts) {
|
|
16545
16605
|
const baseUrl = envToBaseUrl(opts.env, opts.baseUrl ?? process.env.VECTROS_API_BASE_URL);
|
|
@@ -17051,6 +17111,7 @@ function keyNameFor(pack, machineId) {
|
|
|
17051
17111
|
}
|
|
17052
17112
|
|
|
17053
17113
|
// src/identity-resolver.ts
|
|
17114
|
+
var IDENTITIES_TO_ENSURE_HEADING = " Identities to ensure (idempotent by external id; a non-user entity lives with its namespace):";
|
|
17054
17115
|
function userPayload(decl) {
|
|
17055
17116
|
const meta = decl.metadata;
|
|
17056
17117
|
if (decl.displayName === void 0) return meta;
|
|
@@ -17191,7 +17252,7 @@ async function withAuth(opts, deps, body) {
|
|
|
17191
17252
|
log
|
|
17192
17253
|
});
|
|
17193
17254
|
} catch (err) {
|
|
17194
|
-
if (err instanceof MissingCredentialError || err instanceof InvalidBaseUrlError || err instanceof InvalidCognitoRegionError) {
|
|
17255
|
+
if (err instanceof MissingCredentialError || err instanceof SessionExpiredError || err instanceof InvalidBaseUrlError || err instanceof InvalidCognitoRegionError) {
|
|
17195
17256
|
process.stderr.write(` \u2716 ${err.message}
|
|
17196
17257
|
`);
|
|
17197
17258
|
return 2;
|
|
@@ -17786,15 +17847,6 @@ async function provisionContext(client, input) {
|
|
|
17786
17847
|
const { blueprint, allowTenantWideNamespaces = false, readExistingKey, confirmExistingPrincipal, log } = input;
|
|
17787
17848
|
assertBlueprintGates(blueprint);
|
|
17788
17849
|
assertTenantWideNamespacesAllowed(blueprint, allowTenantWideNamespaces);
|
|
17789
|
-
const ctxResp = await client.auth.createAppContext({
|
|
17790
|
-
body: {
|
|
17791
|
-
contextId: blueprint.contextId,
|
|
17792
|
-
name: contextNameOf(blueprint),
|
|
17793
|
-
...blueprint.identityProjectionClaims === void 0 ? {} : { identityProjectionClaims: blueprint.identityProjectionClaims }
|
|
17794
|
-
}
|
|
17795
|
-
});
|
|
17796
|
-
const contextAction = actionOf(ctxResp.created);
|
|
17797
|
-
log.debug({ component: "loader", contextId: blueprint.contextId, contextAction }, "app-context ensured");
|
|
17798
17850
|
const user = await client.identity.createUser({
|
|
17799
17851
|
body: {
|
|
17800
17852
|
externalId: blueprint.servicePrincipal.externalId,
|
|
@@ -17835,6 +17887,15 @@ async function provisionContext(client, input) {
|
|
|
17835
17887
|
"existing-principal collision confirmed (or unwired) \u2014 proceeding with any further write to this principal"
|
|
17836
17888
|
);
|
|
17837
17889
|
}
|
|
17890
|
+
const ctxResp = await client.auth.createAppContext({
|
|
17891
|
+
body: {
|
|
17892
|
+
contextId: blueprint.contextId,
|
|
17893
|
+
name: contextNameOf(blueprint),
|
|
17894
|
+
...blueprint.identityProjectionClaims === void 0 ? {} : { identityProjectionClaims: blueprint.identityProjectionClaims }
|
|
17895
|
+
}
|
|
17896
|
+
});
|
|
17897
|
+
const contextAction = actionOf(ctxResp.created);
|
|
17898
|
+
log.debug({ component: "loader", contextId: blueprint.contextId, contextAction }, "app-context ensured");
|
|
17838
17899
|
const needsContextPinnedBootstrap = (blueprint.namespaces ?? []).length > 0;
|
|
17839
17900
|
const ctxBootstrapClient = needsContextPinnedBootstrap ? input.remintBootstrapForContext ? await input.remintBootstrapForContext(blueprint.contextId) : client : void 0;
|
|
17840
17901
|
if (ctxBootstrapClient && input.remintBootstrapForContext) {
|
|
@@ -18343,14 +18404,14 @@ function renderPlanDataScope(ds) {
|
|
|
18343
18404
|
function buildPlan(blueprint, opts = {}) {
|
|
18344
18405
|
const steps = [];
|
|
18345
18406
|
const projection = blueprint.identityProjectionClaims?.length ? `, identity projection: [${blueprint.identityProjectionClaims.join(", ")}]` : blueprint.identityProjectionClaims ? ", identity projection: disabled (empty list)" : "";
|
|
18346
|
-
steps.push({
|
|
18347
|
-
kind: "context",
|
|
18348
|
-
summary: `context '${blueprint.contextId}' (${contextNameOf(blueprint)}${projection})`
|
|
18349
|
-
});
|
|
18350
18407
|
steps.push({
|
|
18351
18408
|
kind: "principal",
|
|
18352
18409
|
summary: `principal '${blueprint.servicePrincipal.externalId}' (SERVICE)`
|
|
18353
18410
|
});
|
|
18411
|
+
steps.push({
|
|
18412
|
+
kind: "context",
|
|
18413
|
+
summary: `context '${blueprint.contextId}' (${contextNameOf(blueprint)}${projection})`
|
|
18414
|
+
});
|
|
18354
18415
|
for (const ns of blueprint.namespaces ?? []) {
|
|
18355
18416
|
const ownerNote = ns.tenantWide ? "TENANT-WIDE \u2014 visible to every context" : `owned by '${blueprint.contextId}'`;
|
|
18356
18417
|
const entityNote = ns.entityBacked ? ", entity-backed" : "";
|
|
@@ -18905,9 +18966,8 @@ var OsKeychainSecretStore = class {
|
|
|
18905
18966
|
* enumeration, and `deleteCredential` on a nonexistent ref returns the same
|
|
18906
18967
|
* meaningless `false` as Linux. So the `.some(c => c.account === ref)` comparison
|
|
18907
18968
|
* is sound on both measured platforms. Still unmeasured: a CI lane that runs this
|
|
18908
|
-
* against the real Windows store on every
|
|
18909
|
-
* measurement, not a regression gate.
|
|
18910
|
-
* public npm, so no tracker number here.)
|
|
18969
|
+
* against the real Windows store on every change — the dev-machine probe is a point
|
|
18970
|
+
* measurement, not a regression gate.
|
|
18911
18971
|
*
|
|
18912
18972
|
* ⚠️ THIS METHOD'S FAIL-CLOSED GUARANTEE IS AN UPSTREAM BEHAVIOURAL CONTRACT, NOT
|
|
18913
18973
|
* SOMETHING THIS CODE ENFORCES. `@napi-rs/keyring` is pinned to an EXACT version
|
|
@@ -19063,7 +19123,7 @@ var CryptoFileSecretStore = class {
|
|
|
19063
19123
|
async setSecret(ref, secret) {
|
|
19064
19124
|
const key = await this.encryptionKey();
|
|
19065
19125
|
const iv = randomBytes2(IV_LEN);
|
|
19066
|
-
const cipher = createCipheriv(ALGO, key, iv);
|
|
19126
|
+
const cipher = createCipheriv(ALGO, key, iv, { authTagLength: TAG_LEN });
|
|
19067
19127
|
const enc = Buffer.concat([cipher.update(secret, "utf8"), cipher.final()]);
|
|
19068
19128
|
const tag = cipher.getAuthTag();
|
|
19069
19129
|
const blob = Buffer.concat([iv, tag, enc]);
|
|
@@ -19086,7 +19146,7 @@ var CryptoFileSecretStore = class {
|
|
|
19086
19146
|
const iv = blob.subarray(0, IV_LEN);
|
|
19087
19147
|
const tag = blob.subarray(IV_LEN, IV_LEN + TAG_LEN);
|
|
19088
19148
|
const enc = blob.subarray(IV_LEN + TAG_LEN);
|
|
19089
|
-
const decipher = createDecipheriv(ALGO, key, iv);
|
|
19149
|
+
const decipher = createDecipheriv(ALGO, key, iv, { authTagLength: TAG_LEN });
|
|
19090
19150
|
decipher.setAuthTag(tag);
|
|
19091
19151
|
return Buffer.concat([decipher.update(enc), decipher.final()]).toString("utf8");
|
|
19092
19152
|
} catch {
|
|
@@ -19270,8 +19330,8 @@ async function withIndexLock(dir, fn, timings = LOCK_OPTS) {
|
|
|
19270
19330
|
// sections are ~1ms, so in practice the holder is gone before the first tick
|
|
19271
19331
|
// and nothing is ever detected. (The probe that observed the no-op ran at
|
|
19272
19332
|
// stale:2000 with the section artificially held open — a configuration we do
|
|
19273
|
-
// not ship
|
|
19274
|
-
//
|
|
19333
|
+
// not ship, and a measurement taken in a configuration you don't ship is not
|
|
19334
|
+
// evidence about the configuration you do.) The
|
|
19275
19335
|
// honest shape: our release runs unconditionally, and after a stale-break
|
|
19276
19336
|
// collision it CAN remove a successor's fresh lock — one more route to two
|
|
19277
19337
|
// writers at once, on top of the rmdir race below. The backstop for both is
|
|
@@ -19286,7 +19346,7 @@ async function withIndexLock(dir, fn, timings = LOCK_OPTS) {
|
|
|
19286
19346
|
// judged: two waiters can both condemn one crash remnant, and the second's
|
|
19287
19347
|
// already-decided rmdir destroys the first's FRESH lock. Nothing freezes.
|
|
19288
19348
|
//
|
|
19289
|
-
// MEASURED against this exact config (
|
|
19349
|
+
// MEASURED against this exact config (probe results below):
|
|
19290
19350
|
// real OS processes, no injection : 0/25 and 1/25 (two independent runs)
|
|
19291
19351
|
// in-process, 5 waiters : ~3% per round
|
|
19292
19352
|
// rival's rmdir lagged one turn : 19/25
|
|
@@ -21119,7 +21179,7 @@ async function resolveBridgeCognitoToken(args, prompt, isTty, resolveCognito = r
|
|
|
21119
21179
|
}
|
|
21120
21180
|
if (!isTty) {
|
|
21121
21181
|
throw new Error(
|
|
21122
|
-
|
|
21182
|
+
`No bridge token: set VECTROS_BOOTSTRAP_TOKEN, pass --token, or run \`${loginCommandFor(args.env)}\` first (required in non-interactive mode).`
|
|
21123
21183
|
);
|
|
21124
21184
|
}
|
|
21125
21185
|
process.stdout.write(
|
|
@@ -21228,7 +21288,9 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
21228
21288
|
`);
|
|
21229
21289
|
const declaredIdentities = Object.entries(blueprint.identities ?? {});
|
|
21230
21290
|
if (declaredIdentities.length > 0) {
|
|
21231
|
-
process.stdout.write(
|
|
21291
|
+
process.stdout.write(`
|
|
21292
|
+
${IDENTITIES_TO_ENSURE_HEADING}
|
|
21293
|
+
`);
|
|
21232
21294
|
for (const [name, decl] of declaredIdentities) {
|
|
21233
21295
|
process.stdout.write(` ~ ${name} (${decl.kind}) \u2014 external id '${decl.externalId}'
|
|
21234
21296
|
`);
|
|
@@ -21459,6 +21521,12 @@ ${codeAddHint}`);
|
|
|
21459
21521
|
}
|
|
21460
21522
|
return finishApply();
|
|
21461
21523
|
} catch (err) {
|
|
21524
|
+
if (err instanceof SessionExpiredError) {
|
|
21525
|
+
process.stderr.write(`
|
|
21526
|
+
\u2716 ${err.message}
|
|
21527
|
+
`);
|
|
21528
|
+
return 2;
|
|
21529
|
+
}
|
|
21462
21530
|
if (err instanceof LostKeyError) {
|
|
21463
21531
|
process.stderr.write(`
|
|
21464
21532
|
\u2716 ${err.message}
|
|
@@ -21830,7 +21898,9 @@ ${renderPlan(blueprint, buildPlan(blueprint, { noSeed, noKey: true }), { prune:
|
|
|
21830
21898
|
process.stdout.write(applySeedNotice(blueprint, args.seed));
|
|
21831
21899
|
const declaredIdentities = Object.entries(blueprint.identities ?? {});
|
|
21832
21900
|
if (declaredIdentities.length > 0) {
|
|
21833
|
-
process.stdout.write(
|
|
21901
|
+
process.stdout.write(`
|
|
21902
|
+
${IDENTITIES_TO_ENSURE_HEADING}
|
|
21903
|
+
`);
|
|
21834
21904
|
for (const [name, decl] of declaredIdentities) {
|
|
21835
21905
|
process.stdout.write(` ~ ${name} (${decl.kind}) \u2014 external id '${decl.externalId}'
|
|
21836
21906
|
`);
|
|
@@ -21963,7 +22033,7 @@ function reportApplyError(err) {
|
|
|
21963
22033
|
If this is expected (a re-apply of a blueprint you manage), re-run with --confirm-existing-principal
|
|
21964
22034
|
(with --yes in a pipeline, where it is standing consent), or confirm at the prompt.
|
|
21965
22035
|
If you don't recognize this identity, investigate the blueprint's servicePrincipal.externalId first.
|
|
21966
|
-
Only the
|
|
22036
|
+
Only the principal lookup had run; no context, grant or anything else was created or changed.
|
|
21967
22037
|
`
|
|
21968
22038
|
);
|
|
21969
22039
|
return 3;
|
|
@@ -22316,10 +22386,14 @@ schemas:
|
|
|
22316
22386
|
accessProfile:
|
|
22317
22387
|
# SCOPE GATE: data-plane only (records/schemas/search/documents/folders/inference/entities),
|
|
22318
22388
|
# PLUS profiles:r/u/d (bounded by the platform to redistributing access you already have,
|
|
22319
|
-
# never granting more)
|
|
22320
|
-
# platform resolves "self" from the caller, not from anything you declare here)
|
|
22321
|
-
#
|
|
22322
|
-
#
|
|
22389
|
+
# never granting more), profiles:c:self (create a profile for yourself only \u2014 the
|
|
22390
|
+
# platform resolves "self" from the caller, not from anything you declare here) and
|
|
22391
|
+
# scripts:x:<script-name> (run one named script that this blueprint ships; there is no bare
|
|
22392
|
+
# or wildcard form).
|
|
22393
|
+
# A bare profiles:c is accepted ONLY when this same clause also declares the
|
|
22394
|
+
# member-lifecycle capability (see "capabilities" below); without it, and always for
|
|
22395
|
+
# profiles:c:usr_<id> naming another principal, it is REJECTED \u2014 no override. Every other
|
|
22396
|
+
# control-plane resource (keys/users/admin/namespaces/triggers/...) and "*" is REJECTED the same way.
|
|
22323
22397
|
# Ops: c|r|u|d, combinable (records:cru). Prefer the narrowest set (least privilege).
|
|
22324
22398
|
allowedActions: [records:r, records:c, records:u, search:r, schemas:r]
|
|
22325
22399
|
# dataScope binds the key to specific owners. Include null to ALSO see
|
|
@@ -22335,8 +22409,10 @@ accessProfile:
|
|
|
22335
22409
|
# above) unless every owner genuinely sharing every record is what you want.
|
|
22336
22410
|
dataScope: {}
|
|
22337
22411
|
# capabilities is a SEPARATE gated dimension from allowedActions \u2014 named platform
|
|
22338
|
-
# effects a resource:ops verb can't express. Only 'member-lifecycle'
|
|
22339
|
-
# today; every other name is
|
|
22412
|
+
# effects a resource:ops verb can't express. Only 'member-lifecycle' and
|
|
22413
|
+
# 'delegate-principal-stamp' are admitted today; every other name is REJECTED, same
|
|
22414
|
+
# posture as the scope gate above. 'member-lifecycle' is also what lets this clause
|
|
22415
|
+
# carry a bare profiles:c.
|
|
22340
22416
|
# capabilities: [member-lifecycle]
|
|
22341
22417
|
|
|
22342
22418
|
servicePrincipal:
|
|
@@ -22423,8 +22499,10 @@ Usage:
|
|
|
22423
22499
|
|
|
22424
22500
|
<file|name> is a path to your own blueprint (YAML .yaml/.yml or JSON .json) OR
|
|
22425
22501
|
the name of a bundled blueprint. The same scope gate that protects bundled
|
|
22426
|
-
blueprints applies to file blueprints
|
|
22427
|
-
|
|
22502
|
+
blueprints applies to file blueprints: beyond data-plane access, a forked or
|
|
22503
|
+
hand-written blueprint can request only a short allowlist of narrow exceptions
|
|
22504
|
+
(profiles:r/u/d, profiles:c:self, a bare profiles:c beside member-lifecycle,
|
|
22505
|
+
scripts:x:<script-name>); every other control-plane scope is rejected.
|
|
22428
22506
|
|
|
22429
22507
|
A blueprint may declare a top-level 'inputs:' block and reference its values
|
|
22430
22508
|
with \${{ inputs.<name> }} (and the built-ins \${{ vectros.context }} /
|
|
@@ -22536,7 +22614,9 @@ ${renderPlan(blueprint, steps)}
|
|
|
22536
22614
|
`);
|
|
22537
22615
|
const declaredIdentities = Object.entries(blueprint.identities ?? {});
|
|
22538
22616
|
if (declaredIdentities.length > 0) {
|
|
22539
|
-
process.stdout.write(
|
|
22617
|
+
process.stdout.write(`
|
|
22618
|
+
${IDENTITIES_TO_ENSURE_HEADING}
|
|
22619
|
+
`);
|
|
22540
22620
|
for (const [name, decl] of declaredIdentities) {
|
|
22541
22621
|
process.stdout.write(` ~ ${name} (${decl.kind}) \u2014 external id '${decl.externalId}'
|
|
22542
22622
|
`);
|
|
@@ -22670,6 +22750,89 @@ ${BLUEPRINT_HELP}
|
|
|
22670
22750
|
// src/program.ts
|
|
22671
22751
|
import { Command, Option } from "commander";
|
|
22672
22752
|
|
|
22753
|
+
// src/dev-api.ts
|
|
22754
|
+
var DeveloperApiError = class extends Error {
|
|
22755
|
+
statusCode;
|
|
22756
|
+
/** The platform's error body, when it answered with one (its message, request id and error code). */
|
|
22757
|
+
body;
|
|
22758
|
+
constructor(message, statusCode, body) {
|
|
22759
|
+
super(message);
|
|
22760
|
+
this.name = "DeveloperApiError";
|
|
22761
|
+
this.statusCode = statusCode;
|
|
22762
|
+
this.body = body;
|
|
22763
|
+
}
|
|
22764
|
+
};
|
|
22765
|
+
async function fetchDeveloperUsersPage(opts) {
|
|
22766
|
+
const { cognitoToken, apiBaseUrl, tenant, startFrom, limit, fetchImpl = fetch } = opts;
|
|
22767
|
+
const base = apiBaseUrl.replace(/\/$/, "");
|
|
22768
|
+
let url = `${base}/developer/users?tenant=${encodeURIComponent(tenant)}`;
|
|
22769
|
+
if (startFrom) url += `&startFrom=${encodeURIComponent(startFrom)}`;
|
|
22770
|
+
if (limit !== void 0) url += `&limit=${encodeURIComponent(String(limit))}`;
|
|
22771
|
+
let res;
|
|
22772
|
+
try {
|
|
22773
|
+
res = await fetchImpl(url, {
|
|
22774
|
+
method: "GET",
|
|
22775
|
+
headers: { Authorization: `Bearer ${cognitoToken}` }
|
|
22776
|
+
});
|
|
22777
|
+
} catch (err) {
|
|
22778
|
+
throw new DeveloperApiError(
|
|
22779
|
+
`Request to /developer/users failed: ${err instanceof Error ? err.message : String(err)}`
|
|
22780
|
+
);
|
|
22781
|
+
}
|
|
22782
|
+
if (!res.ok) {
|
|
22783
|
+
const body = await res.text().catch(() => "");
|
|
22784
|
+
const hint = res.status === 403 ? " (this command requires an account owner login \u2014 sign in as the owner and retry)" : "";
|
|
22785
|
+
throw new DeveloperApiError(
|
|
22786
|
+
`GET /developer/users failed: HTTP ${res.status}${hint}${body ? ` \u2014 ${body}` : ""}`,
|
|
22787
|
+
res.status
|
|
22788
|
+
);
|
|
22789
|
+
}
|
|
22790
|
+
let parsed;
|
|
22791
|
+
try {
|
|
22792
|
+
parsed = await res.json();
|
|
22793
|
+
} catch {
|
|
22794
|
+
throw new DeveloperApiError("GET /developer/users returned a non-JSON body");
|
|
22795
|
+
}
|
|
22796
|
+
const data = Array.isArray(parsed.data) ? parsed.data : [];
|
|
22797
|
+
const nextCursor = typeof parsed.nextCursor === "string" ? parsed.nextCursor : null;
|
|
22798
|
+
return { data, nextCursor };
|
|
22799
|
+
}
|
|
22800
|
+
async function fetchDeveloperMe(opts) {
|
|
22801
|
+
const { cognitoToken, apiBaseUrl, tenant, fetchImpl = fetch } = opts;
|
|
22802
|
+
const url = `${apiBaseUrl.replace(/\/$/, "")}/developer/me?tenant=${encodeURIComponent(tenant)}`;
|
|
22803
|
+
let res;
|
|
22804
|
+
try {
|
|
22805
|
+
res = await fetchImpl(url, { method: "GET", headers: { Authorization: `Bearer ${cognitoToken}` } });
|
|
22806
|
+
} catch (err) {
|
|
22807
|
+
throw new DeveloperApiError(
|
|
22808
|
+
`Request to /developer/me failed: ${err instanceof Error ? err.message : String(err)}`
|
|
22809
|
+
);
|
|
22810
|
+
}
|
|
22811
|
+
if (res.status === 404) return null;
|
|
22812
|
+
if (!res.ok) {
|
|
22813
|
+
const failure = await devRouteError(res, "GET /developer/me");
|
|
22814
|
+
throw new DeveloperApiError(
|
|
22815
|
+
`GET /developer/me failed: HTTP ${res.status}`,
|
|
22816
|
+
res.status,
|
|
22817
|
+
failure.body
|
|
22818
|
+
);
|
|
22819
|
+
}
|
|
22820
|
+
let parsed;
|
|
22821
|
+
try {
|
|
22822
|
+
parsed = await res.json();
|
|
22823
|
+
} catch {
|
|
22824
|
+
throw new DeveloperApiError("GET /developer/me returned a non-JSON body");
|
|
22825
|
+
}
|
|
22826
|
+
if (typeof parsed.principalId !== "string" || typeof parsed.userId !== "string") {
|
|
22827
|
+
throw new DeveloperApiError("GET /developer/me returned no principal");
|
|
22828
|
+
}
|
|
22829
|
+
return {
|
|
22830
|
+
principalId: parsed.principalId,
|
|
22831
|
+
userId: parsed.userId,
|
|
22832
|
+
tenantId: typeof parsed.tenantId === "string" ? parsed.tenantId : void 0
|
|
22833
|
+
};
|
|
22834
|
+
}
|
|
22835
|
+
|
|
22673
22836
|
// src/self-principal.ts
|
|
22674
22837
|
var PRINCIPAL_PREFIX_USER = "usr_";
|
|
22675
22838
|
function emailFromJwt(token) {
|
|
@@ -22698,6 +22861,37 @@ async function resolveSelfPrincipal(client, cognitoToken) {
|
|
|
22698
22861
|
if (!match?.id) return null;
|
|
22699
22862
|
return { principalId: `${PRINCIPAL_PREFIX_USER}${match.id}`, userId: match.id, email };
|
|
22700
22863
|
}
|
|
22864
|
+
async function resolveSelfPrincipalForTenant(auth, fetchImpl) {
|
|
22865
|
+
const tenant = auth.tenant ?? "live";
|
|
22866
|
+
let me;
|
|
22867
|
+
try {
|
|
22868
|
+
me = await fetchDeveloperMe({
|
|
22869
|
+
cognitoToken: auth.cognitoToken,
|
|
22870
|
+
apiBaseUrl: auth.baseUrl,
|
|
22871
|
+
tenant,
|
|
22872
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
22873
|
+
});
|
|
22874
|
+
} catch (err) {
|
|
22875
|
+
if (err instanceof DeveloperApiError && err.statusCode !== void 0) {
|
|
22876
|
+
if (err.body?.errorCode === "SESSION_REFRESH_REQUIRED") throw new SessionExpiredError(auth.env ?? "production");
|
|
22877
|
+
throw err;
|
|
22878
|
+
}
|
|
22879
|
+
if (tenant === "test") throw err;
|
|
22880
|
+
return resolveSelfPrincipal(auth.client, auth.cognitoToken);
|
|
22881
|
+
}
|
|
22882
|
+
if (me === null) return null;
|
|
22883
|
+
const forThisTenant = Boolean(auth.tenantId && me.tenantId && me.tenantId === auth.tenantId);
|
|
22884
|
+
if (forThisTenant && me.principalId.startsWith(PRINCIPAL_PREFIX_USER)) {
|
|
22885
|
+
return { principalId: me.principalId, userId: me.userId, email: emailFromJwt(auth.cognitoToken) ?? "" };
|
|
22886
|
+
}
|
|
22887
|
+
return tenant === "live" ? resolveSelfPrincipal(auth.client, auth.cognitoToken) : null;
|
|
22888
|
+
}
|
|
22889
|
+
function selfPrincipalUnresolvedMessage(tenant, flag) {
|
|
22890
|
+
if (tenant === "test") {
|
|
22891
|
+
return `Could not resolve your own user id in the test tenant: the platform did not report one for this login. Find it with \`vectros identity list-all --tenant test\` (an owner login) and pass ${flag} usr_<id> explicitly.`;
|
|
22892
|
+
}
|
|
22893
|
+
return `Could not resolve your own user id from your login. Pass ${flag} usr_<id> explicitly (find the id with \`vectros identity list --type user\`).`;
|
|
22894
|
+
}
|
|
22701
22895
|
|
|
22702
22896
|
// src/commands/whoami.ts
|
|
22703
22897
|
async function runWhoami(options, deps = {}) {
|
|
@@ -22705,7 +22899,10 @@ async function runWhoami(options, deps = {}) {
|
|
|
22705
22899
|
const { identity, baseUrl } = auth;
|
|
22706
22900
|
let self2 = null;
|
|
22707
22901
|
try {
|
|
22708
|
-
self2 = await
|
|
22902
|
+
self2 = await resolveSelfPrincipalForTenant(
|
|
22903
|
+
{ ...auth, client: auth.client },
|
|
22904
|
+
deps.fetchImpl
|
|
22905
|
+
);
|
|
22709
22906
|
} catch {
|
|
22710
22907
|
self2 = null;
|
|
22711
22908
|
}
|
|
@@ -23111,51 +23308,6 @@ ${list}
|
|
|
23111
23308
|
});
|
|
23112
23309
|
}
|
|
23113
23310
|
|
|
23114
|
-
// src/dev-api.ts
|
|
23115
|
-
var DeveloperApiError = class extends Error {
|
|
23116
|
-
statusCode;
|
|
23117
|
-
constructor(message, statusCode) {
|
|
23118
|
-
super(message);
|
|
23119
|
-
this.name = "DeveloperApiError";
|
|
23120
|
-
this.statusCode = statusCode;
|
|
23121
|
-
}
|
|
23122
|
-
};
|
|
23123
|
-
async function fetchDeveloperUsersPage(opts) {
|
|
23124
|
-
const { cognitoToken, apiBaseUrl, tenant, startFrom, limit, fetchImpl = fetch } = opts;
|
|
23125
|
-
const base = apiBaseUrl.replace(/\/$/, "");
|
|
23126
|
-
let url = `${base}/developer/users?tenant=${encodeURIComponent(tenant)}`;
|
|
23127
|
-
if (startFrom) url += `&startFrom=${encodeURIComponent(startFrom)}`;
|
|
23128
|
-
if (limit !== void 0) url += `&limit=${encodeURIComponent(String(limit))}`;
|
|
23129
|
-
let res;
|
|
23130
|
-
try {
|
|
23131
|
-
res = await fetchImpl(url, {
|
|
23132
|
-
method: "GET",
|
|
23133
|
-
headers: { Authorization: `Bearer ${cognitoToken}` }
|
|
23134
|
-
});
|
|
23135
|
-
} catch (err) {
|
|
23136
|
-
throw new DeveloperApiError(
|
|
23137
|
-
`Request to /developer/users failed: ${err instanceof Error ? err.message : String(err)}`
|
|
23138
|
-
);
|
|
23139
|
-
}
|
|
23140
|
-
if (!res.ok) {
|
|
23141
|
-
const body = await res.text().catch(() => "");
|
|
23142
|
-
const hint = res.status === 403 ? " (this command requires an account owner login \u2014 sign in as the owner and retry)" : "";
|
|
23143
|
-
throw new DeveloperApiError(
|
|
23144
|
-
`GET /developer/users failed: HTTP ${res.status}${hint}${body ? ` \u2014 ${body}` : ""}`,
|
|
23145
|
-
res.status
|
|
23146
|
-
);
|
|
23147
|
-
}
|
|
23148
|
-
let parsed;
|
|
23149
|
-
try {
|
|
23150
|
-
parsed = await res.json();
|
|
23151
|
-
} catch {
|
|
23152
|
-
throw new DeveloperApiError("GET /developer/users returned a non-JSON body");
|
|
23153
|
-
}
|
|
23154
|
-
const data = Array.isArray(parsed.data) ? parsed.data : [];
|
|
23155
|
-
const nextCursor = typeof parsed.nextCursor === "string" ? parsed.nextCursor : null;
|
|
23156
|
-
return { data, nextCursor };
|
|
23157
|
-
}
|
|
23158
|
-
|
|
23159
23311
|
// src/commands/identity.ts
|
|
23160
23312
|
function isUserDimension(type) {
|
|
23161
23313
|
return type === "user";
|
|
@@ -23451,14 +23603,13 @@ function assertPrincipalShape(principal) {
|
|
|
23451
23603
|
}
|
|
23452
23604
|
}
|
|
23453
23605
|
var SELF_PRINCIPAL_ALIAS = "me";
|
|
23454
|
-
async function resolvePrincipalArg(raw, auth) {
|
|
23606
|
+
async function resolvePrincipalArg(raw, auth, deps) {
|
|
23455
23607
|
if (raw !== SELF_PRINCIPAL_ALIAS) return raw;
|
|
23456
|
-
const self2 = await
|
|
23457
|
-
|
|
23458
|
-
|
|
23459
|
-
|
|
23460
|
-
|
|
23461
|
-
}
|
|
23608
|
+
const self2 = await resolveSelfPrincipalForTenant(
|
|
23609
|
+
{ ...auth, client: auth.client },
|
|
23610
|
+
deps.fetchImpl
|
|
23611
|
+
);
|
|
23612
|
+
if (!self2) throw new Error(selfPrincipalUnresolvedMessage(auth.tenant, "--principal"));
|
|
23462
23613
|
return self2.principalId;
|
|
23463
23614
|
}
|
|
23464
23615
|
function roleSummary(p) {
|
|
@@ -23511,7 +23662,7 @@ async function runAccessGrant(opts, deps = {}) {
|
|
|
23511
23662
|
return withAuth(opts, deps, async (auth) => {
|
|
23512
23663
|
const client = auth.client;
|
|
23513
23664
|
try {
|
|
23514
|
-
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
23665
|
+
const principal = await resolvePrincipalArg(opts.principal, auth, deps);
|
|
23515
23666
|
const scopes = hasActions ? [{ allowed_actions: splitCsv(opts.actions) }] : void 0;
|
|
23516
23667
|
const profile = await client.auth.createAccessProfile({
|
|
23517
23668
|
contextId: opts.context,
|
|
@@ -23521,8 +23672,10 @@ async function runAccessGrant(opts, deps = {}) {
|
|
|
23521
23672
|
// Always send the plural `roleIds`, even for a single
|
|
23522
23673
|
// `--role`: it is a strict superset of the deprecated singular
|
|
23523
23674
|
// `roleId` (equivalent to `roleIds: ["<value>"]`), and the platform
|
|
23524
|
-
// reads it uniformly regardless of how many roles compose.
|
|
23525
|
-
|
|
23675
|
+
// reads it uniformly regardless of how many roles compose. With
|
|
23676
|
+
// `--actions` there is no role: omit `roleIds` (the platform refuses an
|
|
23677
|
+
// empty list next to inline `scopes`).
|
|
23678
|
+
roleIds: hasRole ? opts.role : void 0,
|
|
23526
23679
|
scopes,
|
|
23527
23680
|
identityOverrides
|
|
23528
23681
|
}
|
|
@@ -23559,7 +23712,7 @@ async function runAccessRevoke(opts, deps = {}) {
|
|
|
23559
23712
|
return withAuth(opts, deps, async (auth) => {
|
|
23560
23713
|
const client = auth.client;
|
|
23561
23714
|
try {
|
|
23562
|
-
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
23715
|
+
const principal = await resolvePrincipalArg(opts.principal, auth, deps);
|
|
23563
23716
|
await client.auth.deleteAccessProfile({ contextId: opts.context, principalId: principal });
|
|
23564
23717
|
process.stdout.write(` \u2714 Revoked ${principal} from '${opts.context}'
|
|
23565
23718
|
`);
|
|
@@ -23588,7 +23741,7 @@ async function runAccessList(opts, deps = {}) {
|
|
|
23588
23741
|
return withAuth(opts, deps, async (auth) => {
|
|
23589
23742
|
const client = auth.client;
|
|
23590
23743
|
try {
|
|
23591
|
-
const principal = opts.principal ? await resolvePrincipalArg(opts.principal, auth) : void 0;
|
|
23744
|
+
const principal = opts.principal ? await resolvePrincipalArg(opts.principal, auth, deps) : void 0;
|
|
23592
23745
|
const rows = opts.context ? await drainPages(
|
|
23593
23746
|
(startFrom) => client.auth.listAccessProfiles({ contextId: opts.context, startFrom, limit: PAGE_LIMIT })
|
|
23594
23747
|
) : await drainPages(
|
|
@@ -23628,7 +23781,7 @@ async function runAccessGet(opts, deps = {}) {
|
|
|
23628
23781
|
return withAuth(opts, deps, async (auth) => {
|
|
23629
23782
|
const client = auth.client;
|
|
23630
23783
|
try {
|
|
23631
|
-
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
23784
|
+
const principal = await resolvePrincipalArg(opts.principal, auth, deps);
|
|
23632
23785
|
const p = await client.auth.getAccessProfile({ contextId: opts.context, principalId: principal });
|
|
23633
23786
|
if (opts.json) {
|
|
23634
23787
|
process.stdout.write(`${formatJson(p)}
|
|
@@ -23850,11 +24003,13 @@ async function runAccessExplain(opts, deps = {}) {
|
|
|
23850
24003
|
try {
|
|
23851
24004
|
let principal = opts.principal;
|
|
23852
24005
|
if (principal === SELF_PRINCIPAL_ALIAS2) {
|
|
23853
|
-
const self2 = await
|
|
24006
|
+
const self2 = await resolveSelfPrincipalForTenant(
|
|
24007
|
+
{ ...auth, client: auth.client },
|
|
24008
|
+
deps.fetchImpl
|
|
24009
|
+
);
|
|
23854
24010
|
if (!self2) {
|
|
23855
|
-
process.stderr.write(
|
|
23856
|
-
|
|
23857
|
-
);
|
|
24011
|
+
process.stderr.write(` \u2716 ${selfPrincipalUnresolvedMessage(auth.tenant, "--principal")}
|
|
24012
|
+
`);
|
|
23858
24013
|
return 1;
|
|
23859
24014
|
}
|
|
23860
24015
|
principal = self2.principalId;
|
|
@@ -24233,10 +24388,17 @@ async function runContextDestroy(opts, deps = {}) {
|
|
|
24233
24388
|
`);
|
|
24234
24389
|
return 0;
|
|
24235
24390
|
}
|
|
24391
|
+
const tenantFlag = auth.tenant === "test" ? " --tenant test" : "";
|
|
24236
24392
|
process.stdout.write(
|
|
24237
24393
|
`
|
|
24238
24394
|
\u2714 teardown of '${contextId}' started \u2014 the context and its contents are being
|
|
24239
24395
|
deleted asynchronously (it reports status 'purging' until the drain completes)
|
|
24396
|
+
|
|
24397
|
+
Not removed: a service principal a blueprint bootstrap created for this context (the
|
|
24398
|
+
externalId in the blueprint's servicePrincipal) belongs to the whole tenant, so it stays. To clean it up
|
|
24399
|
+
when it exists and nothing else uses it:
|
|
24400
|
+
vectros identity list-all${tenantFlag}
|
|
24401
|
+
vectros identity delete --type user --id <id>${tenantFlag}
|
|
24240
24402
|
`
|
|
24241
24403
|
);
|
|
24242
24404
|
return 0;
|
|
@@ -24313,12 +24475,12 @@ function parseRestrictedToDomain(raw) {
|
|
|
24313
24475
|
const domain = raw.trim();
|
|
24314
24476
|
if (domain.length === 0) {
|
|
24315
24477
|
throw new Error(
|
|
24316
|
-
"--restricted-to-domain must be a non-blank domain (e.g. 'acme.com'). To remove the scope from an existing issuer, use --clear-restricted-to-domain."
|
|
24478
|
+
"--restricted-to-domain must be a non-blank domain (e.g. 'acme.com'). To remove the scope from an existing issuer, use --clear-restricted-to-domain (the platform refuses that unless the registration already holds the unrestricted (issuer, audience) pair)."
|
|
24317
24479
|
);
|
|
24318
24480
|
}
|
|
24319
24481
|
if (!isRestrictedToDomainName(domain.toLowerCase())) {
|
|
24320
24482
|
throw new Error(
|
|
24321
|
-
`--restricted-to-domain must be a DNS name such as 'acme.com' \u2014 letters, digits and dashes in dot-separated labels, at least two labels, no scheme, path or port (write an internationalized domain in its punycode xn-- form); got '${domain}'. To remove the scope from an existing issuer, use --clear-restricted-to-domain.`
|
|
24483
|
+
`--restricted-to-domain must be a DNS name such as 'acme.com' \u2014 letters, digits and dashes in dot-separated labels, at least two labels, no scheme, path or port (write an internationalized domain in its punycode xn-- form); got '${domain}'. To remove the scope from an existing issuer, use --clear-restricted-to-domain (the platform refuses that unless the registration already holds the unrestricted (issuer, audience) pair).`
|
|
24322
24484
|
);
|
|
24323
24485
|
}
|
|
24324
24486
|
return domain;
|
|
@@ -24771,30 +24933,555 @@ ${renderIssuer(issuer)}
|
|
|
24771
24933
|
});
|
|
24772
24934
|
}
|
|
24773
24935
|
|
|
24774
|
-
// src/commands/
|
|
24775
|
-
|
|
24776
|
-
|
|
24777
|
-
|
|
24778
|
-
|
|
24779
|
-
|
|
24780
|
-
|
|
24781
|
-
|
|
24782
|
-
|
|
24783
|
-
|
|
24784
|
-
|
|
24936
|
+
// src/commands/provider-config.ts
|
|
24937
|
+
import { createReadStream as createReadStream2 } from "fs";
|
|
24938
|
+
import * as readline6 from "readline/promises";
|
|
24939
|
+
var PROVIDER_TYPES = ["ANTHROPIC_NATIVE", "OPENAI_COMPATIBLE"];
|
|
24940
|
+
var BILLING_MODES = ["CUSTOMER_DIRECT"];
|
|
24941
|
+
var STATUSES = ["ACTIVE", "DISABLED"];
|
|
24942
|
+
var MAX_SECRET_BYTES = 8 * 1024;
|
|
24943
|
+
async function readBoundedSecret(stream) {
|
|
24944
|
+
try {
|
|
24945
|
+
return await readAllFrom(stream, MAX_SECRET_BYTES);
|
|
24946
|
+
} catch (err) {
|
|
24947
|
+
if (err instanceof TokenTooLargeError) {
|
|
24948
|
+
throw new Error(
|
|
24949
|
+
`the input is larger than ${MAX_SECRET_BYTES / 1024} KB, which no API key is; nothing was read further and nothing was sent.`
|
|
24950
|
+
);
|
|
24951
|
+
}
|
|
24952
|
+
throw err;
|
|
24953
|
+
}
|
|
24785
24954
|
}
|
|
24786
|
-
|
|
24787
|
-
|
|
24788
|
-
|
|
24789
|
-
|
|
24790
|
-
|
|
24791
|
-
|
|
24792
|
-
|
|
24793
|
-
|
|
24794
|
-
|
|
24795
|
-
|
|
24955
|
+
function fileFailureReason2(err) {
|
|
24956
|
+
switch (err?.code) {
|
|
24957
|
+
case "ENOENT":
|
|
24958
|
+
return "no such file.";
|
|
24959
|
+
case "EISDIR":
|
|
24960
|
+
return "that is a directory.";
|
|
24961
|
+
case "EACCES":
|
|
24962
|
+
case "EPERM":
|
|
24963
|
+
return "permission denied.";
|
|
24964
|
+
default:
|
|
24965
|
+
return "it could not be read.";
|
|
24966
|
+
}
|
|
24967
|
+
}
|
|
24968
|
+
async function resolveApiKeySecret(fileFlag, deps, mode) {
|
|
24969
|
+
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
|
|
24970
|
+
const readStdin = deps.readStdin ?? (() => readBoundedSecret(process.stdin));
|
|
24971
|
+
const readTextFile = deps.readTextFile ?? ((path4) => readBoundedSecret(createReadStream2(path4)));
|
|
24972
|
+
let raw;
|
|
24973
|
+
if (fileFlag !== void 0 && fileFlag !== "-") {
|
|
24974
|
+
if (looksLikeCredential(fileFlag)) {
|
|
24975
|
+
throw new Error(
|
|
24976
|
+
"--api-key-secret-file takes the PATH of a file holding the secret, but the value given looks like a secret (or is too long to be a path). Save it to a file and pass that path (for a file in this directory, write ./name), or pipe it on stdin with --api-key-secret-file -."
|
|
24796
24977
|
);
|
|
24797
|
-
|
|
24978
|
+
}
|
|
24979
|
+
try {
|
|
24980
|
+
raw = await readTextFile(fileFlag);
|
|
24981
|
+
} catch (err) {
|
|
24982
|
+
const name = safeBaseName(fileFlag);
|
|
24983
|
+
throw new Error(
|
|
24984
|
+
`could not read the file given to --api-key-secret-file${name ? ` (${name})` : ""}: ${fileFailureReason2(err)}`
|
|
24985
|
+
);
|
|
24986
|
+
}
|
|
24987
|
+
} else if (fileFlag === "-") {
|
|
24988
|
+
if (isTty) {
|
|
24989
|
+
throw new Error(
|
|
24990
|
+
"stdin is a terminal, so --api-key-secret-file - would have you paste the secret where it is displayed. Pipe it instead (some-command | vectros provider-config \u2026), or save it to a file and pass --api-key-secret-file <path>."
|
|
24991
|
+
);
|
|
24992
|
+
}
|
|
24993
|
+
raw = await readStdin();
|
|
24994
|
+
} else if (mode === "required" && !isTty) {
|
|
24995
|
+
process.stderr.write(" \u2026 no --api-key-secret-file given: reading the secret from stdin.\n");
|
|
24996
|
+
raw = await readStdin();
|
|
24997
|
+
} else if (mode === "required") {
|
|
24998
|
+
throw new Error(
|
|
24999
|
+
"apiKeySecret is required. Save it to a file and pass --api-key-secret-file <path>, or pipe it on stdin (some-command | vectros provider-config create \u2026)."
|
|
25000
|
+
);
|
|
25001
|
+
} else {
|
|
25002
|
+
return void 0;
|
|
25003
|
+
}
|
|
25004
|
+
const secret = raw.trim();
|
|
25005
|
+
if (secret.length === 0) throw new Error("the secret is empty.");
|
|
25006
|
+
return secret;
|
|
25007
|
+
}
|
|
25008
|
+
function renderProviderConfig(c) {
|
|
25009
|
+
return formatKeyValues([
|
|
25010
|
+
["id", c.id],
|
|
25011
|
+
["tenantId", c.tenantId],
|
|
25012
|
+
["alias", c.alias],
|
|
25013
|
+
["providerType", c.providerType],
|
|
25014
|
+
["endpoint", c.endpoint],
|
|
25015
|
+
["declaredProcessingRegion", c.declaredProcessingRegion],
|
|
25016
|
+
["defaultProviderModelId", c.defaultProviderModelId],
|
|
25017
|
+
["waiverType", c.waiverType],
|
|
25018
|
+
["billingMode", c.billingMode],
|
|
25019
|
+
["status", c.status],
|
|
25020
|
+
["createdAt", c.createdAt]
|
|
25021
|
+
]);
|
|
25022
|
+
}
|
|
25023
|
+
function billingModeError(value) {
|
|
25024
|
+
if (value === void 0 || BILLING_MODES.includes(value)) return void 0;
|
|
25025
|
+
if (value === "PLATFORM_BILLED") {
|
|
25026
|
+
return "--billing-mode PLATFORM_BILLED is retired: a provider you configure bills you directly, so CUSTOMER_DIRECT is the only billing mode.";
|
|
25027
|
+
}
|
|
25028
|
+
return invalidEnum("--billing-mode", value, BILLING_MODES);
|
|
25029
|
+
}
|
|
25030
|
+
function invalidEnum(flag, value, allowed) {
|
|
25031
|
+
return `${flag} must be one of ${allowed.join(", ")} (got '${value}').`;
|
|
25032
|
+
}
|
|
25033
|
+
var NOT_FOUND_HINT = "'vectros provider-config list --tenant <test|live>' enumerates configs in a tenant.";
|
|
25034
|
+
async function runProviderConfigCreate(opts, deps = {}) {
|
|
25035
|
+
if (!PROVIDER_TYPES.includes(opts.providerType)) {
|
|
25036
|
+
process.stderr.write(` \u2716 ${invalidEnum("--provider-type", opts.providerType, PROVIDER_TYPES)}
|
|
25037
|
+
`);
|
|
25038
|
+
return 2;
|
|
25039
|
+
}
|
|
25040
|
+
const billingModeProblem = billingModeError(opts.billingMode);
|
|
25041
|
+
if (billingModeProblem !== void 0) {
|
|
25042
|
+
process.stderr.write(` \u2716 ${billingModeProblem}
|
|
25043
|
+
`);
|
|
25044
|
+
return 2;
|
|
25045
|
+
}
|
|
25046
|
+
if (opts.status !== void 0 && !STATUSES.includes(opts.status)) {
|
|
25047
|
+
process.stderr.write(` \u2716 ${invalidEnum("--status", opts.status, STATUSES)}
|
|
25048
|
+
`);
|
|
25049
|
+
return 2;
|
|
25050
|
+
}
|
|
25051
|
+
let apiKeySecret;
|
|
25052
|
+
try {
|
|
25053
|
+
apiKeySecret = await resolveApiKeySecret(opts.apiKeySecretFile, deps, "required");
|
|
25054
|
+
} catch (err) {
|
|
25055
|
+
process.stderr.write(` \u2716 ${formatError(err)}
|
|
25056
|
+
`);
|
|
25057
|
+
return 2;
|
|
25058
|
+
}
|
|
25059
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25060
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25061
|
+
try {
|
|
25062
|
+
const tenantId = requireTenantId(auth);
|
|
25063
|
+
const res = await fetchImpl(`${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs`, {
|
|
25064
|
+
method: "POST",
|
|
25065
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}`, "Content-Type": "application/json" },
|
|
25066
|
+
body: JSON.stringify({
|
|
25067
|
+
tenantId,
|
|
25068
|
+
alias: opts.alias,
|
|
25069
|
+
providerType: opts.providerType,
|
|
25070
|
+
endpoint: opts.endpoint,
|
|
25071
|
+
declaredProcessingRegion: opts.declaredProcessingRegion,
|
|
25072
|
+
defaultProviderModelId: opts.defaultProviderModelId,
|
|
25073
|
+
apiKeySecret,
|
|
25074
|
+
waiverType: opts.waiverType,
|
|
25075
|
+
billingMode: opts.billingMode ?? "CUSTOMER_DIRECT",
|
|
25076
|
+
status: opts.status
|
|
25077
|
+
})
|
|
25078
|
+
});
|
|
25079
|
+
if (!res.ok) throw await devRouteError(res, "provider-config create");
|
|
25080
|
+
const config = await res.json();
|
|
25081
|
+
if (opts.json) {
|
|
25082
|
+
process.stdout.write(`${formatJson(config)}
|
|
25083
|
+
`);
|
|
25084
|
+
return 0;
|
|
25085
|
+
}
|
|
25086
|
+
process.stdout.write(`
|
|
25087
|
+
\u2714 provider config '${opts.alias}' created
|
|
25088
|
+
${renderProviderConfig(config)}
|
|
25089
|
+
`);
|
|
25090
|
+
return 0;
|
|
25091
|
+
} catch (err) {
|
|
25092
|
+
process.stderr.write(formatCliError(err, { action: "provider-config create failed" }));
|
|
25093
|
+
return 1;
|
|
25094
|
+
}
|
|
25095
|
+
});
|
|
25096
|
+
}
|
|
25097
|
+
async function runProviderConfigList(opts, deps = {}) {
|
|
25098
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25099
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25100
|
+
try {
|
|
25101
|
+
const tenantId = requireTenantId(auth);
|
|
25102
|
+
const url = `${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs?tenantId=${encodeURIComponent(tenantId)}`;
|
|
25103
|
+
const res = await fetchImpl(url, { method: "GET", headers: { Authorization: `Bearer ${auth.cognitoToken}` } });
|
|
25104
|
+
if (!res.ok) throw await devRouteError(res, "provider-config list");
|
|
25105
|
+
const rows = await res.json();
|
|
25106
|
+
if (opts.json) {
|
|
25107
|
+
process.stdout.write(`${formatJson(rows)}
|
|
25108
|
+
`);
|
|
25109
|
+
return 0;
|
|
25110
|
+
}
|
|
25111
|
+
process.stdout.write(
|
|
25112
|
+
"\n" + formatTable(rows, [
|
|
25113
|
+
{ header: "id", get: (c) => c.id },
|
|
25114
|
+
{ header: "alias", get: (c) => c.alias },
|
|
25115
|
+
{ header: "provider type", get: (c) => c.providerType },
|
|
25116
|
+
{ header: "billing mode", get: (c) => c.billingMode },
|
|
25117
|
+
{ header: "status", get: (c) => c.status }
|
|
25118
|
+
]) + "\n"
|
|
25119
|
+
);
|
|
25120
|
+
return 0;
|
|
25121
|
+
} catch (err) {
|
|
25122
|
+
process.stderr.write(formatCliError(err, { action: "provider-config list failed" }));
|
|
25123
|
+
return 1;
|
|
25124
|
+
}
|
|
25125
|
+
});
|
|
25126
|
+
}
|
|
25127
|
+
async function runProviderConfigGet(opts, deps = {}) {
|
|
25128
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25129
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25130
|
+
try {
|
|
25131
|
+
const url = `${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs/${encodeURIComponent(opts.id)}`;
|
|
25132
|
+
const res = await fetchImpl(url, { method: "GET", headers: { Authorization: `Bearer ${auth.cognitoToken}` } });
|
|
25133
|
+
if (!res.ok) throw await devRouteError(res, "provider-config get");
|
|
25134
|
+
const config = await res.json();
|
|
25135
|
+
if (opts.json) {
|
|
25136
|
+
process.stdout.write(`${formatJson(config)}
|
|
25137
|
+
`);
|
|
25138
|
+
return 0;
|
|
25139
|
+
}
|
|
25140
|
+
process.stdout.write(`
|
|
25141
|
+
${renderProviderConfig(config)}
|
|
25142
|
+
`);
|
|
25143
|
+
return 0;
|
|
25144
|
+
} catch (err) {
|
|
25145
|
+
process.stderr.write(formatCliError(err, { action: "provider-config get failed", notFoundHint: NOT_FOUND_HINT }));
|
|
25146
|
+
return 1;
|
|
25147
|
+
}
|
|
25148
|
+
});
|
|
25149
|
+
}
|
|
25150
|
+
async function runProviderConfigUpdate(opts, deps = {}) {
|
|
25151
|
+
if (opts.providerType !== void 0 && !PROVIDER_TYPES.includes(opts.providerType)) {
|
|
25152
|
+
process.stderr.write(` \u2716 ${invalidEnum("--provider-type", opts.providerType, PROVIDER_TYPES)}
|
|
25153
|
+
`);
|
|
25154
|
+
return 2;
|
|
25155
|
+
}
|
|
25156
|
+
const billingModeProblem = billingModeError(opts.billingMode);
|
|
25157
|
+
if (billingModeProblem !== void 0) {
|
|
25158
|
+
process.stderr.write(` \u2716 ${billingModeProblem}
|
|
25159
|
+
`);
|
|
25160
|
+
return 2;
|
|
25161
|
+
}
|
|
25162
|
+
if (opts.status !== void 0 && !STATUSES.includes(opts.status)) {
|
|
25163
|
+
process.stderr.write(` \u2716 ${invalidEnum("--status", opts.status, STATUSES)}
|
|
25164
|
+
`);
|
|
25165
|
+
return 2;
|
|
25166
|
+
}
|
|
25167
|
+
let apiKeySecret;
|
|
25168
|
+
try {
|
|
25169
|
+
apiKeySecret = await resolveApiKeySecret(opts.apiKeySecretFile, deps, "optional");
|
|
25170
|
+
} catch (err) {
|
|
25171
|
+
process.stderr.write(` \u2716 ${formatError(err)}
|
|
25172
|
+
`);
|
|
25173
|
+
return 2;
|
|
25174
|
+
}
|
|
25175
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25176
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25177
|
+
try {
|
|
25178
|
+
const url = `${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs/${encodeURIComponent(opts.id)}`;
|
|
25179
|
+
const res = await fetchImpl(url, {
|
|
25180
|
+
method: "PUT",
|
|
25181
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}`, "Content-Type": "application/json" },
|
|
25182
|
+
body: JSON.stringify({
|
|
25183
|
+
alias: opts.alias,
|
|
25184
|
+
providerType: opts.providerType,
|
|
25185
|
+
endpoint: opts.endpoint,
|
|
25186
|
+
declaredProcessingRegion: opts.declaredProcessingRegion,
|
|
25187
|
+
defaultProviderModelId: opts.defaultProviderModelId,
|
|
25188
|
+
apiKeySecret,
|
|
25189
|
+
waiverType: opts.waiverType,
|
|
25190
|
+
billingMode: opts.billingMode,
|
|
25191
|
+
status: opts.status
|
|
25192
|
+
})
|
|
25193
|
+
});
|
|
25194
|
+
if (!res.ok) throw await devRouteError(res, "provider-config update");
|
|
25195
|
+
const config = await res.json();
|
|
25196
|
+
if (opts.json) {
|
|
25197
|
+
process.stdout.write(`${formatJson(config)}
|
|
25198
|
+
`);
|
|
25199
|
+
return 0;
|
|
25200
|
+
}
|
|
25201
|
+
process.stdout.write(`
|
|
25202
|
+
\u2714 provider config '${opts.id}' updated
|
|
25203
|
+
${renderProviderConfig(config)}
|
|
25204
|
+
`);
|
|
25205
|
+
return 0;
|
|
25206
|
+
} catch (err) {
|
|
25207
|
+
process.stderr.write(formatCliError(err, { action: "provider-config update failed", notFoundHint: NOT_FOUND_HINT }));
|
|
25208
|
+
return 1;
|
|
25209
|
+
}
|
|
25210
|
+
});
|
|
25211
|
+
}
|
|
25212
|
+
async function runProviderConfigDelete(opts, deps = {}) {
|
|
25213
|
+
const { id } = opts;
|
|
25214
|
+
if (!opts.force) {
|
|
25215
|
+
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
|
|
25216
|
+
if (!isTty) {
|
|
25217
|
+
process.stderr.write(
|
|
25218
|
+
` \u2716 provider-config delete needs an interactive confirmation.
|
|
25219
|
+
Re-run with --force to delete '${id}' without a prompt.
|
|
25220
|
+
`
|
|
25221
|
+
);
|
|
25222
|
+
return 2;
|
|
25223
|
+
}
|
|
25224
|
+
const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
|
|
25225
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
25226
|
+
return { prompt: (q) => rl.question(q), close: () => rl.close() };
|
|
25227
|
+
})();
|
|
25228
|
+
try {
|
|
25229
|
+
process.stdout.write(
|
|
25230
|
+
`
|
|
25231
|
+
\u26A0 Deleting provider config '${id}' removes it permanently. Any inference call that resolves
|
|
25232
|
+
a provider by this config's alias starts failing immediately. To disable it instead (reversible),
|
|
25233
|
+
run \`vectros provider-config update ${id} --status DISABLED\`.
|
|
25234
|
+
|
|
25235
|
+
`
|
|
25236
|
+
);
|
|
25237
|
+
const typed = await promptCtx.prompt(` Type the config id ('${id}') to confirm: `);
|
|
25238
|
+
if (typed.trim() !== id) {
|
|
25239
|
+
process.stderr.write(` \u2716 Confirmation did not match \u2014 nothing deleted.
|
|
25240
|
+
`);
|
|
25241
|
+
return 1;
|
|
25242
|
+
}
|
|
25243
|
+
} finally {
|
|
25244
|
+
promptCtx.close();
|
|
25245
|
+
}
|
|
25246
|
+
}
|
|
25247
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25248
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25249
|
+
try {
|
|
25250
|
+
const url = `${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs/${encodeURIComponent(id)}`;
|
|
25251
|
+
const res = await fetchImpl(url, { method: "DELETE", headers: { Authorization: `Bearer ${auth.cognitoToken}` } });
|
|
25252
|
+
if (!res.ok) throw await devRouteError(res, "provider-config delete");
|
|
25253
|
+
if (opts.json) {
|
|
25254
|
+
process.stdout.write(`${formatJson({ id, deleted: true })}
|
|
25255
|
+
`);
|
|
25256
|
+
return 0;
|
|
25257
|
+
}
|
|
25258
|
+
process.stdout.write(`
|
|
25259
|
+
\u2714 provider config '${id}' deleted
|
|
25260
|
+
`);
|
|
25261
|
+
return 0;
|
|
25262
|
+
} catch (err) {
|
|
25263
|
+
process.stderr.write(formatCliError(err, { action: "provider-config delete failed", notFoundHint: NOT_FOUND_HINT }));
|
|
25264
|
+
return 1;
|
|
25265
|
+
}
|
|
25266
|
+
});
|
|
25267
|
+
}
|
|
25268
|
+
|
|
25269
|
+
// src/commands/domains.ts
|
|
25270
|
+
import * as readline7 from "readline/promises";
|
|
25271
|
+
function renderDomainVerifyResult(d) {
|
|
25272
|
+
return formatKeyValues([
|
|
25273
|
+
["id", d.id],
|
|
25274
|
+
["domain", d.domain],
|
|
25275
|
+
["status", d.status]
|
|
25276
|
+
]);
|
|
25277
|
+
}
|
|
25278
|
+
var NOT_FOUND_HINT2 = "'vectros domains list' enumerates your registered domains.";
|
|
25279
|
+
async function runDomainsRegister(opts, deps = {}) {
|
|
25280
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25281
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25282
|
+
try {
|
|
25283
|
+
const res = await fetchImpl(`${auth.baseUrl.replace(/\/$/, "")}/developer/domains`, {
|
|
25284
|
+
method: "POST",
|
|
25285
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}`, "Content-Type": "application/json" },
|
|
25286
|
+
body: JSON.stringify({ domain: opts.domain })
|
|
25287
|
+
});
|
|
25288
|
+
if (res.status === 409) {
|
|
25289
|
+
const raw = await res.text().catch(() => "");
|
|
25290
|
+
let message = "Domain already registered";
|
|
25291
|
+
let existingId;
|
|
25292
|
+
let existingStatus;
|
|
25293
|
+
try {
|
|
25294
|
+
const parsed = JSON.parse(raw);
|
|
25295
|
+
if (typeof parsed.message === "string" && parsed.message.trim()) message = parsed.message.trim();
|
|
25296
|
+
if (typeof parsed.id === "string") existingId = parsed.id;
|
|
25297
|
+
if (typeof parsed.status === "string") existingStatus = parsed.status;
|
|
25298
|
+
} catch {
|
|
25299
|
+
}
|
|
25300
|
+
if (opts.json) {
|
|
25301
|
+
process.stdout.write(`${formatJson({ message, id: existingId, status: existingStatus })}
|
|
25302
|
+
`);
|
|
25303
|
+
return 1;
|
|
25304
|
+
}
|
|
25305
|
+
process.stderr.write(
|
|
25306
|
+
` \u2716 ${message}${existingId ? ` (id: ${existingId}${existingStatus ? `, status: ${existingStatus}` : ""})` : ""}
|
|
25307
|
+
` + (existingStatus === "PENDING" && existingId ? ` Still pending verification. Run: vectros domains verify ${existingId}
|
|
25308
|
+
` : " Run `vectros domains list` to find it.\n")
|
|
25309
|
+
);
|
|
25310
|
+
return 1;
|
|
25311
|
+
}
|
|
25312
|
+
if (!res.ok) throw await devRouteError(res, "domains register");
|
|
25313
|
+
const result = await res.json();
|
|
25314
|
+
if (opts.json) {
|
|
25315
|
+
process.stdout.write(`${formatJson(result)}
|
|
25316
|
+
`);
|
|
25317
|
+
return 0;
|
|
25318
|
+
}
|
|
25319
|
+
process.stdout.write(
|
|
25320
|
+
`
|
|
25321
|
+
\u2714 domain '${result.domain ?? opts.domain}' registered (id: ${result.id ?? "?"}, status: ${result.status ?? "PENDING"})
|
|
25322
|
+
|
|
25323
|
+
${result.instructions ?? `Add a DNS TXT record: _vectros-verify.${result.domain ?? opts.domain} \u2192 "${result.verificationToken ?? ""}"`}
|
|
25324
|
+
|
|
25325
|
+
Once the record is published, run:
|
|
25326
|
+
vectros domains verify ${result.id ?? "<id>"}
|
|
25327
|
+
`
|
|
25328
|
+
);
|
|
25329
|
+
return 0;
|
|
25330
|
+
} catch (err) {
|
|
25331
|
+
process.stderr.write(formatCliError(err, { action: "domains register failed" }));
|
|
25332
|
+
return 1;
|
|
25333
|
+
}
|
|
25334
|
+
});
|
|
25335
|
+
}
|
|
25336
|
+
async function runDomainsList(opts, deps = {}) {
|
|
25337
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25338
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25339
|
+
try {
|
|
25340
|
+
const res = await fetchImpl(`${auth.baseUrl.replace(/\/$/, "")}/developer/domains`, {
|
|
25341
|
+
method: "GET",
|
|
25342
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}` }
|
|
25343
|
+
});
|
|
25344
|
+
if (!res.ok) throw await devRouteError(res, "domains list");
|
|
25345
|
+
const rows = await res.json();
|
|
25346
|
+
if (opts.json) {
|
|
25347
|
+
process.stdout.write(`${formatJson(rows)}
|
|
25348
|
+
`);
|
|
25349
|
+
return 0;
|
|
25350
|
+
}
|
|
25351
|
+
process.stdout.write(
|
|
25352
|
+
"\n" + formatTable(rows, [
|
|
25353
|
+
{ header: "id", get: (d) => d.id },
|
|
25354
|
+
{ header: "domain", get: (d) => d.domain },
|
|
25355
|
+
{ header: "status", get: (d) => d.status },
|
|
25356
|
+
{ header: "verified at", get: (d) => d.verifiedAt }
|
|
25357
|
+
]) + "\n"
|
|
25358
|
+
);
|
|
25359
|
+
return 0;
|
|
25360
|
+
} catch (err) {
|
|
25361
|
+
process.stderr.write(formatCliError(err, { action: "domains list failed" }));
|
|
25362
|
+
return 1;
|
|
25363
|
+
}
|
|
25364
|
+
});
|
|
25365
|
+
}
|
|
25366
|
+
async function runDomainsDelete(opts, deps = {}) {
|
|
25367
|
+
const { id } = opts;
|
|
25368
|
+
if (!opts.force) {
|
|
25369
|
+
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
|
|
25370
|
+
if (!isTty) {
|
|
25371
|
+
process.stderr.write(
|
|
25372
|
+
` \u2716 domains delete needs an interactive confirmation.
|
|
25373
|
+
Re-run with --force to delete '${id}' without a prompt.
|
|
25374
|
+
`
|
|
25375
|
+
);
|
|
25376
|
+
return 2;
|
|
25377
|
+
}
|
|
25378
|
+
const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
|
|
25379
|
+
const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
|
|
25380
|
+
return { prompt: (q) => rl.question(q), close: () => rl.close() };
|
|
25381
|
+
})();
|
|
25382
|
+
try {
|
|
25383
|
+
process.stdout.write(
|
|
25384
|
+
`
|
|
25385
|
+
\u26A0 Deleting domain '${id}' removes its proof-of-control record. The platform refuses this while
|
|
25386
|
+
any active webhook still references it \u2014 disable or delete those first if this fails.
|
|
25387
|
+
|
|
25388
|
+
`
|
|
25389
|
+
);
|
|
25390
|
+
const typed = await promptCtx.prompt(` Type the domain id ('${id}') to confirm: `);
|
|
25391
|
+
if (typed.trim() !== id) {
|
|
25392
|
+
process.stderr.write(` \u2716 Confirmation did not match \u2014 nothing deleted.
|
|
25393
|
+
`);
|
|
25394
|
+
return 1;
|
|
25395
|
+
}
|
|
25396
|
+
} finally {
|
|
25397
|
+
promptCtx.close();
|
|
25398
|
+
}
|
|
25399
|
+
}
|
|
25400
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25401
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25402
|
+
try {
|
|
25403
|
+
const res = await fetchImpl(`${auth.baseUrl.replace(/\/$/, "")}/developer/domains/${encodeURIComponent(id)}`, {
|
|
25404
|
+
method: "DELETE",
|
|
25405
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}` }
|
|
25406
|
+
});
|
|
25407
|
+
if (!res.ok) throw await devRouteError(res, "domains delete");
|
|
25408
|
+
if (opts.json) {
|
|
25409
|
+
process.stdout.write(`${formatJson({ id, deleted: true })}
|
|
25410
|
+
`);
|
|
25411
|
+
return 0;
|
|
25412
|
+
}
|
|
25413
|
+
process.stdout.write(`
|
|
25414
|
+
\u2714 domain '${id}' deleted
|
|
25415
|
+
`);
|
|
25416
|
+
return 0;
|
|
25417
|
+
} catch (err) {
|
|
25418
|
+
process.stderr.write(formatCliError(err, { action: "domains delete failed", notFoundHint: NOT_FOUND_HINT2 }));
|
|
25419
|
+
return 1;
|
|
25420
|
+
}
|
|
25421
|
+
});
|
|
25422
|
+
}
|
|
25423
|
+
async function runDomainsVerify(opts, deps = {}) {
|
|
25424
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25425
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25426
|
+
try {
|
|
25427
|
+
const res = await fetchImpl(
|
|
25428
|
+
`${auth.baseUrl.replace(/\/$/, "")}/developer/domains/${encodeURIComponent(opts.id)}/verify`,
|
|
25429
|
+
{ method: "POST", headers: { Authorization: `Bearer ${auth.cognitoToken}` } }
|
|
25430
|
+
);
|
|
25431
|
+
if (!res.ok) throw await devRouteError(res, "domains verify");
|
|
25432
|
+
const result = await res.json();
|
|
25433
|
+
const verified = result.verified === true && result.status === "VERIFIED";
|
|
25434
|
+
if (opts.json) {
|
|
25435
|
+
process.stdout.write(`${formatJson(result)}
|
|
25436
|
+
`);
|
|
25437
|
+
return verified ? 0 : 1;
|
|
25438
|
+
}
|
|
25439
|
+
if (!verified) {
|
|
25440
|
+
process.stderr.write(
|
|
25441
|
+
` \u2716 domain '${result.domain ?? opts.id}' is not verified (status: ${result.status ?? "(none)"}).
|
|
25442
|
+
Check the DNS TXT record is published, allow time for it to propagate, then re-run this command.
|
|
25443
|
+
`
|
|
25444
|
+
);
|
|
25445
|
+
return 1;
|
|
25446
|
+
}
|
|
25447
|
+
process.stdout.write(
|
|
25448
|
+
`
|
|
25449
|
+
\u2714 domain '${result.domain ?? opts.id}' verified.
|
|
25450
|
+
${renderDomainVerifyResult(result)}
|
|
25451
|
+
`
|
|
25452
|
+
);
|
|
25453
|
+
return 0;
|
|
25454
|
+
} catch (err) {
|
|
25455
|
+
process.stderr.write(formatCliError(err, { action: "domains verify failed", notFoundHint: NOT_FOUND_HINT2 }));
|
|
25456
|
+
return 1;
|
|
25457
|
+
}
|
|
25458
|
+
});
|
|
25459
|
+
}
|
|
25460
|
+
|
|
25461
|
+
// src/commands/namespace.ts
|
|
25462
|
+
function renderNamespace(n) {
|
|
25463
|
+
return formatKeyValues([
|
|
25464
|
+
["namespace", n.namespace],
|
|
25465
|
+
["contextId", n.contextId ?? "(tenant-wide)"],
|
|
25466
|
+
["entityBacked", n.entityBacked === void 0 ? void 0 : String(n.entityBacked)],
|
|
25467
|
+
["reserved", n.reserved === void 0 ? void 0 : String(n.reserved)],
|
|
25468
|
+
["specificityRank", n.specificityRank === void 0 ? void 0 : String(n.specificityRank)],
|
|
25469
|
+
["defaultSchemaId", n.defaultSchemaId],
|
|
25470
|
+
["createdAt", n.createdAt]
|
|
25471
|
+
]);
|
|
25472
|
+
}
|
|
25473
|
+
async function runNamespaceList(opts, deps = {}) {
|
|
25474
|
+
return withAuth({ ...opts, context: opts.context }, deps, async (auth) => {
|
|
25475
|
+
const client = auth.client;
|
|
25476
|
+
try {
|
|
25477
|
+
const rows = await drainPages(
|
|
25478
|
+
(startFrom) => client.identity.listNamespaces({
|
|
25479
|
+
limit: PAGE_LIMIT,
|
|
25480
|
+
...opts.context ? { contextId: opts.context } : {},
|
|
25481
|
+
...startFrom ? { startFrom } : {}
|
|
25482
|
+
})
|
|
25483
|
+
);
|
|
25484
|
+
if (opts.json) {
|
|
24798
25485
|
process.stdout.write(`${formatJson(rows)}
|
|
24799
25486
|
`);
|
|
24800
25487
|
return 0;
|
|
@@ -24875,7 +25562,7 @@ ${renderNamespace(ns)}
|
|
|
24875
25562
|
|
|
24876
25563
|
// src/commands/scripts.ts
|
|
24877
25564
|
import { readFile as readFile3 } from "fs/promises";
|
|
24878
|
-
import * as
|
|
25565
|
+
import * as readline8 from "readline/promises";
|
|
24879
25566
|
var SOURCE_MAX_BYTES = 3e5;
|
|
24880
25567
|
var INPUT_CONTRACT_MAX_BYTES = 5e4;
|
|
24881
25568
|
var utf8Bytes2 = (s) => new TextEncoder().encode(s).length;
|
|
@@ -25094,7 +25781,7 @@ async function runScriptsDelete(opts, deps = {}) {
|
|
|
25094
25781
|
return 2;
|
|
25095
25782
|
}
|
|
25096
25783
|
const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
|
|
25097
|
-
const rl =
|
|
25784
|
+
const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
|
|
25098
25785
|
return { prompt: (q) => rl.question(q), close: () => rl.close() };
|
|
25099
25786
|
})();
|
|
25100
25787
|
try {
|
|
@@ -26077,15 +26764,14 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
|
|
|
26077
26764
|
|
|
26078
26765
|
// src/commands/join.ts
|
|
26079
26766
|
var SELF_ALIAS = "me";
|
|
26080
|
-
async function resolvePrincipal(as, auth) {
|
|
26767
|
+
async function resolvePrincipal(as, auth, deps) {
|
|
26081
26768
|
const raw = as ?? SELF_ALIAS;
|
|
26082
26769
|
if (raw === SELF_ALIAS) {
|
|
26083
|
-
const self2 = await
|
|
26084
|
-
|
|
26085
|
-
|
|
26086
|
-
|
|
26087
|
-
|
|
26088
|
-
}
|
|
26770
|
+
const self2 = await resolveSelfPrincipalForTenant(
|
|
26771
|
+
{ ...auth, client: auth.client },
|
|
26772
|
+
deps.fetchImpl
|
|
26773
|
+
);
|
|
26774
|
+
if (!self2) throw new Error(selfPrincipalUnresolvedMessage(auth.tenant, "--as"));
|
|
26089
26775
|
return self2.principalId;
|
|
26090
26776
|
}
|
|
26091
26777
|
if (!raw.startsWith("usr_")) {
|
|
@@ -26155,7 +26841,7 @@ async function runJoin(opts, deps = {}) {
|
|
|
26155
26841
|
const client = auth.client;
|
|
26156
26842
|
try {
|
|
26157
26843
|
const tenantId = requireTenantId(auth);
|
|
26158
|
-
const principalId = await resolvePrincipal(opts.as, auth);
|
|
26844
|
+
const principalId = await resolvePrincipal(opts.as, auth, deps);
|
|
26159
26845
|
const userId = normalizePrincipalUserId(principalId);
|
|
26160
26846
|
const scopes = hasActions ? [{ allowed_actions: splitCsv(opts.actions) }] : void 0;
|
|
26161
26847
|
await client.auth.createAccessProfile({
|
|
@@ -26166,7 +26852,9 @@ async function runJoin(opts, deps = {}) {
|
|
|
26166
26852
|
// Always send the plural `roleIds`, even for a single
|
|
26167
26853
|
// `--role`: a strict superset of the deprecated singular `roleId`,
|
|
26168
26854
|
// read uniformly by the platform regardless of how many compose.
|
|
26169
|
-
|
|
26855
|
+
// With `--actions` there is no role: omit `roleIds` (the platform
|
|
26856
|
+
// refuses an empty list next to inline `scopes`).
|
|
26857
|
+
roleIds: hasRole ? opts.role : void 0,
|
|
26170
26858
|
scopes,
|
|
26171
26859
|
identityOverrides
|
|
26172
26860
|
}
|
|
@@ -26253,9 +26941,9 @@ function emitJoined(opts) {
|
|
|
26253
26941
|
|
|
26254
26942
|
// src/build-info.ts
|
|
26255
26943
|
var BUILD_INFO = {
|
|
26256
|
-
cli: true ? "0.
|
|
26257
|
-
sdk: true ? "0.
|
|
26258
|
-
blueprints: true ? "0.20.
|
|
26944
|
+
cli: true ? "0.25.0" : "dev",
|
|
26945
|
+
sdk: true ? "0.47.0" : "dev",
|
|
26946
|
+
blueprints: true ? "0.20.2" : "dev"
|
|
26259
26947
|
};
|
|
26260
26948
|
function formatBuildInfo() {
|
|
26261
26949
|
return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
|
|
@@ -26340,6 +27028,8 @@ function buildProgram(deps = {}) {
|
|
|
26340
27028
|
registerRoleCommands(program, deps);
|
|
26341
27029
|
registerContextCommands(program, deps);
|
|
26342
27030
|
registerIssuersCommands(program, deps);
|
|
27031
|
+
registerProviderConfigCommands(program, deps);
|
|
27032
|
+
registerDomainsCommands(program, deps);
|
|
26343
27033
|
registerNamespaceCommands(program, deps);
|
|
26344
27034
|
registerScriptsCommands(program, deps);
|
|
26345
27035
|
registerAssumeCommand(program, deps);
|
|
@@ -26461,7 +27151,7 @@ function registerContextCommands(program, deps) {
|
|
|
26461
27151
|
);
|
|
26462
27152
|
});
|
|
26463
27153
|
const destroy = context.command("destroy").description(
|
|
26464
|
-
"Permanently destroy an app context and ALL of its data (irreversible; asks for confirmation)."
|
|
27154
|
+
"Permanently destroy an app context and ALL of its data (irreversible; asks for confirmation). A blueprint's service principal belongs to the whole tenant and is NOT removed: delete it with 'identity delete'."
|
|
26465
27155
|
).argument("<contextId>", "the app context to destroy").option("--force", "skip the interactive confirmation (required when stdin is not a TTY)").option("--tenant <tenant>", "tenant the context lives in: 'test' or 'live' (default live)");
|
|
26466
27156
|
addCommonAuthOptions(destroy).action(
|
|
26467
27157
|
async (contextId, flags) => {
|
|
@@ -26550,7 +27240,10 @@ function registerIssuersCommands(program, deps) {
|
|
|
26550
27240
|
).option("--clear-captured-claims", "capture nothing beyond the email claim (sends an empty list; XOR --captured-claim)").option(
|
|
26551
27241
|
"--restricted-to-domain <domain>",
|
|
26552
27242
|
"scope the registration to a domain already verified for your account; a token with no matching 'hd' claim then stops matching \u2014 omit to leave unchanged. Refused on a registration that is pending verification: verify it first"
|
|
26553
|
-
).option(
|
|
27243
|
+
).option(
|
|
27244
|
+
"--clear-restricted-to-domain",
|
|
27245
|
+
"remove the domain scope (XOR --restricted-to-domain). Refused unless the registration already holds the unrestricted (issuer, audience) pair, which one created scoped to a domain normally does not; see the README"
|
|
27246
|
+
).option("--tenant <tenant>", "tenant the context lives in: 'test' or 'live' (default live)");
|
|
26554
27247
|
addCommonAuthOptions(update).action(
|
|
26555
27248
|
async (issuerId, flags) => {
|
|
26556
27249
|
const tenant = parseTenantFlag(flags.tenant);
|
|
@@ -26611,6 +27304,89 @@ function registerIssuersCommands(program, deps) {
|
|
|
26611
27304
|
}
|
|
26612
27305
|
);
|
|
26613
27306
|
}
|
|
27307
|
+
function registerProviderConfigCommands(program, deps) {
|
|
27308
|
+
const providerConfig = program.command("provider-config").description("Manage BYOM (bring-your-own-model) tenant-level provider configs.");
|
|
27309
|
+
const create = providerConfig.command("create").description("Create a new BYOM provider config (not idempotent \u2014 a repeat --alias 400s).").requiredOption("--alias <alias>", "operator-facing name used to select this config; unique per tenant; 1-64 lowercase letters, digits, - or _").requiredOption("--provider-type <type>", "'ANTHROPIC_NATIVE' or 'OPENAI_COMPATIBLE'").option("--endpoint <url>", "required for OPENAI_COMPATIBLE (no default); an optional override for ANTHROPIC_NATIVE; https on port 443").option("--declared-processing-region <region>", "customer-asserted, free-text, Vectros-unverified transparency field").option("--default-provider-model-id <id>", "convenience default only \u2014 a per-request model id, when the caller supplies one, always takes precedence").requiredOption("--api-key-secret-file <path>", "path to a file holding the provider credential ('-' for stdin) \u2014 never pass the secret as a bare flag value").requiredOption("--waiver-type <type>", "the waiver code this config is entitled under; the platform accepts byo-model-inference:customer-key for a config you set up yourself").option("--billing-mode <mode>", "only 'CUSTOMER_DIRECT' (your provider bills you), which is also the default; 'PLATFORM_BILLED' is retired").option("--status <status>", "'ACTIVE' or 'DISABLED' (default ACTIVE) \u2014 ACTIVE requires the tenant already hold a granted waiver for --waiver-type").option("--tenant <tenant>", "tenant to create the config in: 'test' or 'live' (default live)");
|
|
27310
|
+
addCommonAuthOptions(create).action(
|
|
27311
|
+
async (flags) => {
|
|
27312
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27313
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27314
|
+
process.exitCode = await runProviderConfigCreate(
|
|
27315
|
+
{
|
|
27316
|
+
...toCommonOptions(flags),
|
|
27317
|
+
alias: flags.alias,
|
|
27318
|
+
providerType: flags.providerType,
|
|
27319
|
+
endpoint: flags.endpoint,
|
|
27320
|
+
declaredProcessingRegion: flags.declaredProcessingRegion,
|
|
27321
|
+
defaultProviderModelId: flags.defaultProviderModelId,
|
|
27322
|
+
apiKeySecretFile: flags.apiKeySecretFile,
|
|
27323
|
+
waiverType: flags.waiverType,
|
|
27324
|
+
billingMode: flags.billingMode,
|
|
27325
|
+
status: flags.status,
|
|
27326
|
+
tenant
|
|
27327
|
+
},
|
|
27328
|
+
deps
|
|
27329
|
+
);
|
|
27330
|
+
}
|
|
27331
|
+
);
|
|
27332
|
+
const list = providerConfig.command("list").description("List provider configs in one tenant.").option("--tenant <tenant>", "tenant to read: 'test' or 'live' (default live)");
|
|
27333
|
+
addCommonAuthOptions(list).action(async (flags) => {
|
|
27334
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27335
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27336
|
+
process.exitCode = await runProviderConfigList({ ...toCommonOptions(flags), tenant }, deps);
|
|
27337
|
+
});
|
|
27338
|
+
const get = providerConfig.command("get").description("Show one provider config's metadata (no secret \u2014 the platform never returns it).").argument("<id>");
|
|
27339
|
+
addCommonAuthOptions(get).action(async (id, flags) => {
|
|
27340
|
+
process.exitCode = await runProviderConfigGet({ ...toCommonOptions(flags), id }, deps);
|
|
27341
|
+
});
|
|
27342
|
+
const update = providerConfig.command("update").description("Patch a provider config (partial update \u2014 an omitted field is left unchanged).").argument("<id>").option("--alias <alias>", "rename this config (unique per tenant); the platform refuses a new alias that is not 1-64 lowercase letters, digits, - or _ (an alias the config already holds is accepted)").option("--provider-type <type>", "'ANTHROPIC_NATIVE' or 'OPENAI_COMPATIBLE'; changing the type of an existing config needs --endpoint stated in the same command (--endpoint '' means the provider's default), or the platform answers 400").option("--endpoint <url>", "update the provider endpoint; https on port 443; changing the host needs --api-key-secret-file again; '' clears it (the provider's default), which also changes the host").option("--declared-processing-region <region>", "update the transparency field").option("--default-provider-model-id <id>", "update the convenience default model id").option("--api-key-secret-file <path>", "rotate the provider credential from a file ('-' for stdin); omit to leave the stored secret unchanged (required when --endpoint changes the host)").option("--waiver-type <type>", "update the waiver code this config is entitled under; the platform refuses a change to anything but byo-model-inference:customer-key (a value the config already holds is accepted)").option("--billing-mode <mode>", "only 'CUSTOMER_DIRECT' (your provider bills you); 'PLATFORM_BILLED' is retired. Omit it to leave the stored mode unchanged").option("--status <status>", "'ACTIVE' or 'DISABLED' \u2014 activating requires a granted waiver for this config's waiverType");
|
|
27343
|
+
addCommonAuthOptions(update).action(
|
|
27344
|
+
async (id, flags) => {
|
|
27345
|
+
process.exitCode = await runProviderConfigUpdate(
|
|
27346
|
+
{
|
|
27347
|
+
...toCommonOptions(flags),
|
|
27348
|
+
id,
|
|
27349
|
+
alias: flags.alias,
|
|
27350
|
+
providerType: flags.providerType,
|
|
27351
|
+
endpoint: flags.endpoint,
|
|
27352
|
+
declaredProcessingRegion: flags.declaredProcessingRegion,
|
|
27353
|
+
defaultProviderModelId: flags.defaultProviderModelId,
|
|
27354
|
+
apiKeySecretFile: flags.apiKeySecretFile,
|
|
27355
|
+
waiverType: flags.waiverType,
|
|
27356
|
+
billingMode: flags.billingMode,
|
|
27357
|
+
status: flags.status
|
|
27358
|
+
},
|
|
27359
|
+
deps
|
|
27360
|
+
);
|
|
27361
|
+
}
|
|
27362
|
+
);
|
|
27363
|
+
const del = providerConfig.command("delete").description("Delete a provider config.").argument("<id>").option("--force", "skip the interactive confirmation (required when stdin is not a TTY)");
|
|
27364
|
+
addCommonAuthOptions(del).action(async (id, flags) => {
|
|
27365
|
+
process.exitCode = await runProviderConfigDelete(
|
|
27366
|
+
{ ...toCommonOptions(flags), id, force: Boolean(flags.force) },
|
|
27367
|
+
deps
|
|
27368
|
+
);
|
|
27369
|
+
});
|
|
27370
|
+
}
|
|
27371
|
+
function registerDomainsCommands(program, deps) {
|
|
27372
|
+
const domains = program.command("domains").description("Manage account-level domain ownership verification (DNS TXT proof-of-control).");
|
|
27373
|
+
const register = domains.command("register").description("Register a domain and start DNS TXT verification (not idempotent \u2014 a repeat --domain 409s).").requiredOption("--domain <domain>", "the domain to prove control of (e.g. example.com)");
|
|
27374
|
+
addCommonAuthOptions(register).action(async (flags) => {
|
|
27375
|
+
process.exitCode = await runDomainsRegister({ ...toCommonOptions(flags), domain: flags.domain }, deps);
|
|
27376
|
+
});
|
|
27377
|
+
const list = domains.command("list").description("List domains registered for this account (both tenants share this list).");
|
|
27378
|
+
addCommonAuthOptions(list).action(async (flags) => {
|
|
27379
|
+
process.exitCode = await runDomainsList(toCommonOptions(flags), deps);
|
|
27380
|
+
});
|
|
27381
|
+
const del = domains.command("delete").description("Deregister a domain (refused while any active webhook still references it).").argument("<id>").option("--force", "skip the interactive confirmation (required when stdin is not a TTY)");
|
|
27382
|
+
addCommonAuthOptions(del).action(async (id, flags) => {
|
|
27383
|
+
process.exitCode = await runDomainsDelete({ ...toCommonOptions(flags), id, force: Boolean(flags.force) }, deps);
|
|
27384
|
+
});
|
|
27385
|
+
const verify = domains.command("verify").description("Perform one DNS TXT lookup and report VERIFIED/FAILED \u2014 always re-runnable, never polls.").argument("<id>");
|
|
27386
|
+
addCommonAuthOptions(verify).action(async (id, flags) => {
|
|
27387
|
+
process.exitCode = await runDomainsVerify({ ...toCommonOptions(flags), id }, deps);
|
|
27388
|
+
});
|
|
27389
|
+
}
|
|
26614
27390
|
function registerNamespaceCommands(program, deps) {
|
|
26615
27391
|
const namespace = program.command("namespace").description("Manage scope-namespace registrations (whether scope:<namespace> values resolve to identity entities).");
|
|
26616
27392
|
const register = namespace.command("register").description("Register a new scope namespace (NOT idempotent \u2014 a repeat name 400s).").argument("<name>", "namespace name: 2-32 chars, a lowercase letter first, then lowercase/digit/dash/underscore").requiredOption("--context <c>", "app context to own this registration").requiredOption("--specificity-rank <n>", "position in the account specificity order (integer 0-1000000, unique)").option("--entity-backed", "values must resolve to an identity entity (create via `identity create --type <name>`)").option("--default-schema-id <id>", "default record schema bound to entities in this namespace");
|
|
@@ -26845,12 +27621,15 @@ function dimensionOption() {
|
|
|
26845
27621
|
).makeOptionMandatory();
|
|
26846
27622
|
}
|
|
26847
27623
|
function registerIdentityCommands(program, deps) {
|
|
26848
|
-
const identity = program.command("identity").description("Manage tenant-wide
|
|
26849
|
-
const create = identity.command("create").description("Create (idempotent by externalId) a principal.").addOption(dimensionOption()).requiredOption("--external-id <id>", "your stable id for the principal (idempotency key)").option("--name <name>", "display name (entity namespaces only, not --type user)").option("--email <email>", "email (user only)").option("--service", "user only: mint a SERVICE principal (default HUMAN)").option("--scope <ns:value>", "entity only: a parent ownership edge (repeatable, \u22642, e.g. org:<id>)", collect, []).option("--metadata <json>", "JSON object of metadata");
|
|
27624
|
+
const identity = program.command("identity").description("Manage principals: users (tenant-wide) and namespaced entities (an entity lives with its namespace) \u2014 reference, never embed.");
|
|
27625
|
+
const create = identity.command("create").description("Create (idempotent by externalId) a principal.").addOption(dimensionOption()).requiredOption("--external-id <id>", "your stable id for the principal (idempotency key)").option("--name <name>", "display name (entity namespaces only, not --type user)").option("--email <email>", "email (user only)").option("--service", "user only: mint a SERVICE principal (default HUMAN)").option("--scope <ns:value>", "entity only: a parent ownership edge (repeatable, \u22642, e.g. org:<id>)", collect, []).option("--metadata <json>", "JSON object of metadata").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
26850
27626
|
addCommonAuthOptions(create).action(
|
|
26851
27627
|
async (flags) => {
|
|
27628
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27629
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
26852
27630
|
process.exitCode = await runIdentityCreate({
|
|
26853
27631
|
...toCommonOptions(flags),
|
|
27632
|
+
tenant,
|
|
26854
27633
|
type: flags.type,
|
|
26855
27634
|
externalId: flags.externalId,
|
|
26856
27635
|
name: flags.name,
|
|
@@ -26861,11 +27640,14 @@ function registerIdentityCommands(program, deps) {
|
|
|
26861
27640
|
}, deps);
|
|
26862
27641
|
}
|
|
26863
27642
|
);
|
|
26864
|
-
const list = identity.command("list").description("List principals of a dimension (optionally filtered).").addOption(dimensionOption()).option("--external-id <id>", "filter to a stable id").option("--limit <n>", "page size (1\u2013100)", (v) => Number.parseInt(v, 10));
|
|
27643
|
+
const list = identity.command("list").description("List principals of a dimension (optionally filtered).").addOption(dimensionOption()).option("--external-id <id>", "filter to a stable id").option("--limit <n>", "page size (1\u2013100)", (v) => Number.parseInt(v, 10)).option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
26865
27644
|
addCommonAuthOptions(list).action(
|
|
26866
27645
|
async (flags) => {
|
|
27646
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27647
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
26867
27648
|
process.exitCode = await runIdentityList({
|
|
26868
27649
|
...toCommonOptions(flags),
|
|
27650
|
+
tenant,
|
|
26869
27651
|
type: flags.type,
|
|
26870
27652
|
externalId: flags.externalId,
|
|
26871
27653
|
limit: flags.limit
|
|
@@ -26885,20 +27667,30 @@ function registerIdentityCommands(program, deps) {
|
|
|
26885
27667
|
);
|
|
26886
27668
|
}
|
|
26887
27669
|
);
|
|
26888
|
-
const get = identity.command("get").description("Show one principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id (from create/list)");
|
|
27670
|
+
const get = identity.command("get").description("Show one principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id (from create/list)").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
26889
27671
|
addCommonAuthOptions(get).action(async (flags) => {
|
|
26890
|
-
|
|
27672
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27673
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27674
|
+
process.exitCode = await runIdentityGet({ ...toCommonOptions(flags), tenant, type: flags.type, id: flags.id }, deps);
|
|
26891
27675
|
});
|
|
26892
|
-
const del = identity.command("delete").description("Delete a principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id");
|
|
27676
|
+
const del = identity.command("delete").description("Delete a principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
26893
27677
|
addAuthOptions(del).action(async (flags) => {
|
|
26894
|
-
|
|
27678
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27679
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27680
|
+
process.exitCode = await runIdentityDelete(
|
|
27681
|
+
{ ...toCommonOptions(flags), tenant, type: flags.type, id: flags.id },
|
|
27682
|
+
deps
|
|
27683
|
+
);
|
|
26895
27684
|
});
|
|
26896
|
-
const invite = identity.command("invite").description("Onboard a person into a context by email (POST /v1/users/invite) \u2014 no root key required.").requiredOption("--context <contextId>", "the app context the invitee will operate in").requiredOption("--role <roleId>", "an existing Role in that context; the invitee inherits its scopes").requiredOption("--email <email>", "the invitee's email address").option("--first-name <name>", "personalizes the invitation email greeting").option("--accept-url <url>", "your accept-invite landing page (required unless --no-send-email)").option("--no-send-email", "skip platform delivery; the response carries the raw inviteToken/acceptLink instead");
|
|
27685
|
+
const invite = identity.command("invite").description("Onboard a person into a context by email (POST /v1/users/invite) \u2014 no root key required.").requiredOption("--context <contextId>", "the app context the invitee will operate in").requiredOption("--role <roleId>", "an existing Role in that context; the invitee inherits its scopes").requiredOption("--email <email>", "the invitee's email address").option("--first-name <name>", "personalizes the invitation email greeting").option("--accept-url <url>", "your accept-invite landing page (required unless --no-send-email)").option("--no-send-email", "skip platform delivery; the response carries the raw inviteToken/acceptLink instead").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
26897
27686
|
addCommonAuthOptions(invite).action(
|
|
26898
27687
|
async (flags) => {
|
|
27688
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27689
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
26899
27690
|
process.exitCode = await runIdentityInvite(
|
|
26900
27691
|
{
|
|
26901
27692
|
...toCommonOptions(flags),
|
|
27693
|
+
tenant,
|
|
26902
27694
|
context: flags.context,
|
|
26903
27695
|
role: flags.role,
|
|
26904
27696
|
email: flags.email,
|