@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.js
CHANGED
|
@@ -518,7 +518,7 @@ var require_graceful_fs = __commonJS({
|
|
|
518
518
|
function patch(fs5) {
|
|
519
519
|
polyfills(fs5);
|
|
520
520
|
fs5.gracefulify = patch;
|
|
521
|
-
fs5.createReadStream =
|
|
521
|
+
fs5.createReadStream = createReadStream3;
|
|
522
522
|
fs5.createWriteStream = createWriteStream;
|
|
523
523
|
var fs$readFile = fs5.readFile;
|
|
524
524
|
fs5.readFile = readFile4;
|
|
@@ -728,7 +728,7 @@ var require_graceful_fs = __commonJS({
|
|
|
728
728
|
}
|
|
729
729
|
});
|
|
730
730
|
}
|
|
731
|
-
function
|
|
731
|
+
function createReadStream3(path4, options) {
|
|
732
732
|
return new fs5.ReadStream(path4, options);
|
|
733
733
|
}
|
|
734
734
|
function createWriteStream(path4, options) {
|
|
@@ -1634,6 +1634,7 @@ __export(api_exports, {
|
|
|
1634
1634
|
ContentTooLargeError: () => ContentTooLargeError,
|
|
1635
1635
|
DocumentContextEvent: () => DocumentContextEvent,
|
|
1636
1636
|
DocumentLookupRequest: () => DocumentLookupRequest,
|
|
1637
|
+
DocumentPatchRequest: () => DocumentPatchRequest,
|
|
1637
1638
|
DocumentRequest: () => DocumentRequest,
|
|
1638
1639
|
DocumentResponse: () => DocumentResponse,
|
|
1639
1640
|
DoneEvent: () => DoneEvent,
|
|
@@ -1977,6 +1978,13 @@ var DocumentLookupRequest;
|
|
|
1977
1978
|
Desc: "desc"
|
|
1978
1979
|
};
|
|
1979
1980
|
})(DocumentLookupRequest || (DocumentLookupRequest = {}));
|
|
1981
|
+
var DocumentPatchRequest;
|
|
1982
|
+
((DocumentPatchRequest2) => {
|
|
1983
|
+
DocumentPatchRequest2.Status = {
|
|
1984
|
+
Active: "ACTIVE",
|
|
1985
|
+
Archived: "ARCHIVED"
|
|
1986
|
+
};
|
|
1987
|
+
})(DocumentPatchRequest || (DocumentPatchRequest = {}));
|
|
1980
1988
|
var FileUploadRequest;
|
|
1981
1989
|
((FileUploadRequest2) => {
|
|
1982
1990
|
FileUploadRequest2.IndexMode = {
|
|
@@ -1989,6 +1997,7 @@ var FileUploadRequest;
|
|
|
1989
1997
|
var documents_exports = {};
|
|
1990
1998
|
__export(documents_exports, {
|
|
1991
1999
|
DocumentLookupRequest: () => DocumentLookupRequest,
|
|
2000
|
+
DocumentPatchRequest: () => DocumentPatchRequest,
|
|
1992
2001
|
FileUploadRequest: () => FileUploadRequest,
|
|
1993
2002
|
LookupDocumentsRequestOrder: () => LookupDocumentsRequestOrder
|
|
1994
2003
|
});
|
|
@@ -4035,7 +4044,7 @@ var AuthClient = class {
|
|
|
4035
4044
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/jwks");
|
|
4036
4045
|
}
|
|
4037
4046
|
/**
|
|
4038
|
-
* Returns a page of per-subject PHI read-access rows: who read which subject's PHI, when, against which record, and whether any sensitive value was actually revealed in plaintext. Metadata only — never the PHI itself.
|
|
4047
|
+
* Returns a page of per-subject PHI read-access rows: who read which subject's PHI, when, against which record, and whether any sensitive value was actually revealed in plaintext. Metadata only — never the PHI itself. Rows exist only where read-access logging is enabled. Provide at least one query axis: a subject (`subjectType` + `subjectId`) within a `contextId` for the primary accounting query; `resourceId` within a `contextId` for 'who read this record'; `callerKeyId` for 'what did this credential read' (account-wide forensic); or `contextId` alone to enumerate a whole context. `from`/`to` bound the time window. Results are scoped to your account, derived from your token — never from input. Requires the `access-log:r` scope.
|
|
4039
4048
|
*
|
|
4040
4049
|
* @param {Vectros.GetAccessLogRequest} request
|
|
4041
4050
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -4653,6 +4662,7 @@ var AuthClient = class {
|
|
|
4653
4662
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
4654
4663
|
*
|
|
4655
4664
|
* @throws {@link Vectros.BadRequestError}
|
|
4665
|
+
* @throws {@link Vectros.PaymentRequiredError}
|
|
4656
4666
|
* @throws {@link Vectros.ForbiddenError}
|
|
4657
4667
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
4658
4668
|
*
|
|
@@ -4702,6 +4712,8 @@ var AuthClient = class {
|
|
|
4702
4712
|
switch (_response.error.statusCode) {
|
|
4703
4713
|
case 400:
|
|
4704
4714
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
4715
|
+
case 402:
|
|
4716
|
+
throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
|
|
4705
4717
|
case 403:
|
|
4706
4718
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
4707
4719
|
case 429:
|
|
@@ -5737,7 +5749,7 @@ var AuthClient = class {
|
|
|
5737
5749
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/issuers/{issuerId}");
|
|
5738
5750
|
}
|
|
5739
5751
|
/**
|
|
5740
|
-
* Updates the mutable fields of a registered issuer: `subClaim`, `emailClaim`, `userinfoUri`, `capturedClaims`, `restrictedToDomain` (a new non-blank value must already be a VERIFIED domain for your account; an empty string clears it
|
|
5752
|
+
* Updates the mutable fields of a registered issuer: `subClaim`, `emailClaim`, `userinfoUri`, `capturedClaims`, `restrictedToDomain` (a new non-blank value must already be a VERIFIED domain for your account; an empty string clears it where permitted; see `IssuerUpdateRequest.restrictedToDomain` for the full semantics), `status` (`active`/`suspended` — a registration awaiting verification (`pending_verification`) accepts no status change until it is verified; a suspended issuer's tokens are rejected identically to an unregistered issuer at exchange time), and `selfSignupPolicies`. Fields omitted from the body are left unchanged (partial update). `issuer`, `jwksUri`, `audience`, and `contextId` are trust-anchor / routing-pin fields and are immutable via this route — supplying a value that differs from the current registration is rejected with 400; supplying the current value back is a no-op. Rotating a trust anchor requires deleting and re-registering the issuer, which is itself refused while any user is bound through it. **`subClaim` is identity-determining, not merely cosmetic**: it names which verified JWT claim is read as the federated user's identifier, so changing it on an issuer that already has bound users would silently re-identify (or, under self-signup, orphan) every one of them — changing it is therefore refused once any user has bound through this issuer, the same guard `DELETE` already applies, and is only free before the first real login. Requires a root API key or the bootstrap's provisioning capability, gated identically to every other operation on this surface. A credential confined to one app context may only update an issuer registered in that context; naming one registered in another context returns 404, identically to a nonexistent issuerId. A root API key may update any issuer.
|
|
5741
5753
|
*
|
|
5742
5754
|
* @param {Vectros.IssuerUpdateRequest} request
|
|
5743
5755
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6547,7 +6559,7 @@ var ComplianceClient = class {
|
|
|
6547
6559
|
this._options = normalizeClientOptionsWithAuth(options);
|
|
6548
6560
|
}
|
|
6549
6561
|
/**
|
|
6550
|
-
* Submits
|
|
6562
|
+
* Submits an erasure request for a single end-subject (a user, or an identity entity in any namespace). Erasure deletes the records, documents, folders and record schemas the subject solely owns across the declared contexts (and, for a user, its access profiles there), plus its identity and lookup rows when all contexts are covered. Rows co-owned with another owner scope or principal are kept; read-access log entries are not erased. The request is asynchronous: it returns 202 with a `requestId`; poll `GET /v1/erasure-requests/{id}` until the job completes to obtain the completion certificate. Requires a root API key — a scoped credential is rejected with 403.
|
|
6551
6563
|
*
|
|
6552
6564
|
* @param {Vectros.ErasureRequest} request
|
|
6553
6565
|
* @param {ComplianceClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6610,7 +6622,7 @@ var ComplianceClient = class {
|
|
|
6610
6622
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/erasure-requests");
|
|
6611
6623
|
}
|
|
6612
6624
|
/**
|
|
6613
|
-
* Polls an erasure request by id. While the job is still running this returns its status only; once it completes, the response also includes the
|
|
6625
|
+
* Polls an erasure request by id. While the job is still running this returns its status only; once it completes, the response also includes the completion certificate (which contexts were swept, per-context deletion counts, whether the identity was kept, and reports of dangling references and shared rows left intact). Requires a root API key.
|
|
6614
6626
|
*
|
|
6615
6627
|
* @param {Vectros.GetErasureRequestRequest} request
|
|
6616
6628
|
* @param {ComplianceClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6853,7 +6865,7 @@ var DocumentsClient = class {
|
|
|
6853
6865
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents");
|
|
6854
6866
|
}
|
|
6855
6867
|
/**
|
|
6856
|
-
* 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.
|
|
6868
|
+
* 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.
|
|
6857
6869
|
*
|
|
6858
6870
|
* @param {Vectros.IngestDocumentRequest} request
|
|
6859
6871
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6873,10 +6885,11 @@ var DocumentsClient = class {
|
|
|
6873
6885
|
return HttpResponsePromise.fromPromise(this.__ingestDocument(request, requestOptions));
|
|
6874
6886
|
}
|
|
6875
6887
|
async __ingestDocument(request, requestOptions) {
|
|
6876
|
-
const { upsert, allowClear, body: _body } = request;
|
|
6888
|
+
const { upsert, allowClear, confirmUntyped, body: _body } = request;
|
|
6877
6889
|
const _queryParams = {
|
|
6878
6890
|
upsert,
|
|
6879
|
-
allowClear
|
|
6891
|
+
allowClear,
|
|
6892
|
+
confirmUntyped
|
|
6880
6893
|
};
|
|
6881
6894
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
6882
6895
|
const _headers = mergeHeaders(
|
|
@@ -7111,7 +7124,7 @@ var DocumentsClient = class {
|
|
|
7111
7124
|
/**
|
|
7112
7125
|
* 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.
|
|
7113
7126
|
*
|
|
7114
|
-
* @param {Vectros.
|
|
7127
|
+
* @param {Vectros.DocumentPatchRequest} request
|
|
7115
7128
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
7116
7129
|
*
|
|
7117
7130
|
* @throws {@link Vectros.BadRequestError}
|
|
@@ -7122,17 +7135,14 @@ var DocumentsClient = class {
|
|
|
7122
7135
|
*
|
|
7123
7136
|
* @example
|
|
7124
7137
|
* await client.documents.patchDocument({
|
|
7125
|
-
* id: "id"
|
|
7126
|
-
* body: {
|
|
7127
|
-
* title: "Patient Intake Form \u2014 Jane Doe"
|
|
7128
|
-
* }
|
|
7138
|
+
* id: "id"
|
|
7129
7139
|
* })
|
|
7130
7140
|
*/
|
|
7131
7141
|
patchDocument(request, requestOptions) {
|
|
7132
7142
|
return HttpResponsePromise.fromPromise(this.__patchDocument(request, requestOptions));
|
|
7133
7143
|
}
|
|
7134
7144
|
async __patchDocument(request, requestOptions) {
|
|
7135
|
-
const { id,
|
|
7145
|
+
const { id, ..._body } = request;
|
|
7136
7146
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
7137
7147
|
const _headers = mergeHeaders(
|
|
7138
7148
|
_authRequest.headers,
|
|
@@ -7504,7 +7514,7 @@ var DocumentsClient = class {
|
|
|
7504
7514
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/versions");
|
|
7505
7515
|
}
|
|
7506
7516
|
/**
|
|
7507
|
-
* 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.
|
|
7517
|
+
* 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.
|
|
7508
7518
|
*
|
|
7509
7519
|
* @param {Vectros.FileUploadRequest} request
|
|
7510
7520
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -7523,9 +7533,10 @@ var DocumentsClient = class {
|
|
|
7523
7533
|
return HttpResponsePromise.fromPromise(this.__uploadDocument(request, requestOptions));
|
|
7524
7534
|
}
|
|
7525
7535
|
async __uploadDocument(request, requestOptions) {
|
|
7526
|
-
const { upsert, ..._body } = request;
|
|
7536
|
+
const { upsert, confirmUntyped, ..._body } = request;
|
|
7527
7537
|
const _queryParams = {
|
|
7528
|
-
upsert
|
|
7538
|
+
upsert,
|
|
7539
|
+
confirmUntyped
|
|
7529
7540
|
};
|
|
7530
7541
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
7531
7542
|
const _headers = mergeHeaders(
|
|
@@ -9430,6 +9441,8 @@ var InferenceClient = class {
|
|
|
9430
9441
|
throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
|
|
9431
9442
|
case 403:
|
|
9432
9443
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
9444
|
+
case 413:
|
|
9445
|
+
throw new ContentTooLargeError(_response.error.body, _response.rawResponse);
|
|
9433
9446
|
case 429:
|
|
9434
9447
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
9435
9448
|
default:
|
|
@@ -9569,6 +9582,8 @@ var InferenceClient = class {
|
|
|
9569
9582
|
throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
|
|
9570
9583
|
case 403:
|
|
9571
9584
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
9585
|
+
case 413:
|
|
9586
|
+
throw new ContentTooLargeError(_response.error.body, _response.rawResponse);
|
|
9572
9587
|
case 429:
|
|
9573
9588
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
9574
9589
|
case 500:
|
|
@@ -11866,7 +11881,7 @@ var RenderHintsSchema = import_zod.z.object({
|
|
|
11866
11881
|
helpText: import_zod.z.string().optional(),
|
|
11867
11882
|
// Marks this field as the record's headline (display) field — the linked
|
|
11868
11883
|
// primary column in the records list + the title on the detail view. At most
|
|
11869
|
-
// one per schema (the platform
|
|
11884
|
+
// one per schema (the platform stores the hint and does not enforce one; the reference UI uses the first by order). Format passthrough →
|
|
11870
11885
|
// SchemaRequest.renderHints[fieldId].displayField.
|
|
11871
11886
|
displayField: import_zod.z.boolean().optional()
|
|
11872
11887
|
}).strict();
|
|
@@ -12541,7 +12556,11 @@ var BlueprintIssuerSchema = import_zod.z.object({
|
|
|
12541
12556
|
* actually scoped. An unverified domain fails the apply with the platform's `400`.
|
|
12542
12557
|
*
|
|
12543
12558
|
* Omit to register a domain-less issuer. Omitting it on a re-apply leaves an existing scope
|
|
12544
|
-
* unchanged
|
|
12559
|
+
* unchanged. On platform 0.45.0 and later a scope is removed only from a registration that
|
|
12560
|
+
* already holds the unrestricted `(issuer, audience)` pair, which one created with a domain
|
|
12561
|
+
* normally does not (`vectros issuers update --clear-restricted-to-domain` is refused for it):
|
|
12562
|
+
* replace it with a new issuer declared without `restrictedToDomain` and verify that, under a
|
|
12563
|
+
* different app context if the old issuer has served users.
|
|
12545
12564
|
*/
|
|
12546
12565
|
restrictedToDomain: import_zod.z.string().regex(RESTRICTED_DOMAIN_RE, {
|
|
12547
12566
|
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"
|
|
@@ -13521,7 +13540,8 @@ var BlueprintSchema = import_zod.z.object({
|
|
|
13521
13540
|
* alongside app-context/service-principal creation and issuers, BEFORE
|
|
13522
13541
|
* schemas/seed (an `entityBacked` namespace's entities, and an
|
|
13523
13542
|
* `allowedSurfaces: ['entity']` schema, both need the registration to
|
|
13524
|
-
* already exist). `org`/`client`
|
|
13543
|
+
* already exist). `org`/`client` are registered the same way as any other
|
|
13544
|
+
* namespace: signup does not register them, so declare an entry for each you use.
|
|
13525
13545
|
*/
|
|
13526
13546
|
namespaces: import_zod.z.array(BlueprintNamespaceSchema).optional(),
|
|
13527
13547
|
/**
|
|
@@ -15655,7 +15675,7 @@ async function devRouteError(res, action) {
|
|
|
15655
15675
|
}
|
|
15656
15676
|
function formatCliError(err, opts) {
|
|
15657
15677
|
const info = extractServerError(err);
|
|
15658
|
-
const actionable = renderActionableError(info);
|
|
15678
|
+
const actionable = renderActionableError(info, opts.notFoundHint);
|
|
15659
15679
|
const block = actionable ?? ` \u2716 ${opts.action}: ${info.message}
|
|
15660
15680
|
`;
|
|
15661
15681
|
const debug = opts.debug ?? Boolean(process.env.VECTROS_MCP_DEBUG);
|
|
@@ -15669,7 +15689,7 @@ function formatCliError(err, opts) {
|
|
|
15669
15689
|
}
|
|
15670
15690
|
return block;
|
|
15671
15691
|
}
|
|
15672
|
-
function renderActionableError(info) {
|
|
15692
|
+
function renderActionableError(info, notFoundHint) {
|
|
15673
15693
|
const msg = info.message;
|
|
15674
15694
|
switch (info.errorCode) {
|
|
15675
15695
|
case "SESSION_REFRESH_REQUIRED":
|
|
@@ -15707,7 +15727,7 @@ function renderActionableError(info) {
|
|
|
15707
15727
|
}
|
|
15708
15728
|
if (info.statusCode === 404) {
|
|
15709
15729
|
return ` \u2716 ${msg}
|
|
15710
|
-
Check the id \u2014 'vectros context list' enumerates the contexts you can reach.
|
|
15730
|
+
Check the id \u2014 ${notFoundHint ?? "'vectros context list' enumerates the contexts you can reach."}
|
|
15711
15731
|
`;
|
|
15712
15732
|
}
|
|
15713
15733
|
if (/migration-locked|cannot be (changed|modified)|immutable/i.test(msg) && /schema|field|lookup|type/i.test(msg)) {
|
|
@@ -16485,6 +16505,25 @@ async function clearSession(baseDir) {
|
|
|
16485
16505
|
return false;
|
|
16486
16506
|
}
|
|
16487
16507
|
}
|
|
16508
|
+
var TokenEndpointError = class extends Error {
|
|
16509
|
+
status;
|
|
16510
|
+
/** The OAuth `error` field of a JSON error body (e.g. `invalid_grant`), when there is one. */
|
|
16511
|
+
oauthError;
|
|
16512
|
+
constructor(status, text) {
|
|
16513
|
+
super(`token endpoint HTTP ${status}${text ? ` \u2014 ${stripControlCharacters(text, 500)}` : ""}`);
|
|
16514
|
+
this.name = "TokenEndpointError";
|
|
16515
|
+
this.status = status;
|
|
16516
|
+
this.oauthError = oauthErrorOf(text);
|
|
16517
|
+
}
|
|
16518
|
+
};
|
|
16519
|
+
function oauthErrorOf(text) {
|
|
16520
|
+
try {
|
|
16521
|
+
const parsed = JSON.parse(text);
|
|
16522
|
+
return typeof parsed.error === "string" ? parsed.error : void 0;
|
|
16523
|
+
} catch {
|
|
16524
|
+
return void 0;
|
|
16525
|
+
}
|
|
16526
|
+
}
|
|
16488
16527
|
async function postToken(config, body, fetchImpl) {
|
|
16489
16528
|
const res = await fetchImpl(tokenEndpoint(config), {
|
|
16490
16529
|
method: "POST",
|
|
@@ -16492,9 +16531,7 @@ async function postToken(config, body, fetchImpl) {
|
|
|
16492
16531
|
body: body.toString()
|
|
16493
16532
|
});
|
|
16494
16533
|
const text = await res.text().catch(() => "");
|
|
16495
|
-
if (!res.ok)
|
|
16496
|
-
throw new Error(`token endpoint HTTP ${res.status}${text ? ` \u2014 ${stripControlCharacters(text, 500)}` : ""}`);
|
|
16497
|
-
}
|
|
16534
|
+
if (!res.ok) throw new TokenEndpointError(res.status, text);
|
|
16498
16535
|
try {
|
|
16499
16536
|
return JSON.parse(text);
|
|
16500
16537
|
} catch {
|
|
@@ -16510,31 +16547,54 @@ async function refreshTokens(config, refreshToken, fetchImpl = fetch) {
|
|
|
16510
16547
|
|
|
16511
16548
|
// src/auth-context.ts
|
|
16512
16549
|
var COGNITO_TOKEN_ENV = "VECTROS_BOOTSTRAP_TOKEN";
|
|
16550
|
+
function loginCommandFor(env) {
|
|
16551
|
+
return env === "staging" ? "vectros login --env staging" : "vectros login";
|
|
16552
|
+
}
|
|
16513
16553
|
var MissingCredentialError = class extends Error {
|
|
16514
|
-
constructor() {
|
|
16554
|
+
constructor(env) {
|
|
16515
16555
|
super(
|
|
16516
|
-
`No credential found.
|
|
16556
|
+
`No credential found. Run \`${loginCommandFor(env)}\` to sign in, or set ${COGNITO_TOKEN_ENV} or pass --token (generate a token in the developer portal).`
|
|
16517
16557
|
);
|
|
16518
16558
|
this.name = "MissingCredentialError";
|
|
16519
16559
|
}
|
|
16520
16560
|
};
|
|
16561
|
+
var SessionExpiredError = class extends Error {
|
|
16562
|
+
constructor(env) {
|
|
16563
|
+
super(
|
|
16564
|
+
`Your Vectros login session has expired or was revoked. Run \`${loginCommandFor(env)}\` to sign in again.`
|
|
16565
|
+
);
|
|
16566
|
+
this.name = "SessionExpiredError";
|
|
16567
|
+
}
|
|
16568
|
+
};
|
|
16521
16569
|
function resolveCognitoTokenSync(explicit) {
|
|
16522
16570
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
16523
16571
|
const fromEnv = process.env[COGNITO_TOKEN_ENV];
|
|
16524
16572
|
if (fromEnv && fromEnv.trim()) return fromEnv.trim();
|
|
16525
16573
|
return void 0;
|
|
16526
16574
|
}
|
|
16527
|
-
async function resolveCognitoToken(env, explicit) {
|
|
16575
|
+
async function resolveCognitoToken(env, explicit, deps = { readSession, refreshTokens, saveSession }) {
|
|
16528
16576
|
const sync = resolveCognitoTokenSync(explicit);
|
|
16529
16577
|
if (sync) return sync;
|
|
16530
|
-
const session = await readSession();
|
|
16578
|
+
const session = await deps.readSession();
|
|
16531
16579
|
if (session && session.env === env) {
|
|
16532
16580
|
const config = resolveCognitoConfig(env);
|
|
16533
|
-
|
|
16581
|
+
let tokens;
|
|
16582
|
+
try {
|
|
16583
|
+
tokens = await deps.refreshTokens(config, session.refreshToken);
|
|
16584
|
+
} catch (err) {
|
|
16585
|
+
if (err instanceof TokenEndpointError && err.oauthError === "invalid_grant") throw new SessionExpiredError(env);
|
|
16586
|
+
throw err;
|
|
16587
|
+
}
|
|
16588
|
+
if (tokens.refresh_token && tokens.refresh_token !== session.refreshToken) {
|
|
16589
|
+
const stored = await deps.readSession().catch(() => void 0);
|
|
16590
|
+
if (stored?.refreshToken === session.refreshToken) {
|
|
16591
|
+
await deps.saveSession({ ...session, refreshToken: tokens.refresh_token }).catch(() => void 0);
|
|
16592
|
+
}
|
|
16593
|
+
}
|
|
16534
16594
|
const bearer = tokens.id_token ?? tokens.access_token;
|
|
16535
16595
|
if (bearer) return bearer;
|
|
16536
16596
|
}
|
|
16537
|
-
throw new MissingCredentialError();
|
|
16597
|
+
throw new MissingCredentialError(env);
|
|
16538
16598
|
}
|
|
16539
16599
|
async function resolveAuthContext(opts) {
|
|
16540
16600
|
const baseUrl = envToBaseUrl(opts.env, opts.baseUrl ?? process.env.VECTROS_API_BASE_URL);
|
|
@@ -17046,6 +17106,7 @@ function keyNameFor(pack, machineId) {
|
|
|
17046
17106
|
}
|
|
17047
17107
|
|
|
17048
17108
|
// src/identity-resolver.ts
|
|
17109
|
+
var IDENTITIES_TO_ENSURE_HEADING = " Identities to ensure (idempotent by external id; a non-user entity lives with its namespace):";
|
|
17049
17110
|
function userPayload(decl) {
|
|
17050
17111
|
const meta = decl.metadata;
|
|
17051
17112
|
if (decl.displayName === void 0) return meta;
|
|
@@ -17186,7 +17247,7 @@ async function withAuth(opts, deps, body) {
|
|
|
17186
17247
|
log
|
|
17187
17248
|
});
|
|
17188
17249
|
} catch (err) {
|
|
17189
|
-
if (err instanceof MissingCredentialError || err instanceof InvalidBaseUrlError || err instanceof InvalidCognitoRegionError) {
|
|
17250
|
+
if (err instanceof MissingCredentialError || err instanceof SessionExpiredError || err instanceof InvalidBaseUrlError || err instanceof InvalidCognitoRegionError) {
|
|
17190
17251
|
process.stderr.write(` \u2716 ${err.message}
|
|
17191
17252
|
`);
|
|
17192
17253
|
return 2;
|
|
@@ -17781,15 +17842,6 @@ async function provisionContext(client, input) {
|
|
|
17781
17842
|
const { blueprint, allowTenantWideNamespaces = false, readExistingKey, confirmExistingPrincipal, log } = input;
|
|
17782
17843
|
assertBlueprintGates(blueprint);
|
|
17783
17844
|
assertTenantWideNamespacesAllowed(blueprint, allowTenantWideNamespaces);
|
|
17784
|
-
const ctxResp = await client.auth.createAppContext({
|
|
17785
|
-
body: {
|
|
17786
|
-
contextId: blueprint.contextId,
|
|
17787
|
-
name: contextNameOf(blueprint),
|
|
17788
|
-
...blueprint.identityProjectionClaims === void 0 ? {} : { identityProjectionClaims: blueprint.identityProjectionClaims }
|
|
17789
|
-
}
|
|
17790
|
-
});
|
|
17791
|
-
const contextAction = actionOf(ctxResp.created);
|
|
17792
|
-
log.debug({ component: "loader", contextId: blueprint.contextId, contextAction }, "app-context ensured");
|
|
17793
17845
|
const user = await client.identity.createUser({
|
|
17794
17846
|
body: {
|
|
17795
17847
|
externalId: blueprint.servicePrincipal.externalId,
|
|
@@ -17830,6 +17882,15 @@ async function provisionContext(client, input) {
|
|
|
17830
17882
|
"existing-principal collision confirmed (or unwired) \u2014 proceeding with any further write to this principal"
|
|
17831
17883
|
);
|
|
17832
17884
|
}
|
|
17885
|
+
const ctxResp = await client.auth.createAppContext({
|
|
17886
|
+
body: {
|
|
17887
|
+
contextId: blueprint.contextId,
|
|
17888
|
+
name: contextNameOf(blueprint),
|
|
17889
|
+
...blueprint.identityProjectionClaims === void 0 ? {} : { identityProjectionClaims: blueprint.identityProjectionClaims }
|
|
17890
|
+
}
|
|
17891
|
+
});
|
|
17892
|
+
const contextAction = actionOf(ctxResp.created);
|
|
17893
|
+
log.debug({ component: "loader", contextId: blueprint.contextId, contextAction }, "app-context ensured");
|
|
17833
17894
|
const needsContextPinnedBootstrap = (blueprint.namespaces ?? []).length > 0;
|
|
17834
17895
|
const ctxBootstrapClient = needsContextPinnedBootstrap ? input.remintBootstrapForContext ? await input.remintBootstrapForContext(blueprint.contextId) : client : void 0;
|
|
17835
17896
|
if (ctxBootstrapClient && input.remintBootstrapForContext) {
|
|
@@ -18338,14 +18399,14 @@ function renderPlanDataScope(ds) {
|
|
|
18338
18399
|
function buildPlan(blueprint, opts = {}) {
|
|
18339
18400
|
const steps = [];
|
|
18340
18401
|
const projection = blueprint.identityProjectionClaims?.length ? `, identity projection: [${blueprint.identityProjectionClaims.join(", ")}]` : blueprint.identityProjectionClaims ? ", identity projection: disabled (empty list)" : "";
|
|
18341
|
-
steps.push({
|
|
18342
|
-
kind: "context",
|
|
18343
|
-
summary: `context '${blueprint.contextId}' (${contextNameOf(blueprint)}${projection})`
|
|
18344
|
-
});
|
|
18345
18402
|
steps.push({
|
|
18346
18403
|
kind: "principal",
|
|
18347
18404
|
summary: `principal '${blueprint.servicePrincipal.externalId}' (SERVICE)`
|
|
18348
18405
|
});
|
|
18406
|
+
steps.push({
|
|
18407
|
+
kind: "context",
|
|
18408
|
+
summary: `context '${blueprint.contextId}' (${contextNameOf(blueprint)}${projection})`
|
|
18409
|
+
});
|
|
18349
18410
|
for (const ns of blueprint.namespaces ?? []) {
|
|
18350
18411
|
const ownerNote = ns.tenantWide ? "TENANT-WIDE \u2014 visible to every context" : `owned by '${blueprint.contextId}'`;
|
|
18351
18412
|
const entityNote = ns.entityBacked ? ", entity-backed" : "";
|
|
@@ -18900,9 +18961,8 @@ var OsKeychainSecretStore = class {
|
|
|
18900
18961
|
* enumeration, and `deleteCredential` on a nonexistent ref returns the same
|
|
18901
18962
|
* meaningless `false` as Linux. So the `.some(c => c.account === ref)` comparison
|
|
18902
18963
|
* is sound on both measured platforms. Still unmeasured: a CI lane that runs this
|
|
18903
|
-
* against the real Windows store on every
|
|
18904
|
-
* measurement, not a regression gate.
|
|
18905
|
-
* public npm, so no tracker number here.)
|
|
18964
|
+
* against the real Windows store on every change — the dev-machine probe is a point
|
|
18965
|
+
* measurement, not a regression gate.
|
|
18906
18966
|
*
|
|
18907
18967
|
* ⚠️ THIS METHOD'S FAIL-CLOSED GUARANTEE IS AN UPSTREAM BEHAVIOURAL CONTRACT, NOT
|
|
18908
18968
|
* SOMETHING THIS CODE ENFORCES. `@napi-rs/keyring` is pinned to an EXACT version
|
|
@@ -19058,7 +19118,7 @@ var CryptoFileSecretStore = class {
|
|
|
19058
19118
|
async setSecret(ref, secret) {
|
|
19059
19119
|
const key = await this.encryptionKey();
|
|
19060
19120
|
const iv = (0, import_node_crypto3.randomBytes)(IV_LEN);
|
|
19061
|
-
const cipher = (0, import_node_crypto3.createCipheriv)(ALGO, key, iv);
|
|
19121
|
+
const cipher = (0, import_node_crypto3.createCipheriv)(ALGO, key, iv, { authTagLength: TAG_LEN });
|
|
19062
19122
|
const enc = Buffer.concat([cipher.update(secret, "utf8"), cipher.final()]);
|
|
19063
19123
|
const tag = cipher.getAuthTag();
|
|
19064
19124
|
const blob = Buffer.concat([iv, tag, enc]);
|
|
@@ -19081,7 +19141,7 @@ var CryptoFileSecretStore = class {
|
|
|
19081
19141
|
const iv = blob.subarray(0, IV_LEN);
|
|
19082
19142
|
const tag = blob.subarray(IV_LEN, IV_LEN + TAG_LEN);
|
|
19083
19143
|
const enc = blob.subarray(IV_LEN + TAG_LEN);
|
|
19084
|
-
const decipher = (0, import_node_crypto3.createDecipheriv)(ALGO, key, iv);
|
|
19144
|
+
const decipher = (0, import_node_crypto3.createDecipheriv)(ALGO, key, iv, { authTagLength: TAG_LEN });
|
|
19085
19145
|
decipher.setAuthTag(tag);
|
|
19086
19146
|
return Buffer.concat([decipher.update(enc), decipher.final()]).toString("utf8");
|
|
19087
19147
|
} catch {
|
|
@@ -19265,8 +19325,8 @@ async function withIndexLock(dir, fn, timings = LOCK_OPTS) {
|
|
|
19265
19325
|
// sections are ~1ms, so in practice the holder is gone before the first tick
|
|
19266
19326
|
// and nothing is ever detected. (The probe that observed the no-op ran at
|
|
19267
19327
|
// stale:2000 with the section artificially held open — a configuration we do
|
|
19268
|
-
// not ship
|
|
19269
|
-
//
|
|
19328
|
+
// not ship, and a measurement taken in a configuration you don't ship is not
|
|
19329
|
+
// evidence about the configuration you do.) The
|
|
19270
19330
|
// honest shape: our release runs unconditionally, and after a stale-break
|
|
19271
19331
|
// collision it CAN remove a successor's fresh lock — one more route to two
|
|
19272
19332
|
// writers at once, on top of the rmdir race below. The backstop for both is
|
|
@@ -19281,7 +19341,7 @@ async function withIndexLock(dir, fn, timings = LOCK_OPTS) {
|
|
|
19281
19341
|
// judged: two waiters can both condemn one crash remnant, and the second's
|
|
19282
19342
|
// already-decided rmdir destroys the first's FRESH lock. Nothing freezes.
|
|
19283
19343
|
//
|
|
19284
|
-
// MEASURED against this exact config (
|
|
19344
|
+
// MEASURED against this exact config (probe results below):
|
|
19285
19345
|
// real OS processes, no injection : 0/25 and 1/25 (two independent runs)
|
|
19286
19346
|
// in-process, 5 waiters : ~3% per round
|
|
19287
19347
|
// rival's rmdir lagged one turn : 19/25
|
|
@@ -21114,7 +21174,7 @@ async function resolveBridgeCognitoToken(args, prompt, isTty, resolveCognito = r
|
|
|
21114
21174
|
}
|
|
21115
21175
|
if (!isTty) {
|
|
21116
21176
|
throw new Error(
|
|
21117
|
-
|
|
21177
|
+
`No bridge token: set VECTROS_BOOTSTRAP_TOKEN, pass --token, or run \`${loginCommandFor(args.env)}\` first (required in non-interactive mode).`
|
|
21118
21178
|
);
|
|
21119
21179
|
}
|
|
21120
21180
|
process.stdout.write(
|
|
@@ -21223,7 +21283,9 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
21223
21283
|
`);
|
|
21224
21284
|
const declaredIdentities = Object.entries(blueprint.identities ?? {});
|
|
21225
21285
|
if (declaredIdentities.length > 0) {
|
|
21226
|
-
process.stdout.write(
|
|
21286
|
+
process.stdout.write(`
|
|
21287
|
+
${IDENTITIES_TO_ENSURE_HEADING}
|
|
21288
|
+
`);
|
|
21227
21289
|
for (const [name, decl] of declaredIdentities) {
|
|
21228
21290
|
process.stdout.write(` ~ ${name} (${decl.kind}) \u2014 external id '${decl.externalId}'
|
|
21229
21291
|
`);
|
|
@@ -21454,6 +21516,12 @@ ${codeAddHint}`);
|
|
|
21454
21516
|
}
|
|
21455
21517
|
return finishApply();
|
|
21456
21518
|
} catch (err) {
|
|
21519
|
+
if (err instanceof SessionExpiredError) {
|
|
21520
|
+
process.stderr.write(`
|
|
21521
|
+
\u2716 ${err.message}
|
|
21522
|
+
`);
|
|
21523
|
+
return 2;
|
|
21524
|
+
}
|
|
21457
21525
|
if (err instanceof LostKeyError) {
|
|
21458
21526
|
process.stderr.write(`
|
|
21459
21527
|
\u2716 ${err.message}
|
|
@@ -21825,7 +21893,9 @@ ${renderPlan(blueprint, buildPlan(blueprint, { noSeed, noKey: true }), { prune:
|
|
|
21825
21893
|
process.stdout.write(applySeedNotice(blueprint, args.seed));
|
|
21826
21894
|
const declaredIdentities = Object.entries(blueprint.identities ?? {});
|
|
21827
21895
|
if (declaredIdentities.length > 0) {
|
|
21828
|
-
process.stdout.write(
|
|
21896
|
+
process.stdout.write(`
|
|
21897
|
+
${IDENTITIES_TO_ENSURE_HEADING}
|
|
21898
|
+
`);
|
|
21829
21899
|
for (const [name, decl] of declaredIdentities) {
|
|
21830
21900
|
process.stdout.write(` ~ ${name} (${decl.kind}) \u2014 external id '${decl.externalId}'
|
|
21831
21901
|
`);
|
|
@@ -21958,7 +22028,7 @@ function reportApplyError(err) {
|
|
|
21958
22028
|
If this is expected (a re-apply of a blueprint you manage), re-run with --confirm-existing-principal
|
|
21959
22029
|
(with --yes in a pipeline, where it is standing consent), or confirm at the prompt.
|
|
21960
22030
|
If you don't recognize this identity, investigate the blueprint's servicePrincipal.externalId first.
|
|
21961
|
-
Only the
|
|
22031
|
+
Only the principal lookup had run; no context, grant or anything else was created or changed.
|
|
21962
22032
|
`
|
|
21963
22033
|
);
|
|
21964
22034
|
return 3;
|
|
@@ -22311,10 +22381,14 @@ schemas:
|
|
|
22311
22381
|
accessProfile:
|
|
22312
22382
|
# SCOPE GATE: data-plane only (records/schemas/search/documents/folders/inference/entities),
|
|
22313
22383
|
# PLUS profiles:r/u/d (bounded by the platform to redistributing access you already have,
|
|
22314
|
-
# never granting more)
|
|
22315
|
-
# platform resolves "self" from the caller, not from anything you declare here)
|
|
22316
|
-
#
|
|
22317
|
-
#
|
|
22384
|
+
# never granting more), profiles:c:self (create a profile for yourself only \u2014 the
|
|
22385
|
+
# platform resolves "self" from the caller, not from anything you declare here) and
|
|
22386
|
+
# scripts:x:<script-name> (run one named script that this blueprint ships; there is no bare
|
|
22387
|
+
# or wildcard form).
|
|
22388
|
+
# A bare profiles:c is accepted ONLY when this same clause also declares the
|
|
22389
|
+
# member-lifecycle capability (see "capabilities" below); without it, and always for
|
|
22390
|
+
# profiles:c:usr_<id> naming another principal, it is REJECTED \u2014 no override. Every other
|
|
22391
|
+
# control-plane resource (keys/users/admin/namespaces/triggers/...) and "*" is REJECTED the same way.
|
|
22318
22392
|
# Ops: c|r|u|d, combinable (records:cru). Prefer the narrowest set (least privilege).
|
|
22319
22393
|
allowedActions: [records:r, records:c, records:u, search:r, schemas:r]
|
|
22320
22394
|
# dataScope binds the key to specific owners. Include null to ALSO see
|
|
@@ -22330,8 +22404,10 @@ accessProfile:
|
|
|
22330
22404
|
# above) unless every owner genuinely sharing every record is what you want.
|
|
22331
22405
|
dataScope: {}
|
|
22332
22406
|
# capabilities is a SEPARATE gated dimension from allowedActions \u2014 named platform
|
|
22333
|
-
# effects a resource:ops verb can't express. Only 'member-lifecycle'
|
|
22334
|
-
# today; every other name is
|
|
22407
|
+
# effects a resource:ops verb can't express. Only 'member-lifecycle' and
|
|
22408
|
+
# 'delegate-principal-stamp' are admitted today; every other name is REJECTED, same
|
|
22409
|
+
# posture as the scope gate above. 'member-lifecycle' is also what lets this clause
|
|
22410
|
+
# carry a bare profiles:c.
|
|
22335
22411
|
# capabilities: [member-lifecycle]
|
|
22336
22412
|
|
|
22337
22413
|
servicePrincipal:
|
|
@@ -22418,8 +22494,10 @@ Usage:
|
|
|
22418
22494
|
|
|
22419
22495
|
<file|name> is a path to your own blueprint (YAML .yaml/.yml or JSON .json) OR
|
|
22420
22496
|
the name of a bundled blueprint. The same scope gate that protects bundled
|
|
22421
|
-
blueprints applies to file blueprints
|
|
22422
|
-
|
|
22497
|
+
blueprints applies to file blueprints: beyond data-plane access, a forked or
|
|
22498
|
+
hand-written blueprint can request only a short allowlist of narrow exceptions
|
|
22499
|
+
(profiles:r/u/d, profiles:c:self, a bare profiles:c beside member-lifecycle,
|
|
22500
|
+
scripts:x:<script-name>); every other control-plane scope is rejected.
|
|
22423
22501
|
|
|
22424
22502
|
A blueprint may declare a top-level 'inputs:' block and reference its values
|
|
22425
22503
|
with \${{ inputs.<name> }} (and the built-ins \${{ vectros.context }} /
|
|
@@ -22531,7 +22609,9 @@ ${renderPlan(blueprint, steps)}
|
|
|
22531
22609
|
`);
|
|
22532
22610
|
const declaredIdentities = Object.entries(blueprint.identities ?? {});
|
|
22533
22611
|
if (declaredIdentities.length > 0) {
|
|
22534
|
-
process.stdout.write(
|
|
22612
|
+
process.stdout.write(`
|
|
22613
|
+
${IDENTITIES_TO_ENSURE_HEADING}
|
|
22614
|
+
`);
|
|
22535
22615
|
for (const [name, decl] of declaredIdentities) {
|
|
22536
22616
|
process.stdout.write(` ~ ${name} (${decl.kind}) \u2014 external id '${decl.externalId}'
|
|
22537
22617
|
`);
|
|
@@ -22665,6 +22745,89 @@ ${BLUEPRINT_HELP}
|
|
|
22665
22745
|
// src/program.ts
|
|
22666
22746
|
var import_commander = require("commander");
|
|
22667
22747
|
|
|
22748
|
+
// src/dev-api.ts
|
|
22749
|
+
var DeveloperApiError = class extends Error {
|
|
22750
|
+
statusCode;
|
|
22751
|
+
/** The platform's error body, when it answered with one (its message, request id and error code). */
|
|
22752
|
+
body;
|
|
22753
|
+
constructor(message, statusCode, body) {
|
|
22754
|
+
super(message);
|
|
22755
|
+
this.name = "DeveloperApiError";
|
|
22756
|
+
this.statusCode = statusCode;
|
|
22757
|
+
this.body = body;
|
|
22758
|
+
}
|
|
22759
|
+
};
|
|
22760
|
+
async function fetchDeveloperUsersPage(opts) {
|
|
22761
|
+
const { cognitoToken, apiBaseUrl, tenant, startFrom, limit, fetchImpl = fetch } = opts;
|
|
22762
|
+
const base = apiBaseUrl.replace(/\/$/, "");
|
|
22763
|
+
let url = `${base}/developer/users?tenant=${encodeURIComponent(tenant)}`;
|
|
22764
|
+
if (startFrom) url += `&startFrom=${encodeURIComponent(startFrom)}`;
|
|
22765
|
+
if (limit !== void 0) url += `&limit=${encodeURIComponent(String(limit))}`;
|
|
22766
|
+
let res;
|
|
22767
|
+
try {
|
|
22768
|
+
res = await fetchImpl(url, {
|
|
22769
|
+
method: "GET",
|
|
22770
|
+
headers: { Authorization: `Bearer ${cognitoToken}` }
|
|
22771
|
+
});
|
|
22772
|
+
} catch (err) {
|
|
22773
|
+
throw new DeveloperApiError(
|
|
22774
|
+
`Request to /developer/users failed: ${err instanceof Error ? err.message : String(err)}`
|
|
22775
|
+
);
|
|
22776
|
+
}
|
|
22777
|
+
if (!res.ok) {
|
|
22778
|
+
const body = await res.text().catch(() => "");
|
|
22779
|
+
const hint = res.status === 403 ? " (this command requires an account owner login \u2014 sign in as the owner and retry)" : "";
|
|
22780
|
+
throw new DeveloperApiError(
|
|
22781
|
+
`GET /developer/users failed: HTTP ${res.status}${hint}${body ? ` \u2014 ${body}` : ""}`,
|
|
22782
|
+
res.status
|
|
22783
|
+
);
|
|
22784
|
+
}
|
|
22785
|
+
let parsed;
|
|
22786
|
+
try {
|
|
22787
|
+
parsed = await res.json();
|
|
22788
|
+
} catch {
|
|
22789
|
+
throw new DeveloperApiError("GET /developer/users returned a non-JSON body");
|
|
22790
|
+
}
|
|
22791
|
+
const data = Array.isArray(parsed.data) ? parsed.data : [];
|
|
22792
|
+
const nextCursor = typeof parsed.nextCursor === "string" ? parsed.nextCursor : null;
|
|
22793
|
+
return { data, nextCursor };
|
|
22794
|
+
}
|
|
22795
|
+
async function fetchDeveloperMe(opts) {
|
|
22796
|
+
const { cognitoToken, apiBaseUrl, tenant, fetchImpl = fetch } = opts;
|
|
22797
|
+
const url = `${apiBaseUrl.replace(/\/$/, "")}/developer/me?tenant=${encodeURIComponent(tenant)}`;
|
|
22798
|
+
let res;
|
|
22799
|
+
try {
|
|
22800
|
+
res = await fetchImpl(url, { method: "GET", headers: { Authorization: `Bearer ${cognitoToken}` } });
|
|
22801
|
+
} catch (err) {
|
|
22802
|
+
throw new DeveloperApiError(
|
|
22803
|
+
`Request to /developer/me failed: ${err instanceof Error ? err.message : String(err)}`
|
|
22804
|
+
);
|
|
22805
|
+
}
|
|
22806
|
+
if (res.status === 404) return null;
|
|
22807
|
+
if (!res.ok) {
|
|
22808
|
+
const failure = await devRouteError(res, "GET /developer/me");
|
|
22809
|
+
throw new DeveloperApiError(
|
|
22810
|
+
`GET /developer/me failed: HTTP ${res.status}`,
|
|
22811
|
+
res.status,
|
|
22812
|
+
failure.body
|
|
22813
|
+
);
|
|
22814
|
+
}
|
|
22815
|
+
let parsed;
|
|
22816
|
+
try {
|
|
22817
|
+
parsed = await res.json();
|
|
22818
|
+
} catch {
|
|
22819
|
+
throw new DeveloperApiError("GET /developer/me returned a non-JSON body");
|
|
22820
|
+
}
|
|
22821
|
+
if (typeof parsed.principalId !== "string" || typeof parsed.userId !== "string") {
|
|
22822
|
+
throw new DeveloperApiError("GET /developer/me returned no principal");
|
|
22823
|
+
}
|
|
22824
|
+
return {
|
|
22825
|
+
principalId: parsed.principalId,
|
|
22826
|
+
userId: parsed.userId,
|
|
22827
|
+
tenantId: typeof parsed.tenantId === "string" ? parsed.tenantId : void 0
|
|
22828
|
+
};
|
|
22829
|
+
}
|
|
22830
|
+
|
|
22668
22831
|
// src/self-principal.ts
|
|
22669
22832
|
var PRINCIPAL_PREFIX_USER = "usr_";
|
|
22670
22833
|
function emailFromJwt(token) {
|
|
@@ -22693,6 +22856,37 @@ async function resolveSelfPrincipal(client, cognitoToken) {
|
|
|
22693
22856
|
if (!match?.id) return null;
|
|
22694
22857
|
return { principalId: `${PRINCIPAL_PREFIX_USER}${match.id}`, userId: match.id, email };
|
|
22695
22858
|
}
|
|
22859
|
+
async function resolveSelfPrincipalForTenant(auth, fetchImpl) {
|
|
22860
|
+
const tenant = auth.tenant ?? "live";
|
|
22861
|
+
let me;
|
|
22862
|
+
try {
|
|
22863
|
+
me = await fetchDeveloperMe({
|
|
22864
|
+
cognitoToken: auth.cognitoToken,
|
|
22865
|
+
apiBaseUrl: auth.baseUrl,
|
|
22866
|
+
tenant,
|
|
22867
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
22868
|
+
});
|
|
22869
|
+
} catch (err) {
|
|
22870
|
+
if (err instanceof DeveloperApiError && err.statusCode !== void 0) {
|
|
22871
|
+
if (err.body?.errorCode === "SESSION_REFRESH_REQUIRED") throw new SessionExpiredError(auth.env ?? "production");
|
|
22872
|
+
throw err;
|
|
22873
|
+
}
|
|
22874
|
+
if (tenant === "test") throw err;
|
|
22875
|
+
return resolveSelfPrincipal(auth.client, auth.cognitoToken);
|
|
22876
|
+
}
|
|
22877
|
+
if (me === null) return null;
|
|
22878
|
+
const forThisTenant = Boolean(auth.tenantId && me.tenantId && me.tenantId === auth.tenantId);
|
|
22879
|
+
if (forThisTenant && me.principalId.startsWith(PRINCIPAL_PREFIX_USER)) {
|
|
22880
|
+
return { principalId: me.principalId, userId: me.userId, email: emailFromJwt(auth.cognitoToken) ?? "" };
|
|
22881
|
+
}
|
|
22882
|
+
return tenant === "live" ? resolveSelfPrincipal(auth.client, auth.cognitoToken) : null;
|
|
22883
|
+
}
|
|
22884
|
+
function selfPrincipalUnresolvedMessage(tenant, flag) {
|
|
22885
|
+
if (tenant === "test") {
|
|
22886
|
+
return `Could not resolve your own user id in the test tenant: the platform did not report one for this login. Find it with \`vectros identity list-all --tenant test\` (an owner login) and pass ${flag} usr_<id> explicitly.`;
|
|
22887
|
+
}
|
|
22888
|
+
return `Could not resolve your own user id from your login. Pass ${flag} usr_<id> explicitly (find the id with \`vectros identity list --type user\`).`;
|
|
22889
|
+
}
|
|
22696
22890
|
|
|
22697
22891
|
// src/commands/whoami.ts
|
|
22698
22892
|
async function runWhoami(options, deps = {}) {
|
|
@@ -22700,7 +22894,10 @@ async function runWhoami(options, deps = {}) {
|
|
|
22700
22894
|
const { identity, baseUrl } = auth;
|
|
22701
22895
|
let self2 = null;
|
|
22702
22896
|
try {
|
|
22703
|
-
self2 = await
|
|
22897
|
+
self2 = await resolveSelfPrincipalForTenant(
|
|
22898
|
+
{ ...auth, client: auth.client },
|
|
22899
|
+
deps.fetchImpl
|
|
22900
|
+
);
|
|
22704
22901
|
} catch {
|
|
22705
22902
|
self2 = null;
|
|
22706
22903
|
}
|
|
@@ -23106,51 +23303,6 @@ ${list}
|
|
|
23106
23303
|
});
|
|
23107
23304
|
}
|
|
23108
23305
|
|
|
23109
|
-
// src/dev-api.ts
|
|
23110
|
-
var DeveloperApiError = class extends Error {
|
|
23111
|
-
statusCode;
|
|
23112
|
-
constructor(message, statusCode) {
|
|
23113
|
-
super(message);
|
|
23114
|
-
this.name = "DeveloperApiError";
|
|
23115
|
-
this.statusCode = statusCode;
|
|
23116
|
-
}
|
|
23117
|
-
};
|
|
23118
|
-
async function fetchDeveloperUsersPage(opts) {
|
|
23119
|
-
const { cognitoToken, apiBaseUrl, tenant, startFrom, limit, fetchImpl = fetch } = opts;
|
|
23120
|
-
const base = apiBaseUrl.replace(/\/$/, "");
|
|
23121
|
-
let url = `${base}/developer/users?tenant=${encodeURIComponent(tenant)}`;
|
|
23122
|
-
if (startFrom) url += `&startFrom=${encodeURIComponent(startFrom)}`;
|
|
23123
|
-
if (limit !== void 0) url += `&limit=${encodeURIComponent(String(limit))}`;
|
|
23124
|
-
let res;
|
|
23125
|
-
try {
|
|
23126
|
-
res = await fetchImpl(url, {
|
|
23127
|
-
method: "GET",
|
|
23128
|
-
headers: { Authorization: `Bearer ${cognitoToken}` }
|
|
23129
|
-
});
|
|
23130
|
-
} catch (err) {
|
|
23131
|
-
throw new DeveloperApiError(
|
|
23132
|
-
`Request to /developer/users failed: ${err instanceof Error ? err.message : String(err)}`
|
|
23133
|
-
);
|
|
23134
|
-
}
|
|
23135
|
-
if (!res.ok) {
|
|
23136
|
-
const body = await res.text().catch(() => "");
|
|
23137
|
-
const hint = res.status === 403 ? " (this command requires an account owner login \u2014 sign in as the owner and retry)" : "";
|
|
23138
|
-
throw new DeveloperApiError(
|
|
23139
|
-
`GET /developer/users failed: HTTP ${res.status}${hint}${body ? ` \u2014 ${body}` : ""}`,
|
|
23140
|
-
res.status
|
|
23141
|
-
);
|
|
23142
|
-
}
|
|
23143
|
-
let parsed;
|
|
23144
|
-
try {
|
|
23145
|
-
parsed = await res.json();
|
|
23146
|
-
} catch {
|
|
23147
|
-
throw new DeveloperApiError("GET /developer/users returned a non-JSON body");
|
|
23148
|
-
}
|
|
23149
|
-
const data = Array.isArray(parsed.data) ? parsed.data : [];
|
|
23150
|
-
const nextCursor = typeof parsed.nextCursor === "string" ? parsed.nextCursor : null;
|
|
23151
|
-
return { data, nextCursor };
|
|
23152
|
-
}
|
|
23153
|
-
|
|
23154
23306
|
// src/commands/identity.ts
|
|
23155
23307
|
function isUserDimension(type) {
|
|
23156
23308
|
return type === "user";
|
|
@@ -23446,14 +23598,13 @@ function assertPrincipalShape(principal) {
|
|
|
23446
23598
|
}
|
|
23447
23599
|
}
|
|
23448
23600
|
var SELF_PRINCIPAL_ALIAS = "me";
|
|
23449
|
-
async function resolvePrincipalArg(raw, auth) {
|
|
23601
|
+
async function resolvePrincipalArg(raw, auth, deps) {
|
|
23450
23602
|
if (raw !== SELF_PRINCIPAL_ALIAS) return raw;
|
|
23451
|
-
const self2 = await
|
|
23452
|
-
|
|
23453
|
-
|
|
23454
|
-
|
|
23455
|
-
|
|
23456
|
-
}
|
|
23603
|
+
const self2 = await resolveSelfPrincipalForTenant(
|
|
23604
|
+
{ ...auth, client: auth.client },
|
|
23605
|
+
deps.fetchImpl
|
|
23606
|
+
);
|
|
23607
|
+
if (!self2) throw new Error(selfPrincipalUnresolvedMessage(auth.tenant, "--principal"));
|
|
23457
23608
|
return self2.principalId;
|
|
23458
23609
|
}
|
|
23459
23610
|
function roleSummary(p) {
|
|
@@ -23506,7 +23657,7 @@ async function runAccessGrant(opts, deps = {}) {
|
|
|
23506
23657
|
return withAuth(opts, deps, async (auth) => {
|
|
23507
23658
|
const client = auth.client;
|
|
23508
23659
|
try {
|
|
23509
|
-
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
23660
|
+
const principal = await resolvePrincipalArg(opts.principal, auth, deps);
|
|
23510
23661
|
const scopes = hasActions ? [{ allowed_actions: splitCsv(opts.actions) }] : void 0;
|
|
23511
23662
|
const profile = await client.auth.createAccessProfile({
|
|
23512
23663
|
contextId: opts.context,
|
|
@@ -23516,8 +23667,10 @@ async function runAccessGrant(opts, deps = {}) {
|
|
|
23516
23667
|
// Always send the plural `roleIds`, even for a single
|
|
23517
23668
|
// `--role`: it is a strict superset of the deprecated singular
|
|
23518
23669
|
// `roleId` (equivalent to `roleIds: ["<value>"]`), and the platform
|
|
23519
|
-
// reads it uniformly regardless of how many roles compose.
|
|
23520
|
-
|
|
23670
|
+
// reads it uniformly regardless of how many roles compose. With
|
|
23671
|
+
// `--actions` there is no role: omit `roleIds` (the platform refuses an
|
|
23672
|
+
// empty list next to inline `scopes`).
|
|
23673
|
+
roleIds: hasRole ? opts.role : void 0,
|
|
23521
23674
|
scopes,
|
|
23522
23675
|
identityOverrides
|
|
23523
23676
|
}
|
|
@@ -23554,7 +23707,7 @@ async function runAccessRevoke(opts, deps = {}) {
|
|
|
23554
23707
|
return withAuth(opts, deps, async (auth) => {
|
|
23555
23708
|
const client = auth.client;
|
|
23556
23709
|
try {
|
|
23557
|
-
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
23710
|
+
const principal = await resolvePrincipalArg(opts.principal, auth, deps);
|
|
23558
23711
|
await client.auth.deleteAccessProfile({ contextId: opts.context, principalId: principal });
|
|
23559
23712
|
process.stdout.write(` \u2714 Revoked ${principal} from '${opts.context}'
|
|
23560
23713
|
`);
|
|
@@ -23583,7 +23736,7 @@ async function runAccessList(opts, deps = {}) {
|
|
|
23583
23736
|
return withAuth(opts, deps, async (auth) => {
|
|
23584
23737
|
const client = auth.client;
|
|
23585
23738
|
try {
|
|
23586
|
-
const principal = opts.principal ? await resolvePrincipalArg(opts.principal, auth) : void 0;
|
|
23739
|
+
const principal = opts.principal ? await resolvePrincipalArg(opts.principal, auth, deps) : void 0;
|
|
23587
23740
|
const rows = opts.context ? await drainPages(
|
|
23588
23741
|
(startFrom) => client.auth.listAccessProfiles({ contextId: opts.context, startFrom, limit: PAGE_LIMIT })
|
|
23589
23742
|
) : await drainPages(
|
|
@@ -23623,7 +23776,7 @@ async function runAccessGet(opts, deps = {}) {
|
|
|
23623
23776
|
return withAuth(opts, deps, async (auth) => {
|
|
23624
23777
|
const client = auth.client;
|
|
23625
23778
|
try {
|
|
23626
|
-
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
23779
|
+
const principal = await resolvePrincipalArg(opts.principal, auth, deps);
|
|
23627
23780
|
const p = await client.auth.getAccessProfile({ contextId: opts.context, principalId: principal });
|
|
23628
23781
|
if (opts.json) {
|
|
23629
23782
|
process.stdout.write(`${formatJson(p)}
|
|
@@ -23845,11 +23998,13 @@ async function runAccessExplain(opts, deps = {}) {
|
|
|
23845
23998
|
try {
|
|
23846
23999
|
let principal = opts.principal;
|
|
23847
24000
|
if (principal === SELF_PRINCIPAL_ALIAS2) {
|
|
23848
|
-
const self2 = await
|
|
24001
|
+
const self2 = await resolveSelfPrincipalForTenant(
|
|
24002
|
+
{ ...auth, client: auth.client },
|
|
24003
|
+
deps.fetchImpl
|
|
24004
|
+
);
|
|
23849
24005
|
if (!self2) {
|
|
23850
|
-
process.stderr.write(
|
|
23851
|
-
|
|
23852
|
-
);
|
|
24006
|
+
process.stderr.write(` \u2716 ${selfPrincipalUnresolvedMessage(auth.tenant, "--principal")}
|
|
24007
|
+
`);
|
|
23853
24008
|
return 1;
|
|
23854
24009
|
}
|
|
23855
24010
|
principal = self2.principalId;
|
|
@@ -24228,10 +24383,17 @@ async function runContextDestroy(opts, deps = {}) {
|
|
|
24228
24383
|
`);
|
|
24229
24384
|
return 0;
|
|
24230
24385
|
}
|
|
24386
|
+
const tenantFlag = auth.tenant === "test" ? " --tenant test" : "";
|
|
24231
24387
|
process.stdout.write(
|
|
24232
24388
|
`
|
|
24233
24389
|
\u2714 teardown of '${contextId}' started \u2014 the context and its contents are being
|
|
24234
24390
|
deleted asynchronously (it reports status 'purging' until the drain completes)
|
|
24391
|
+
|
|
24392
|
+
Not removed: a service principal a blueprint bootstrap created for this context (the
|
|
24393
|
+
externalId in the blueprint's servicePrincipal) belongs to the whole tenant, so it stays. To clean it up
|
|
24394
|
+
when it exists and nothing else uses it:
|
|
24395
|
+
vectros identity list-all${tenantFlag}
|
|
24396
|
+
vectros identity delete --type user --id <id>${tenantFlag}
|
|
24235
24397
|
`
|
|
24236
24398
|
);
|
|
24237
24399
|
return 0;
|
|
@@ -24308,12 +24470,12 @@ function parseRestrictedToDomain(raw) {
|
|
|
24308
24470
|
const domain = raw.trim();
|
|
24309
24471
|
if (domain.length === 0) {
|
|
24310
24472
|
throw new Error(
|
|
24311
|
-
"--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."
|
|
24473
|
+
"--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)."
|
|
24312
24474
|
);
|
|
24313
24475
|
}
|
|
24314
24476
|
if (!isRestrictedToDomainName(domain.toLowerCase())) {
|
|
24315
24477
|
throw new Error(
|
|
24316
|
-
`--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.`
|
|
24478
|
+
`--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).`
|
|
24317
24479
|
);
|
|
24318
24480
|
}
|
|
24319
24481
|
return domain;
|
|
@@ -24766,30 +24928,555 @@ ${renderIssuer(issuer)}
|
|
|
24766
24928
|
});
|
|
24767
24929
|
}
|
|
24768
24930
|
|
|
24769
|
-
// src/commands/
|
|
24770
|
-
|
|
24771
|
-
|
|
24772
|
-
|
|
24773
|
-
|
|
24774
|
-
|
|
24775
|
-
|
|
24776
|
-
|
|
24777
|
-
|
|
24778
|
-
|
|
24779
|
-
|
|
24931
|
+
// src/commands/provider-config.ts
|
|
24932
|
+
var import_node_fs5 = require("fs");
|
|
24933
|
+
var readline6 = __toESM(require("readline/promises"));
|
|
24934
|
+
var PROVIDER_TYPES = ["ANTHROPIC_NATIVE", "OPENAI_COMPATIBLE"];
|
|
24935
|
+
var BILLING_MODES = ["CUSTOMER_DIRECT"];
|
|
24936
|
+
var STATUSES = ["ACTIVE", "DISABLED"];
|
|
24937
|
+
var MAX_SECRET_BYTES = 8 * 1024;
|
|
24938
|
+
async function readBoundedSecret(stream) {
|
|
24939
|
+
try {
|
|
24940
|
+
return await readAllFrom(stream, MAX_SECRET_BYTES);
|
|
24941
|
+
} catch (err) {
|
|
24942
|
+
if (err instanceof TokenTooLargeError) {
|
|
24943
|
+
throw new Error(
|
|
24944
|
+
`the input is larger than ${MAX_SECRET_BYTES / 1024} KB, which no API key is; nothing was read further and nothing was sent.`
|
|
24945
|
+
);
|
|
24946
|
+
}
|
|
24947
|
+
throw err;
|
|
24948
|
+
}
|
|
24780
24949
|
}
|
|
24781
|
-
|
|
24782
|
-
|
|
24783
|
-
|
|
24784
|
-
|
|
24785
|
-
|
|
24786
|
-
|
|
24787
|
-
|
|
24788
|
-
|
|
24789
|
-
|
|
24790
|
-
|
|
24950
|
+
function fileFailureReason2(err) {
|
|
24951
|
+
switch (err?.code) {
|
|
24952
|
+
case "ENOENT":
|
|
24953
|
+
return "no such file.";
|
|
24954
|
+
case "EISDIR":
|
|
24955
|
+
return "that is a directory.";
|
|
24956
|
+
case "EACCES":
|
|
24957
|
+
case "EPERM":
|
|
24958
|
+
return "permission denied.";
|
|
24959
|
+
default:
|
|
24960
|
+
return "it could not be read.";
|
|
24961
|
+
}
|
|
24962
|
+
}
|
|
24963
|
+
async function resolveApiKeySecret(fileFlag, deps, mode) {
|
|
24964
|
+
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
|
|
24965
|
+
const readStdin = deps.readStdin ?? (() => readBoundedSecret(process.stdin));
|
|
24966
|
+
const readTextFile = deps.readTextFile ?? ((path4) => readBoundedSecret((0, import_node_fs5.createReadStream)(path4)));
|
|
24967
|
+
let raw;
|
|
24968
|
+
if (fileFlag !== void 0 && fileFlag !== "-") {
|
|
24969
|
+
if (looksLikeCredential(fileFlag)) {
|
|
24970
|
+
throw new Error(
|
|
24971
|
+
"--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 -."
|
|
24791
24972
|
);
|
|
24792
|
-
|
|
24973
|
+
}
|
|
24974
|
+
try {
|
|
24975
|
+
raw = await readTextFile(fileFlag);
|
|
24976
|
+
} catch (err) {
|
|
24977
|
+
const name = safeBaseName(fileFlag);
|
|
24978
|
+
throw new Error(
|
|
24979
|
+
`could not read the file given to --api-key-secret-file${name ? ` (${name})` : ""}: ${fileFailureReason2(err)}`
|
|
24980
|
+
);
|
|
24981
|
+
}
|
|
24982
|
+
} else if (fileFlag === "-") {
|
|
24983
|
+
if (isTty) {
|
|
24984
|
+
throw new Error(
|
|
24985
|
+
"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>."
|
|
24986
|
+
);
|
|
24987
|
+
}
|
|
24988
|
+
raw = await readStdin();
|
|
24989
|
+
} else if (mode === "required" && !isTty) {
|
|
24990
|
+
process.stderr.write(" \u2026 no --api-key-secret-file given: reading the secret from stdin.\n");
|
|
24991
|
+
raw = await readStdin();
|
|
24992
|
+
} else if (mode === "required") {
|
|
24993
|
+
throw new Error(
|
|
24994
|
+
"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)."
|
|
24995
|
+
);
|
|
24996
|
+
} else {
|
|
24997
|
+
return void 0;
|
|
24998
|
+
}
|
|
24999
|
+
const secret = raw.trim();
|
|
25000
|
+
if (secret.length === 0) throw new Error("the secret is empty.");
|
|
25001
|
+
return secret;
|
|
25002
|
+
}
|
|
25003
|
+
function renderProviderConfig(c) {
|
|
25004
|
+
return formatKeyValues([
|
|
25005
|
+
["id", c.id],
|
|
25006
|
+
["tenantId", c.tenantId],
|
|
25007
|
+
["alias", c.alias],
|
|
25008
|
+
["providerType", c.providerType],
|
|
25009
|
+
["endpoint", c.endpoint],
|
|
25010
|
+
["declaredProcessingRegion", c.declaredProcessingRegion],
|
|
25011
|
+
["defaultProviderModelId", c.defaultProviderModelId],
|
|
25012
|
+
["waiverType", c.waiverType],
|
|
25013
|
+
["billingMode", c.billingMode],
|
|
25014
|
+
["status", c.status],
|
|
25015
|
+
["createdAt", c.createdAt]
|
|
25016
|
+
]);
|
|
25017
|
+
}
|
|
25018
|
+
function billingModeError(value) {
|
|
25019
|
+
if (value === void 0 || BILLING_MODES.includes(value)) return void 0;
|
|
25020
|
+
if (value === "PLATFORM_BILLED") {
|
|
25021
|
+
return "--billing-mode PLATFORM_BILLED is retired: a provider you configure bills you directly, so CUSTOMER_DIRECT is the only billing mode.";
|
|
25022
|
+
}
|
|
25023
|
+
return invalidEnum("--billing-mode", value, BILLING_MODES);
|
|
25024
|
+
}
|
|
25025
|
+
function invalidEnum(flag, value, allowed) {
|
|
25026
|
+
return `${flag} must be one of ${allowed.join(", ")} (got '${value}').`;
|
|
25027
|
+
}
|
|
25028
|
+
var NOT_FOUND_HINT = "'vectros provider-config list --tenant <test|live>' enumerates configs in a tenant.";
|
|
25029
|
+
async function runProviderConfigCreate(opts, deps = {}) {
|
|
25030
|
+
if (!PROVIDER_TYPES.includes(opts.providerType)) {
|
|
25031
|
+
process.stderr.write(` \u2716 ${invalidEnum("--provider-type", opts.providerType, PROVIDER_TYPES)}
|
|
25032
|
+
`);
|
|
25033
|
+
return 2;
|
|
25034
|
+
}
|
|
25035
|
+
const billingModeProblem = billingModeError(opts.billingMode);
|
|
25036
|
+
if (billingModeProblem !== void 0) {
|
|
25037
|
+
process.stderr.write(` \u2716 ${billingModeProblem}
|
|
25038
|
+
`);
|
|
25039
|
+
return 2;
|
|
25040
|
+
}
|
|
25041
|
+
if (opts.status !== void 0 && !STATUSES.includes(opts.status)) {
|
|
25042
|
+
process.stderr.write(` \u2716 ${invalidEnum("--status", opts.status, STATUSES)}
|
|
25043
|
+
`);
|
|
25044
|
+
return 2;
|
|
25045
|
+
}
|
|
25046
|
+
let apiKeySecret;
|
|
25047
|
+
try {
|
|
25048
|
+
apiKeySecret = await resolveApiKeySecret(opts.apiKeySecretFile, deps, "required");
|
|
25049
|
+
} catch (err) {
|
|
25050
|
+
process.stderr.write(` \u2716 ${formatError(err)}
|
|
25051
|
+
`);
|
|
25052
|
+
return 2;
|
|
25053
|
+
}
|
|
25054
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25055
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25056
|
+
try {
|
|
25057
|
+
const tenantId = requireTenantId(auth);
|
|
25058
|
+
const res = await fetchImpl(`${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs`, {
|
|
25059
|
+
method: "POST",
|
|
25060
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}`, "Content-Type": "application/json" },
|
|
25061
|
+
body: JSON.stringify({
|
|
25062
|
+
tenantId,
|
|
25063
|
+
alias: opts.alias,
|
|
25064
|
+
providerType: opts.providerType,
|
|
25065
|
+
endpoint: opts.endpoint,
|
|
25066
|
+
declaredProcessingRegion: opts.declaredProcessingRegion,
|
|
25067
|
+
defaultProviderModelId: opts.defaultProviderModelId,
|
|
25068
|
+
apiKeySecret,
|
|
25069
|
+
waiverType: opts.waiverType,
|
|
25070
|
+
billingMode: opts.billingMode ?? "CUSTOMER_DIRECT",
|
|
25071
|
+
status: opts.status
|
|
25072
|
+
})
|
|
25073
|
+
});
|
|
25074
|
+
if (!res.ok) throw await devRouteError(res, "provider-config create");
|
|
25075
|
+
const config = await res.json();
|
|
25076
|
+
if (opts.json) {
|
|
25077
|
+
process.stdout.write(`${formatJson(config)}
|
|
25078
|
+
`);
|
|
25079
|
+
return 0;
|
|
25080
|
+
}
|
|
25081
|
+
process.stdout.write(`
|
|
25082
|
+
\u2714 provider config '${opts.alias}' created
|
|
25083
|
+
${renderProviderConfig(config)}
|
|
25084
|
+
`);
|
|
25085
|
+
return 0;
|
|
25086
|
+
} catch (err) {
|
|
25087
|
+
process.stderr.write(formatCliError(err, { action: "provider-config create failed" }));
|
|
25088
|
+
return 1;
|
|
25089
|
+
}
|
|
25090
|
+
});
|
|
25091
|
+
}
|
|
25092
|
+
async function runProviderConfigList(opts, deps = {}) {
|
|
25093
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25094
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25095
|
+
try {
|
|
25096
|
+
const tenantId = requireTenantId(auth);
|
|
25097
|
+
const url = `${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs?tenantId=${encodeURIComponent(tenantId)}`;
|
|
25098
|
+
const res = await fetchImpl(url, { method: "GET", headers: { Authorization: `Bearer ${auth.cognitoToken}` } });
|
|
25099
|
+
if (!res.ok) throw await devRouteError(res, "provider-config list");
|
|
25100
|
+
const rows = await res.json();
|
|
25101
|
+
if (opts.json) {
|
|
25102
|
+
process.stdout.write(`${formatJson(rows)}
|
|
25103
|
+
`);
|
|
25104
|
+
return 0;
|
|
25105
|
+
}
|
|
25106
|
+
process.stdout.write(
|
|
25107
|
+
"\n" + formatTable(rows, [
|
|
25108
|
+
{ header: "id", get: (c) => c.id },
|
|
25109
|
+
{ header: "alias", get: (c) => c.alias },
|
|
25110
|
+
{ header: "provider type", get: (c) => c.providerType },
|
|
25111
|
+
{ header: "billing mode", get: (c) => c.billingMode },
|
|
25112
|
+
{ header: "status", get: (c) => c.status }
|
|
25113
|
+
]) + "\n"
|
|
25114
|
+
);
|
|
25115
|
+
return 0;
|
|
25116
|
+
} catch (err) {
|
|
25117
|
+
process.stderr.write(formatCliError(err, { action: "provider-config list failed" }));
|
|
25118
|
+
return 1;
|
|
25119
|
+
}
|
|
25120
|
+
});
|
|
25121
|
+
}
|
|
25122
|
+
async function runProviderConfigGet(opts, deps = {}) {
|
|
25123
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25124
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25125
|
+
try {
|
|
25126
|
+
const url = `${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs/${encodeURIComponent(opts.id)}`;
|
|
25127
|
+
const res = await fetchImpl(url, { method: "GET", headers: { Authorization: `Bearer ${auth.cognitoToken}` } });
|
|
25128
|
+
if (!res.ok) throw await devRouteError(res, "provider-config get");
|
|
25129
|
+
const config = await res.json();
|
|
25130
|
+
if (opts.json) {
|
|
25131
|
+
process.stdout.write(`${formatJson(config)}
|
|
25132
|
+
`);
|
|
25133
|
+
return 0;
|
|
25134
|
+
}
|
|
25135
|
+
process.stdout.write(`
|
|
25136
|
+
${renderProviderConfig(config)}
|
|
25137
|
+
`);
|
|
25138
|
+
return 0;
|
|
25139
|
+
} catch (err) {
|
|
25140
|
+
process.stderr.write(formatCliError(err, { action: "provider-config get failed", notFoundHint: NOT_FOUND_HINT }));
|
|
25141
|
+
return 1;
|
|
25142
|
+
}
|
|
25143
|
+
});
|
|
25144
|
+
}
|
|
25145
|
+
async function runProviderConfigUpdate(opts, deps = {}) {
|
|
25146
|
+
if (opts.providerType !== void 0 && !PROVIDER_TYPES.includes(opts.providerType)) {
|
|
25147
|
+
process.stderr.write(` \u2716 ${invalidEnum("--provider-type", opts.providerType, PROVIDER_TYPES)}
|
|
25148
|
+
`);
|
|
25149
|
+
return 2;
|
|
25150
|
+
}
|
|
25151
|
+
const billingModeProblem = billingModeError(opts.billingMode);
|
|
25152
|
+
if (billingModeProblem !== void 0) {
|
|
25153
|
+
process.stderr.write(` \u2716 ${billingModeProblem}
|
|
25154
|
+
`);
|
|
25155
|
+
return 2;
|
|
25156
|
+
}
|
|
25157
|
+
if (opts.status !== void 0 && !STATUSES.includes(opts.status)) {
|
|
25158
|
+
process.stderr.write(` \u2716 ${invalidEnum("--status", opts.status, STATUSES)}
|
|
25159
|
+
`);
|
|
25160
|
+
return 2;
|
|
25161
|
+
}
|
|
25162
|
+
let apiKeySecret;
|
|
25163
|
+
try {
|
|
25164
|
+
apiKeySecret = await resolveApiKeySecret(opts.apiKeySecretFile, deps, "optional");
|
|
25165
|
+
} catch (err) {
|
|
25166
|
+
process.stderr.write(` \u2716 ${formatError(err)}
|
|
25167
|
+
`);
|
|
25168
|
+
return 2;
|
|
25169
|
+
}
|
|
25170
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25171
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25172
|
+
try {
|
|
25173
|
+
const url = `${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs/${encodeURIComponent(opts.id)}`;
|
|
25174
|
+
const res = await fetchImpl(url, {
|
|
25175
|
+
method: "PUT",
|
|
25176
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}`, "Content-Type": "application/json" },
|
|
25177
|
+
body: JSON.stringify({
|
|
25178
|
+
alias: opts.alias,
|
|
25179
|
+
providerType: opts.providerType,
|
|
25180
|
+
endpoint: opts.endpoint,
|
|
25181
|
+
declaredProcessingRegion: opts.declaredProcessingRegion,
|
|
25182
|
+
defaultProviderModelId: opts.defaultProviderModelId,
|
|
25183
|
+
apiKeySecret,
|
|
25184
|
+
waiverType: opts.waiverType,
|
|
25185
|
+
billingMode: opts.billingMode,
|
|
25186
|
+
status: opts.status
|
|
25187
|
+
})
|
|
25188
|
+
});
|
|
25189
|
+
if (!res.ok) throw await devRouteError(res, "provider-config update");
|
|
25190
|
+
const config = await res.json();
|
|
25191
|
+
if (opts.json) {
|
|
25192
|
+
process.stdout.write(`${formatJson(config)}
|
|
25193
|
+
`);
|
|
25194
|
+
return 0;
|
|
25195
|
+
}
|
|
25196
|
+
process.stdout.write(`
|
|
25197
|
+
\u2714 provider config '${opts.id}' updated
|
|
25198
|
+
${renderProviderConfig(config)}
|
|
25199
|
+
`);
|
|
25200
|
+
return 0;
|
|
25201
|
+
} catch (err) {
|
|
25202
|
+
process.stderr.write(formatCliError(err, { action: "provider-config update failed", notFoundHint: NOT_FOUND_HINT }));
|
|
25203
|
+
return 1;
|
|
25204
|
+
}
|
|
25205
|
+
});
|
|
25206
|
+
}
|
|
25207
|
+
async function runProviderConfigDelete(opts, deps = {}) {
|
|
25208
|
+
const { id } = opts;
|
|
25209
|
+
if (!opts.force) {
|
|
25210
|
+
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
|
|
25211
|
+
if (!isTty) {
|
|
25212
|
+
process.stderr.write(
|
|
25213
|
+
` \u2716 provider-config delete needs an interactive confirmation.
|
|
25214
|
+
Re-run with --force to delete '${id}' without a prompt.
|
|
25215
|
+
`
|
|
25216
|
+
);
|
|
25217
|
+
return 2;
|
|
25218
|
+
}
|
|
25219
|
+
const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
|
|
25220
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
25221
|
+
return { prompt: (q) => rl.question(q), close: () => rl.close() };
|
|
25222
|
+
})();
|
|
25223
|
+
try {
|
|
25224
|
+
process.stdout.write(
|
|
25225
|
+
`
|
|
25226
|
+
\u26A0 Deleting provider config '${id}' removes it permanently. Any inference call that resolves
|
|
25227
|
+
a provider by this config's alias starts failing immediately. To disable it instead (reversible),
|
|
25228
|
+
run \`vectros provider-config update ${id} --status DISABLED\`.
|
|
25229
|
+
|
|
25230
|
+
`
|
|
25231
|
+
);
|
|
25232
|
+
const typed = await promptCtx.prompt(` Type the config id ('${id}') to confirm: `);
|
|
25233
|
+
if (typed.trim() !== id) {
|
|
25234
|
+
process.stderr.write(` \u2716 Confirmation did not match \u2014 nothing deleted.
|
|
25235
|
+
`);
|
|
25236
|
+
return 1;
|
|
25237
|
+
}
|
|
25238
|
+
} finally {
|
|
25239
|
+
promptCtx.close();
|
|
25240
|
+
}
|
|
25241
|
+
}
|
|
25242
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25243
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25244
|
+
try {
|
|
25245
|
+
const url = `${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs/${encodeURIComponent(id)}`;
|
|
25246
|
+
const res = await fetchImpl(url, { method: "DELETE", headers: { Authorization: `Bearer ${auth.cognitoToken}` } });
|
|
25247
|
+
if (!res.ok) throw await devRouteError(res, "provider-config delete");
|
|
25248
|
+
if (opts.json) {
|
|
25249
|
+
process.stdout.write(`${formatJson({ id, deleted: true })}
|
|
25250
|
+
`);
|
|
25251
|
+
return 0;
|
|
25252
|
+
}
|
|
25253
|
+
process.stdout.write(`
|
|
25254
|
+
\u2714 provider config '${id}' deleted
|
|
25255
|
+
`);
|
|
25256
|
+
return 0;
|
|
25257
|
+
} catch (err) {
|
|
25258
|
+
process.stderr.write(formatCliError(err, { action: "provider-config delete failed", notFoundHint: NOT_FOUND_HINT }));
|
|
25259
|
+
return 1;
|
|
25260
|
+
}
|
|
25261
|
+
});
|
|
25262
|
+
}
|
|
25263
|
+
|
|
25264
|
+
// src/commands/domains.ts
|
|
25265
|
+
var readline7 = __toESM(require("readline/promises"));
|
|
25266
|
+
function renderDomainVerifyResult(d) {
|
|
25267
|
+
return formatKeyValues([
|
|
25268
|
+
["id", d.id],
|
|
25269
|
+
["domain", d.domain],
|
|
25270
|
+
["status", d.status]
|
|
25271
|
+
]);
|
|
25272
|
+
}
|
|
25273
|
+
var NOT_FOUND_HINT2 = "'vectros domains list' enumerates your registered domains.";
|
|
25274
|
+
async function runDomainsRegister(opts, deps = {}) {
|
|
25275
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25276
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25277
|
+
try {
|
|
25278
|
+
const res = await fetchImpl(`${auth.baseUrl.replace(/\/$/, "")}/developer/domains`, {
|
|
25279
|
+
method: "POST",
|
|
25280
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}`, "Content-Type": "application/json" },
|
|
25281
|
+
body: JSON.stringify({ domain: opts.domain })
|
|
25282
|
+
});
|
|
25283
|
+
if (res.status === 409) {
|
|
25284
|
+
const raw = await res.text().catch(() => "");
|
|
25285
|
+
let message = "Domain already registered";
|
|
25286
|
+
let existingId;
|
|
25287
|
+
let existingStatus;
|
|
25288
|
+
try {
|
|
25289
|
+
const parsed = JSON.parse(raw);
|
|
25290
|
+
if (typeof parsed.message === "string" && parsed.message.trim()) message = parsed.message.trim();
|
|
25291
|
+
if (typeof parsed.id === "string") existingId = parsed.id;
|
|
25292
|
+
if (typeof parsed.status === "string") existingStatus = parsed.status;
|
|
25293
|
+
} catch {
|
|
25294
|
+
}
|
|
25295
|
+
if (opts.json) {
|
|
25296
|
+
process.stdout.write(`${formatJson({ message, id: existingId, status: existingStatus })}
|
|
25297
|
+
`);
|
|
25298
|
+
return 1;
|
|
25299
|
+
}
|
|
25300
|
+
process.stderr.write(
|
|
25301
|
+
` \u2716 ${message}${existingId ? ` (id: ${existingId}${existingStatus ? `, status: ${existingStatus}` : ""})` : ""}
|
|
25302
|
+
` + (existingStatus === "PENDING" && existingId ? ` Still pending verification. Run: vectros domains verify ${existingId}
|
|
25303
|
+
` : " Run `vectros domains list` to find it.\n")
|
|
25304
|
+
);
|
|
25305
|
+
return 1;
|
|
25306
|
+
}
|
|
25307
|
+
if (!res.ok) throw await devRouteError(res, "domains register");
|
|
25308
|
+
const result = await res.json();
|
|
25309
|
+
if (opts.json) {
|
|
25310
|
+
process.stdout.write(`${formatJson(result)}
|
|
25311
|
+
`);
|
|
25312
|
+
return 0;
|
|
25313
|
+
}
|
|
25314
|
+
process.stdout.write(
|
|
25315
|
+
`
|
|
25316
|
+
\u2714 domain '${result.domain ?? opts.domain}' registered (id: ${result.id ?? "?"}, status: ${result.status ?? "PENDING"})
|
|
25317
|
+
|
|
25318
|
+
${result.instructions ?? `Add a DNS TXT record: _vectros-verify.${result.domain ?? opts.domain} \u2192 "${result.verificationToken ?? ""}"`}
|
|
25319
|
+
|
|
25320
|
+
Once the record is published, run:
|
|
25321
|
+
vectros domains verify ${result.id ?? "<id>"}
|
|
25322
|
+
`
|
|
25323
|
+
);
|
|
25324
|
+
return 0;
|
|
25325
|
+
} catch (err) {
|
|
25326
|
+
process.stderr.write(formatCliError(err, { action: "domains register failed" }));
|
|
25327
|
+
return 1;
|
|
25328
|
+
}
|
|
25329
|
+
});
|
|
25330
|
+
}
|
|
25331
|
+
async function runDomainsList(opts, deps = {}) {
|
|
25332
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25333
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25334
|
+
try {
|
|
25335
|
+
const res = await fetchImpl(`${auth.baseUrl.replace(/\/$/, "")}/developer/domains`, {
|
|
25336
|
+
method: "GET",
|
|
25337
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}` }
|
|
25338
|
+
});
|
|
25339
|
+
if (!res.ok) throw await devRouteError(res, "domains list");
|
|
25340
|
+
const rows = await res.json();
|
|
25341
|
+
if (opts.json) {
|
|
25342
|
+
process.stdout.write(`${formatJson(rows)}
|
|
25343
|
+
`);
|
|
25344
|
+
return 0;
|
|
25345
|
+
}
|
|
25346
|
+
process.stdout.write(
|
|
25347
|
+
"\n" + formatTable(rows, [
|
|
25348
|
+
{ header: "id", get: (d) => d.id },
|
|
25349
|
+
{ header: "domain", get: (d) => d.domain },
|
|
25350
|
+
{ header: "status", get: (d) => d.status },
|
|
25351
|
+
{ header: "verified at", get: (d) => d.verifiedAt }
|
|
25352
|
+
]) + "\n"
|
|
25353
|
+
);
|
|
25354
|
+
return 0;
|
|
25355
|
+
} catch (err) {
|
|
25356
|
+
process.stderr.write(formatCliError(err, { action: "domains list failed" }));
|
|
25357
|
+
return 1;
|
|
25358
|
+
}
|
|
25359
|
+
});
|
|
25360
|
+
}
|
|
25361
|
+
async function runDomainsDelete(opts, deps = {}) {
|
|
25362
|
+
const { id } = opts;
|
|
25363
|
+
if (!opts.force) {
|
|
25364
|
+
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
|
|
25365
|
+
if (!isTty) {
|
|
25366
|
+
process.stderr.write(
|
|
25367
|
+
` \u2716 domains delete needs an interactive confirmation.
|
|
25368
|
+
Re-run with --force to delete '${id}' without a prompt.
|
|
25369
|
+
`
|
|
25370
|
+
);
|
|
25371
|
+
return 2;
|
|
25372
|
+
}
|
|
25373
|
+
const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
|
|
25374
|
+
const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
|
|
25375
|
+
return { prompt: (q) => rl.question(q), close: () => rl.close() };
|
|
25376
|
+
})();
|
|
25377
|
+
try {
|
|
25378
|
+
process.stdout.write(
|
|
25379
|
+
`
|
|
25380
|
+
\u26A0 Deleting domain '${id}' removes its proof-of-control record. The platform refuses this while
|
|
25381
|
+
any active webhook still references it \u2014 disable or delete those first if this fails.
|
|
25382
|
+
|
|
25383
|
+
`
|
|
25384
|
+
);
|
|
25385
|
+
const typed = await promptCtx.prompt(` Type the domain id ('${id}') to confirm: `);
|
|
25386
|
+
if (typed.trim() !== id) {
|
|
25387
|
+
process.stderr.write(` \u2716 Confirmation did not match \u2014 nothing deleted.
|
|
25388
|
+
`);
|
|
25389
|
+
return 1;
|
|
25390
|
+
}
|
|
25391
|
+
} finally {
|
|
25392
|
+
promptCtx.close();
|
|
25393
|
+
}
|
|
25394
|
+
}
|
|
25395
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25396
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25397
|
+
try {
|
|
25398
|
+
const res = await fetchImpl(`${auth.baseUrl.replace(/\/$/, "")}/developer/domains/${encodeURIComponent(id)}`, {
|
|
25399
|
+
method: "DELETE",
|
|
25400
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}` }
|
|
25401
|
+
});
|
|
25402
|
+
if (!res.ok) throw await devRouteError(res, "domains delete");
|
|
25403
|
+
if (opts.json) {
|
|
25404
|
+
process.stdout.write(`${formatJson({ id, deleted: true })}
|
|
25405
|
+
`);
|
|
25406
|
+
return 0;
|
|
25407
|
+
}
|
|
25408
|
+
process.stdout.write(`
|
|
25409
|
+
\u2714 domain '${id}' deleted
|
|
25410
|
+
`);
|
|
25411
|
+
return 0;
|
|
25412
|
+
} catch (err) {
|
|
25413
|
+
process.stderr.write(formatCliError(err, { action: "domains delete failed", notFoundHint: NOT_FOUND_HINT2 }));
|
|
25414
|
+
return 1;
|
|
25415
|
+
}
|
|
25416
|
+
});
|
|
25417
|
+
}
|
|
25418
|
+
async function runDomainsVerify(opts, deps = {}) {
|
|
25419
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25420
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25421
|
+
try {
|
|
25422
|
+
const res = await fetchImpl(
|
|
25423
|
+
`${auth.baseUrl.replace(/\/$/, "")}/developer/domains/${encodeURIComponent(opts.id)}/verify`,
|
|
25424
|
+
{ method: "POST", headers: { Authorization: `Bearer ${auth.cognitoToken}` } }
|
|
25425
|
+
);
|
|
25426
|
+
if (!res.ok) throw await devRouteError(res, "domains verify");
|
|
25427
|
+
const result = await res.json();
|
|
25428
|
+
const verified = result.verified === true && result.status === "VERIFIED";
|
|
25429
|
+
if (opts.json) {
|
|
25430
|
+
process.stdout.write(`${formatJson(result)}
|
|
25431
|
+
`);
|
|
25432
|
+
return verified ? 0 : 1;
|
|
25433
|
+
}
|
|
25434
|
+
if (!verified) {
|
|
25435
|
+
process.stderr.write(
|
|
25436
|
+
` \u2716 domain '${result.domain ?? opts.id}' is not verified (status: ${result.status ?? "(none)"}).
|
|
25437
|
+
Check the DNS TXT record is published, allow time for it to propagate, then re-run this command.
|
|
25438
|
+
`
|
|
25439
|
+
);
|
|
25440
|
+
return 1;
|
|
25441
|
+
}
|
|
25442
|
+
process.stdout.write(
|
|
25443
|
+
`
|
|
25444
|
+
\u2714 domain '${result.domain ?? opts.id}' verified.
|
|
25445
|
+
${renderDomainVerifyResult(result)}
|
|
25446
|
+
`
|
|
25447
|
+
);
|
|
25448
|
+
return 0;
|
|
25449
|
+
} catch (err) {
|
|
25450
|
+
process.stderr.write(formatCliError(err, { action: "domains verify failed", notFoundHint: NOT_FOUND_HINT2 }));
|
|
25451
|
+
return 1;
|
|
25452
|
+
}
|
|
25453
|
+
});
|
|
25454
|
+
}
|
|
25455
|
+
|
|
25456
|
+
// src/commands/namespace.ts
|
|
25457
|
+
function renderNamespace(n) {
|
|
25458
|
+
return formatKeyValues([
|
|
25459
|
+
["namespace", n.namespace],
|
|
25460
|
+
["contextId", n.contextId ?? "(tenant-wide)"],
|
|
25461
|
+
["entityBacked", n.entityBacked === void 0 ? void 0 : String(n.entityBacked)],
|
|
25462
|
+
["reserved", n.reserved === void 0 ? void 0 : String(n.reserved)],
|
|
25463
|
+
["specificityRank", n.specificityRank === void 0 ? void 0 : String(n.specificityRank)],
|
|
25464
|
+
["defaultSchemaId", n.defaultSchemaId],
|
|
25465
|
+
["createdAt", n.createdAt]
|
|
25466
|
+
]);
|
|
25467
|
+
}
|
|
25468
|
+
async function runNamespaceList(opts, deps = {}) {
|
|
25469
|
+
return withAuth({ ...opts, context: opts.context }, deps, async (auth) => {
|
|
25470
|
+
const client = auth.client;
|
|
25471
|
+
try {
|
|
25472
|
+
const rows = await drainPages(
|
|
25473
|
+
(startFrom) => client.identity.listNamespaces({
|
|
25474
|
+
limit: PAGE_LIMIT,
|
|
25475
|
+
...opts.context ? { contextId: opts.context } : {},
|
|
25476
|
+
...startFrom ? { startFrom } : {}
|
|
25477
|
+
})
|
|
25478
|
+
);
|
|
25479
|
+
if (opts.json) {
|
|
24793
25480
|
process.stdout.write(`${formatJson(rows)}
|
|
24794
25481
|
`);
|
|
24795
25482
|
return 0;
|
|
@@ -24870,7 +25557,7 @@ ${renderNamespace(ns)}
|
|
|
24870
25557
|
|
|
24871
25558
|
// src/commands/scripts.ts
|
|
24872
25559
|
var import_promises4 = require("fs/promises");
|
|
24873
|
-
var
|
|
25560
|
+
var readline8 = __toESM(require("readline/promises"));
|
|
24874
25561
|
var SOURCE_MAX_BYTES = 3e5;
|
|
24875
25562
|
var INPUT_CONTRACT_MAX_BYTES = 5e4;
|
|
24876
25563
|
var utf8Bytes2 = (s) => new TextEncoder().encode(s).length;
|
|
@@ -25089,7 +25776,7 @@ async function runScriptsDelete(opts, deps = {}) {
|
|
|
25089
25776
|
return 2;
|
|
25090
25777
|
}
|
|
25091
25778
|
const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
|
|
25092
|
-
const rl =
|
|
25779
|
+
const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
|
|
25093
25780
|
return { prompt: (q) => rl.question(q), close: () => rl.close() };
|
|
25094
25781
|
})();
|
|
25095
25782
|
try {
|
|
@@ -26072,15 +26759,14 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
|
|
|
26072
26759
|
|
|
26073
26760
|
// src/commands/join.ts
|
|
26074
26761
|
var SELF_ALIAS = "me";
|
|
26075
|
-
async function resolvePrincipal(as, auth) {
|
|
26762
|
+
async function resolvePrincipal(as, auth, deps) {
|
|
26076
26763
|
const raw = as ?? SELF_ALIAS;
|
|
26077
26764
|
if (raw === SELF_ALIAS) {
|
|
26078
|
-
const self2 = await
|
|
26079
|
-
|
|
26080
|
-
|
|
26081
|
-
|
|
26082
|
-
|
|
26083
|
-
}
|
|
26765
|
+
const self2 = await resolveSelfPrincipalForTenant(
|
|
26766
|
+
{ ...auth, client: auth.client },
|
|
26767
|
+
deps.fetchImpl
|
|
26768
|
+
);
|
|
26769
|
+
if (!self2) throw new Error(selfPrincipalUnresolvedMessage(auth.tenant, "--as"));
|
|
26084
26770
|
return self2.principalId;
|
|
26085
26771
|
}
|
|
26086
26772
|
if (!raw.startsWith("usr_")) {
|
|
@@ -26150,7 +26836,7 @@ async function runJoin(opts, deps = {}) {
|
|
|
26150
26836
|
const client = auth.client;
|
|
26151
26837
|
try {
|
|
26152
26838
|
const tenantId = requireTenantId(auth);
|
|
26153
|
-
const principalId = await resolvePrincipal(opts.as, auth);
|
|
26839
|
+
const principalId = await resolvePrincipal(opts.as, auth, deps);
|
|
26154
26840
|
const userId = normalizePrincipalUserId(principalId);
|
|
26155
26841
|
const scopes = hasActions ? [{ allowed_actions: splitCsv(opts.actions) }] : void 0;
|
|
26156
26842
|
await client.auth.createAccessProfile({
|
|
@@ -26161,7 +26847,9 @@ async function runJoin(opts, deps = {}) {
|
|
|
26161
26847
|
// Always send the plural `roleIds`, even for a single
|
|
26162
26848
|
// `--role`: a strict superset of the deprecated singular `roleId`,
|
|
26163
26849
|
// read uniformly by the platform regardless of how many compose.
|
|
26164
|
-
|
|
26850
|
+
// With `--actions` there is no role: omit `roleIds` (the platform
|
|
26851
|
+
// refuses an empty list next to inline `scopes`).
|
|
26852
|
+
roleIds: hasRole ? opts.role : void 0,
|
|
26165
26853
|
scopes,
|
|
26166
26854
|
identityOverrides
|
|
26167
26855
|
}
|
|
@@ -26248,9 +26936,9 @@ function emitJoined(opts) {
|
|
|
26248
26936
|
|
|
26249
26937
|
// src/build-info.ts
|
|
26250
26938
|
var BUILD_INFO = {
|
|
26251
|
-
cli: true ? "0.
|
|
26252
|
-
sdk: true ? "0.
|
|
26253
|
-
blueprints: true ? "0.20.
|
|
26939
|
+
cli: true ? "0.25.0" : "dev",
|
|
26940
|
+
sdk: true ? "0.47.0" : "dev",
|
|
26941
|
+
blueprints: true ? "0.20.2" : "dev"
|
|
26254
26942
|
};
|
|
26255
26943
|
function formatBuildInfo() {
|
|
26256
26944
|
return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
|
|
@@ -26335,6 +27023,8 @@ function buildProgram(deps = {}) {
|
|
|
26335
27023
|
registerRoleCommands(program, deps);
|
|
26336
27024
|
registerContextCommands(program, deps);
|
|
26337
27025
|
registerIssuersCommands(program, deps);
|
|
27026
|
+
registerProviderConfigCommands(program, deps);
|
|
27027
|
+
registerDomainsCommands(program, deps);
|
|
26338
27028
|
registerNamespaceCommands(program, deps);
|
|
26339
27029
|
registerScriptsCommands(program, deps);
|
|
26340
27030
|
registerAssumeCommand(program, deps);
|
|
@@ -26456,7 +27146,7 @@ function registerContextCommands(program, deps) {
|
|
|
26456
27146
|
);
|
|
26457
27147
|
});
|
|
26458
27148
|
const destroy = context.command("destroy").description(
|
|
26459
|
-
"Permanently destroy an app context and ALL of its data (irreversible; asks for confirmation)."
|
|
27149
|
+
"Permanently destroy an app context and ALL of its data (irreversible; asks for confirmation). A blueprint's service principal belongs to the whole tenant and is NOT removed: delete it with 'identity delete'."
|
|
26460
27150
|
).argument("<contextId>", "the app context to destroy").option("--force", "skip the interactive confirmation (required when stdin is not a TTY)").option("--tenant <tenant>", "tenant the context lives in: 'test' or 'live' (default live)");
|
|
26461
27151
|
addCommonAuthOptions(destroy).action(
|
|
26462
27152
|
async (contextId, flags) => {
|
|
@@ -26545,7 +27235,10 @@ function registerIssuersCommands(program, deps) {
|
|
|
26545
27235
|
).option("--clear-captured-claims", "capture nothing beyond the email claim (sends an empty list; XOR --captured-claim)").option(
|
|
26546
27236
|
"--restricted-to-domain <domain>",
|
|
26547
27237
|
"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"
|
|
26548
|
-
).option(
|
|
27238
|
+
).option(
|
|
27239
|
+
"--clear-restricted-to-domain",
|
|
27240
|
+
"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"
|
|
27241
|
+
).option("--tenant <tenant>", "tenant the context lives in: 'test' or 'live' (default live)");
|
|
26549
27242
|
addCommonAuthOptions(update).action(
|
|
26550
27243
|
async (issuerId, flags) => {
|
|
26551
27244
|
const tenant = parseTenantFlag(flags.tenant);
|
|
@@ -26606,6 +27299,89 @@ function registerIssuersCommands(program, deps) {
|
|
|
26606
27299
|
}
|
|
26607
27300
|
);
|
|
26608
27301
|
}
|
|
27302
|
+
function registerProviderConfigCommands(program, deps) {
|
|
27303
|
+
const providerConfig = program.command("provider-config").description("Manage BYOM (bring-your-own-model) tenant-level provider configs.");
|
|
27304
|
+
const create = providerConfig.command("create").description("Create a new BYOM provider config (not idempotent \u2014 a repeat --alias 400s).").requiredOption("--alias <alias>", "operator-facing name used to select this config; unique per tenant; 1-64 lowercase letters, digits, - or _").requiredOption("--provider-type <type>", "'ANTHROPIC_NATIVE' or 'OPENAI_COMPATIBLE'").option("--endpoint <url>", "required for OPENAI_COMPATIBLE (no default); an optional override for ANTHROPIC_NATIVE; https on port 443").option("--declared-processing-region <region>", "customer-asserted, free-text, Vectros-unverified transparency field").option("--default-provider-model-id <id>", "convenience default only \u2014 a per-request model id, when the caller supplies one, always takes precedence").requiredOption("--api-key-secret-file <path>", "path to a file holding the provider credential ('-' for stdin) \u2014 never pass the secret as a bare flag value").requiredOption("--waiver-type <type>", "the waiver code this config is entitled under; the platform accepts byo-model-inference:customer-key for a config you set up yourself").option("--billing-mode <mode>", "only 'CUSTOMER_DIRECT' (your provider bills you), which is also the default; 'PLATFORM_BILLED' is retired").option("--status <status>", "'ACTIVE' or 'DISABLED' (default ACTIVE) \u2014 ACTIVE requires the tenant already hold a granted waiver for --waiver-type").option("--tenant <tenant>", "tenant to create the config in: 'test' or 'live' (default live)");
|
|
27305
|
+
addCommonAuthOptions(create).action(
|
|
27306
|
+
async (flags) => {
|
|
27307
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27308
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27309
|
+
process.exitCode = await runProviderConfigCreate(
|
|
27310
|
+
{
|
|
27311
|
+
...toCommonOptions(flags),
|
|
27312
|
+
alias: flags.alias,
|
|
27313
|
+
providerType: flags.providerType,
|
|
27314
|
+
endpoint: flags.endpoint,
|
|
27315
|
+
declaredProcessingRegion: flags.declaredProcessingRegion,
|
|
27316
|
+
defaultProviderModelId: flags.defaultProviderModelId,
|
|
27317
|
+
apiKeySecretFile: flags.apiKeySecretFile,
|
|
27318
|
+
waiverType: flags.waiverType,
|
|
27319
|
+
billingMode: flags.billingMode,
|
|
27320
|
+
status: flags.status,
|
|
27321
|
+
tenant
|
|
27322
|
+
},
|
|
27323
|
+
deps
|
|
27324
|
+
);
|
|
27325
|
+
}
|
|
27326
|
+
);
|
|
27327
|
+
const list = providerConfig.command("list").description("List provider configs in one tenant.").option("--tenant <tenant>", "tenant to read: 'test' or 'live' (default live)");
|
|
27328
|
+
addCommonAuthOptions(list).action(async (flags) => {
|
|
27329
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27330
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27331
|
+
process.exitCode = await runProviderConfigList({ ...toCommonOptions(flags), tenant }, deps);
|
|
27332
|
+
});
|
|
27333
|
+
const get = providerConfig.command("get").description("Show one provider config's metadata (no secret \u2014 the platform never returns it).").argument("<id>");
|
|
27334
|
+
addCommonAuthOptions(get).action(async (id, flags) => {
|
|
27335
|
+
process.exitCode = await runProviderConfigGet({ ...toCommonOptions(flags), id }, deps);
|
|
27336
|
+
});
|
|
27337
|
+
const update = providerConfig.command("update").description("Patch a provider config (partial update \u2014 an omitted field is left unchanged).").argument("<id>").option("--alias <alias>", "rename this config (unique per tenant); the platform refuses a new alias that is not 1-64 lowercase letters, digits, - or _ (an alias the config already holds is accepted)").option("--provider-type <type>", "'ANTHROPIC_NATIVE' or 'OPENAI_COMPATIBLE'; changing the type of an existing config needs --endpoint stated in the same command (--endpoint '' means the provider's default), or the platform answers 400").option("--endpoint <url>", "update the provider endpoint; https on port 443; changing the host needs --api-key-secret-file again; '' clears it (the provider's default), which also changes the host").option("--declared-processing-region <region>", "update the transparency field").option("--default-provider-model-id <id>", "update the convenience default model id").option("--api-key-secret-file <path>", "rotate the provider credential from a file ('-' for stdin); omit to leave the stored secret unchanged (required when --endpoint changes the host)").option("--waiver-type <type>", "update the waiver code this config is entitled under; the platform refuses a change to anything but byo-model-inference:customer-key (a value the config already holds is accepted)").option("--billing-mode <mode>", "only 'CUSTOMER_DIRECT' (your provider bills you); 'PLATFORM_BILLED' is retired. Omit it to leave the stored mode unchanged").option("--status <status>", "'ACTIVE' or 'DISABLED' \u2014 activating requires a granted waiver for this config's waiverType");
|
|
27338
|
+
addCommonAuthOptions(update).action(
|
|
27339
|
+
async (id, flags) => {
|
|
27340
|
+
process.exitCode = await runProviderConfigUpdate(
|
|
27341
|
+
{
|
|
27342
|
+
...toCommonOptions(flags),
|
|
27343
|
+
id,
|
|
27344
|
+
alias: flags.alias,
|
|
27345
|
+
providerType: flags.providerType,
|
|
27346
|
+
endpoint: flags.endpoint,
|
|
27347
|
+
declaredProcessingRegion: flags.declaredProcessingRegion,
|
|
27348
|
+
defaultProviderModelId: flags.defaultProviderModelId,
|
|
27349
|
+
apiKeySecretFile: flags.apiKeySecretFile,
|
|
27350
|
+
waiverType: flags.waiverType,
|
|
27351
|
+
billingMode: flags.billingMode,
|
|
27352
|
+
status: flags.status
|
|
27353
|
+
},
|
|
27354
|
+
deps
|
|
27355
|
+
);
|
|
27356
|
+
}
|
|
27357
|
+
);
|
|
27358
|
+
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)");
|
|
27359
|
+
addCommonAuthOptions(del).action(async (id, flags) => {
|
|
27360
|
+
process.exitCode = await runProviderConfigDelete(
|
|
27361
|
+
{ ...toCommonOptions(flags), id, force: Boolean(flags.force) },
|
|
27362
|
+
deps
|
|
27363
|
+
);
|
|
27364
|
+
});
|
|
27365
|
+
}
|
|
27366
|
+
function registerDomainsCommands(program, deps) {
|
|
27367
|
+
const domains = program.command("domains").description("Manage account-level domain ownership verification (DNS TXT proof-of-control).");
|
|
27368
|
+
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)");
|
|
27369
|
+
addCommonAuthOptions(register).action(async (flags) => {
|
|
27370
|
+
process.exitCode = await runDomainsRegister({ ...toCommonOptions(flags), domain: flags.domain }, deps);
|
|
27371
|
+
});
|
|
27372
|
+
const list = domains.command("list").description("List domains registered for this account (both tenants share this list).");
|
|
27373
|
+
addCommonAuthOptions(list).action(async (flags) => {
|
|
27374
|
+
process.exitCode = await runDomainsList(toCommonOptions(flags), deps);
|
|
27375
|
+
});
|
|
27376
|
+
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)");
|
|
27377
|
+
addCommonAuthOptions(del).action(async (id, flags) => {
|
|
27378
|
+
process.exitCode = await runDomainsDelete({ ...toCommonOptions(flags), id, force: Boolean(flags.force) }, deps);
|
|
27379
|
+
});
|
|
27380
|
+
const verify = domains.command("verify").description("Perform one DNS TXT lookup and report VERIFIED/FAILED \u2014 always re-runnable, never polls.").argument("<id>");
|
|
27381
|
+
addCommonAuthOptions(verify).action(async (id, flags) => {
|
|
27382
|
+
process.exitCode = await runDomainsVerify({ ...toCommonOptions(flags), id }, deps);
|
|
27383
|
+
});
|
|
27384
|
+
}
|
|
26609
27385
|
function registerNamespaceCommands(program, deps) {
|
|
26610
27386
|
const namespace = program.command("namespace").description("Manage scope-namespace registrations (whether scope:<namespace> values resolve to identity entities).");
|
|
26611
27387
|
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");
|
|
@@ -26840,12 +27616,15 @@ function dimensionOption() {
|
|
|
26840
27616
|
).makeOptionMandatory();
|
|
26841
27617
|
}
|
|
26842
27618
|
function registerIdentityCommands(program, deps) {
|
|
26843
|
-
const identity = program.command("identity").description("Manage tenant-wide
|
|
26844
|
-
const create = identity.command("create").description("Create (idempotent by externalId) a principal.").addOption(dimensionOption()).requiredOption("--external-id <id>", "your stable id for the principal (idempotency key)").option("--name <name>", "display name (entity namespaces only, not --type user)").option("--email <email>", "email (user only)").option("--service", "user only: mint a SERVICE principal (default HUMAN)").option("--scope <ns:value>", "entity only: a parent ownership edge (repeatable, \u22642, e.g. org:<id>)", collect, []).option("--metadata <json>", "JSON object of metadata");
|
|
27619
|
+
const identity = program.command("identity").description("Manage principals: users (tenant-wide) and namespaced entities (an entity lives with its namespace) \u2014 reference, never embed.");
|
|
27620
|
+
const create = identity.command("create").description("Create (idempotent by externalId) a principal.").addOption(dimensionOption()).requiredOption("--external-id <id>", "your stable id for the principal (idempotency key)").option("--name <name>", "display name (entity namespaces only, not --type user)").option("--email <email>", "email (user only)").option("--service", "user only: mint a SERVICE principal (default HUMAN)").option("--scope <ns:value>", "entity only: a parent ownership edge (repeatable, \u22642, e.g. org:<id>)", collect, []).option("--metadata <json>", "JSON object of metadata").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
26845
27621
|
addCommonAuthOptions(create).action(
|
|
26846
27622
|
async (flags) => {
|
|
27623
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27624
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
26847
27625
|
process.exitCode = await runIdentityCreate({
|
|
26848
27626
|
...toCommonOptions(flags),
|
|
27627
|
+
tenant,
|
|
26849
27628
|
type: flags.type,
|
|
26850
27629
|
externalId: flags.externalId,
|
|
26851
27630
|
name: flags.name,
|
|
@@ -26856,11 +27635,14 @@ function registerIdentityCommands(program, deps) {
|
|
|
26856
27635
|
}, deps);
|
|
26857
27636
|
}
|
|
26858
27637
|
);
|
|
26859
|
-
const list = identity.command("list").description("List principals of a dimension (optionally filtered).").addOption(dimensionOption()).option("--external-id <id>", "filter to a stable id").option("--limit <n>", "page size (1\u2013100)", (v) => Number.parseInt(v, 10));
|
|
27638
|
+
const list = identity.command("list").description("List principals of a dimension (optionally filtered).").addOption(dimensionOption()).option("--external-id <id>", "filter to a stable id").option("--limit <n>", "page size (1\u2013100)", (v) => Number.parseInt(v, 10)).option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
26860
27639
|
addCommonAuthOptions(list).action(
|
|
26861
27640
|
async (flags) => {
|
|
27641
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27642
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
26862
27643
|
process.exitCode = await runIdentityList({
|
|
26863
27644
|
...toCommonOptions(flags),
|
|
27645
|
+
tenant,
|
|
26864
27646
|
type: flags.type,
|
|
26865
27647
|
externalId: flags.externalId,
|
|
26866
27648
|
limit: flags.limit
|
|
@@ -26880,20 +27662,30 @@ function registerIdentityCommands(program, deps) {
|
|
|
26880
27662
|
);
|
|
26881
27663
|
}
|
|
26882
27664
|
);
|
|
26883
|
-
const get = identity.command("get").description("Show one principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id (from create/list)");
|
|
27665
|
+
const get = identity.command("get").description("Show one principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id (from create/list)").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
26884
27666
|
addCommonAuthOptions(get).action(async (flags) => {
|
|
26885
|
-
|
|
27667
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27668
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27669
|
+
process.exitCode = await runIdentityGet({ ...toCommonOptions(flags), tenant, type: flags.type, id: flags.id }, deps);
|
|
26886
27670
|
});
|
|
26887
|
-
const del = identity.command("delete").description("Delete a principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id");
|
|
27671
|
+
const del = identity.command("delete").description("Delete a principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
26888
27672
|
addAuthOptions(del).action(async (flags) => {
|
|
26889
|
-
|
|
27673
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27674
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27675
|
+
process.exitCode = await runIdentityDelete(
|
|
27676
|
+
{ ...toCommonOptions(flags), tenant, type: flags.type, id: flags.id },
|
|
27677
|
+
deps
|
|
27678
|
+
);
|
|
26890
27679
|
});
|
|
26891
|
-
const invite = identity.command("invite").description("Onboard a person into a context by email (POST /v1/users/invite) \u2014 no root key required.").requiredOption("--context <contextId>", "the app context the invitee will operate in").requiredOption("--role <roleId>", "an existing Role in that context; the invitee inherits its scopes").requiredOption("--email <email>", "the invitee's email address").option("--first-name <name>", "personalizes the invitation email greeting").option("--accept-url <url>", "your accept-invite landing page (required unless --no-send-email)").option("--no-send-email", "skip platform delivery; the response carries the raw inviteToken/acceptLink instead");
|
|
27680
|
+
const invite = identity.command("invite").description("Onboard a person into a context by email (POST /v1/users/invite) \u2014 no root key required.").requiredOption("--context <contextId>", "the app context the invitee will operate in").requiredOption("--role <roleId>", "an existing Role in that context; the invitee inherits its scopes").requiredOption("--email <email>", "the invitee's email address").option("--first-name <name>", "personalizes the invitation email greeting").option("--accept-url <url>", "your accept-invite landing page (required unless --no-send-email)").option("--no-send-email", "skip platform delivery; the response carries the raw inviteToken/acceptLink instead").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
26892
27681
|
addCommonAuthOptions(invite).action(
|
|
26893
27682
|
async (flags) => {
|
|
27683
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27684
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
26894
27685
|
process.exitCode = await runIdentityInvite(
|
|
26895
27686
|
{
|
|
26896
27687
|
...toCommonOptions(flags),
|
|
27688
|
+
tenant,
|
|
26897
27689
|
context: flags.context,
|
|
26898
27690
|
role: flags.role,
|
|
26899
27691
|
email: flags.email,
|