@vectros-ai/cli 0.23.0 → 0.24.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/CHANGELOG.md +31 -0
- package/README.md +72 -5
- package/dist/cli.js +693 -31
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +693 -31
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -523,7 +523,7 @@ var require_graceful_fs = __commonJS({
|
|
|
523
523
|
function patch(fs5) {
|
|
524
524
|
polyfills(fs5);
|
|
525
525
|
fs5.gracefulify = patch;
|
|
526
|
-
fs5.createReadStream =
|
|
526
|
+
fs5.createReadStream = createReadStream3;
|
|
527
527
|
fs5.createWriteStream = createWriteStream;
|
|
528
528
|
var fs$readFile = fs5.readFile;
|
|
529
529
|
fs5.readFile = readFile4;
|
|
@@ -733,7 +733,7 @@ var require_graceful_fs = __commonJS({
|
|
|
733
733
|
}
|
|
734
734
|
});
|
|
735
735
|
}
|
|
736
|
-
function
|
|
736
|
+
function createReadStream3(path4, options) {
|
|
737
737
|
return new fs5.ReadStream(path4, options);
|
|
738
738
|
}
|
|
739
739
|
function createWriteStream(path4, options) {
|
|
@@ -1619,7 +1619,7 @@ var require_proper_lockfile = __commonJS({
|
|
|
1619
1619
|
// src/cli-bootstrap.ts
|
|
1620
1620
|
import * as readline2 from "readline/promises";
|
|
1621
1621
|
|
|
1622
|
-
//
|
|
1622
|
+
// node_modules/@vectros-ai/sdk/dist/index.mjs
|
|
1623
1623
|
var __defProp2 = Object.defineProperty;
|
|
1624
1624
|
var __export = (target, all) => {
|
|
1625
1625
|
for (var name in all)
|
|
@@ -1639,6 +1639,7 @@ __export(api_exports, {
|
|
|
1639
1639
|
ContentTooLargeError: () => ContentTooLargeError,
|
|
1640
1640
|
DocumentContextEvent: () => DocumentContextEvent,
|
|
1641
1641
|
DocumentLookupRequest: () => DocumentLookupRequest,
|
|
1642
|
+
DocumentPatchRequest: () => DocumentPatchRequest,
|
|
1642
1643
|
DocumentRequest: () => DocumentRequest,
|
|
1643
1644
|
DocumentResponse: () => DocumentResponse,
|
|
1644
1645
|
DoneEvent: () => DoneEvent,
|
|
@@ -1982,6 +1983,13 @@ var DocumentLookupRequest;
|
|
|
1982
1983
|
Desc: "desc"
|
|
1983
1984
|
};
|
|
1984
1985
|
})(DocumentLookupRequest || (DocumentLookupRequest = {}));
|
|
1986
|
+
var DocumentPatchRequest;
|
|
1987
|
+
((DocumentPatchRequest2) => {
|
|
1988
|
+
DocumentPatchRequest2.Status = {
|
|
1989
|
+
Active: "ACTIVE",
|
|
1990
|
+
Archived: "ARCHIVED"
|
|
1991
|
+
};
|
|
1992
|
+
})(DocumentPatchRequest || (DocumentPatchRequest = {}));
|
|
1985
1993
|
var FileUploadRequest;
|
|
1986
1994
|
((FileUploadRequest2) => {
|
|
1987
1995
|
FileUploadRequest2.IndexMode = {
|
|
@@ -1994,6 +2002,7 @@ var FileUploadRequest;
|
|
|
1994
2002
|
var documents_exports = {};
|
|
1995
2003
|
__export(documents_exports, {
|
|
1996
2004
|
DocumentLookupRequest: () => DocumentLookupRequest,
|
|
2005
|
+
DocumentPatchRequest: () => DocumentPatchRequest,
|
|
1997
2006
|
FileUploadRequest: () => FileUploadRequest,
|
|
1998
2007
|
LookupDocumentsRequestOrder: () => LookupDocumentsRequestOrder
|
|
1999
2008
|
});
|
|
@@ -4658,6 +4667,7 @@ var AuthClient = class {
|
|
|
4658
4667
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
4659
4668
|
*
|
|
4660
4669
|
* @throws {@link Vectros.BadRequestError}
|
|
4670
|
+
* @throws {@link Vectros.PaymentRequiredError}
|
|
4661
4671
|
* @throws {@link Vectros.ForbiddenError}
|
|
4662
4672
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
4663
4673
|
*
|
|
@@ -4707,6 +4717,8 @@ var AuthClient = class {
|
|
|
4707
4717
|
switch (_response.error.statusCode) {
|
|
4708
4718
|
case 400:
|
|
4709
4719
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
4720
|
+
case 402:
|
|
4721
|
+
throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
|
|
4710
4722
|
case 403:
|
|
4711
4723
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
4712
4724
|
case 429:
|
|
@@ -6552,7 +6564,7 @@ var ComplianceClient = class {
|
|
|
6552
6564
|
this._options = normalizeClientOptionsWithAuth(options);
|
|
6553
6565
|
}
|
|
6554
6566
|
/**
|
|
6555
|
-
* Submits a right-to-erasure request for a single end-subject (a user, or an identity entity in any namespace). Erasure removes exactly the data the subject solely owns across the declared contexts, plus
|
|
6567
|
+
* Submits a right-to-erasure request for a single end-subject (a user, or an identity entity in any namespace). Erasure removes exactly the data the subject solely owns across the declared contexts, plus its identity and lookup rows when all contexts are covered. It never touches another account's data and never cascades into another subject's data. The request is asynchronous: it returns 202 with a `requestId`; poll `GET /v1/erasure-requests/{id}` until the job completes to obtain the completion certificate. Requires a root API key — a scoped credential is rejected with 403.
|
|
6556
6568
|
*
|
|
6557
6569
|
* @param {Vectros.ErasureRequest} request
|
|
6558
6570
|
* @param {ComplianceClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6615,7 +6627,7 @@ var ComplianceClient = class {
|
|
|
6615
6627
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/erasure-requests");
|
|
6616
6628
|
}
|
|
6617
6629
|
/**
|
|
6618
|
-
* Polls an erasure request by id. While the job is still running this returns its status only; once it completes, the response also includes the verifiable completion certificate (which contexts were swept, per-context deletion counts, and reports of dangling references and shared rows
|
|
6630
|
+
* Polls an erasure request by id. While the job is still running this returns its status only; once it completes, the response also includes the verifiable completion certificate (which contexts were swept, per-context deletion counts, whether the identity was kept, and reports of dangling references and shared rows left intact). Requires a root API key.
|
|
6619
6631
|
*
|
|
6620
6632
|
* @param {Vectros.GetErasureRequestRequest} request
|
|
6621
6633
|
* @param {ComplianceClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6858,7 +6870,7 @@ var DocumentsClient = class {
|
|
|
6858
6870
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents");
|
|
6859
6871
|
}
|
|
6860
6872
|
/**
|
|
6861
|
-
* Creates a document from a raw text string and queues it for asynchronous indexing so it becomes searchable. Optionally supply an `externalId` to make the create idempotent — if a document with the same `externalId` already exists in your context, that existing document is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing document was returned) tells the two apart. To overwrite an existing document's content instead of returning it unchanged, set `?upsert=true` (this also requires the `documents:u` scope). Requires the `documents:c` scope to create. Being returned the existing document on a collision is a read of that document's data and additionally requires the `documents:r` scope — a credential holding `documents:c` alone receives a `400` ("already exists") on collision instead of the document.
|
|
6873
|
+
* Creates a document from a raw text string and queues it for asynchronous indexing so it becomes searchable. Optionally supply an `externalId` to make the create idempotent — if a document with the same `externalId` already exists in your context, that existing document is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing document was returned) tells the two apart. To overwrite an existing document's content instead of returning it unchanged, set `?upsert=true` (this also requires the `documents:u` scope). Requires the `documents:c` scope to create. Being returned the existing document on a collision is a read of that document's data and additionally requires the `documents:r` scope — a credential holding `documents:c` alone receives a `400` ("already exists") on collision instead of the document. Supplying `externalId` WITHOUT `schemaId` is refused (`400`) unless you also pass `confirmUntyped=true` — externalId uniqueness is scoped per schema, so omitting `schemaId` by mistake would otherwise silently create a separate untyped document instead of targeting the typed one you likely meant.
|
|
6862
6874
|
*
|
|
6863
6875
|
* @param {Vectros.IngestDocumentRequest} request
|
|
6864
6876
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6878,10 +6890,11 @@ var DocumentsClient = class {
|
|
|
6878
6890
|
return HttpResponsePromise.fromPromise(this.__ingestDocument(request, requestOptions));
|
|
6879
6891
|
}
|
|
6880
6892
|
async __ingestDocument(request, requestOptions) {
|
|
6881
|
-
const { upsert, allowClear, body: _body } = request;
|
|
6893
|
+
const { upsert, allowClear, confirmUntyped, body: _body } = request;
|
|
6882
6894
|
const _queryParams = {
|
|
6883
6895
|
upsert,
|
|
6884
|
-
allowClear
|
|
6896
|
+
allowClear,
|
|
6897
|
+
confirmUntyped
|
|
6885
6898
|
};
|
|
6886
6899
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
6887
6900
|
const _headers = mergeHeaders(
|
|
@@ -7116,7 +7129,7 @@ var DocumentsClient = class {
|
|
|
7116
7129
|
/**
|
|
7117
7130
|
* Partially updates a document using an RFC 7386 JSON Merge Patch. The `payload` object is deep-merged: keys you send overwrite existing values (recursing into nested objects), a key set to `null` is deleted, and keys you omit are preserved — unlike PUT, which replaces the whole payload. Top-level fields (`title`, `folderId`, `schemaId`, ownership) are set when present and left unchanged when omitted; sending a top-level field as `null` is rejected. Supplying `text` re-ingests the document body (same as PUT). `indexMode`, `externalId`, and `storeText` (text retention is fixed at ingest) are immutable and rejected if present. The merged result is validated against the bound schema. Pass `expectedVersion` for optimistic concurrency (409 on conflict). Requires the `documents:u` scope.
|
|
7118
7131
|
*
|
|
7119
|
-
* @param {Vectros.
|
|
7132
|
+
* @param {Vectros.DocumentPatchRequest} request
|
|
7120
7133
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
7121
7134
|
*
|
|
7122
7135
|
* @throws {@link Vectros.BadRequestError}
|
|
@@ -7127,17 +7140,14 @@ var DocumentsClient = class {
|
|
|
7127
7140
|
*
|
|
7128
7141
|
* @example
|
|
7129
7142
|
* await client.documents.patchDocument({
|
|
7130
|
-
* id: "id"
|
|
7131
|
-
* body: {
|
|
7132
|
-
* title: "Patient Intake Form \u2014 Jane Doe"
|
|
7133
|
-
* }
|
|
7143
|
+
* id: "id"
|
|
7134
7144
|
* })
|
|
7135
7145
|
*/
|
|
7136
7146
|
patchDocument(request, requestOptions) {
|
|
7137
7147
|
return HttpResponsePromise.fromPromise(this.__patchDocument(request, requestOptions));
|
|
7138
7148
|
}
|
|
7139
7149
|
async __patchDocument(request, requestOptions) {
|
|
7140
|
-
const { id,
|
|
7150
|
+
const { id, ..._body } = request;
|
|
7141
7151
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
7142
7152
|
const _headers = mergeHeaders(
|
|
7143
7153
|
_authRequest.headers,
|
|
@@ -7509,7 +7519,7 @@ var DocumentsClient = class {
|
|
|
7509
7519
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/versions");
|
|
7510
7520
|
}
|
|
7511
7521
|
/**
|
|
7512
|
-
* Starts a file-based document by returning a short-lived presigned S3 PUT URL. Upload the file bytes directly to `uploadUrl`; the document is then automatically queued for text extraction and asynchronous indexing. Supplying an `externalId` makes this idempotent — re-initiating an upload with the same `externalId` re-issues a fresh presigned URL for the SAME existing document, targeting a newly-staged object rather than the document's current one — your PUT lands there first, and the document only adopts it once the upload is validated (so a re-upload inherently replaces the file body) rather than creating a duplicate. The response's `created` field (and the HTTP status — 201 when a new document was minted, 200 when an existing one was matched) tells the two apart. With `?upsert=true`, the submitted `payload`/`title` are also applied to the matched document (file-body divergence cannot be diffed at upload-init — the bytes have not arrived yet — so the re-upload itself replaces the body). Creating a NEW document requires the `documents:c` scope. Re-uploading over an EXISTING document overwrites (and re-indexes) its body, so it is an update: it requires the `documents:u` scope (as does `?upsert=true` for the metadata). **Your PUT to `uploadUrl` MUST include the header named in the response's `requiredHeaderName`, set to `requiredHeaderValue` exactly.** It is a signed part of the presigned request — the PUT never reaches this service either way, so neither failure uses this API's JSON error shape. Omitting it, or sending a different value, invalidates the request's signature and S3 rejects it with a 403 `SignatureDoesNotMatch`. Sending it correctly makes the URL single-use: `requiredHeaderName` is always `If-None-Match` and `requiredHeaderValue` is always `*` — a create, a recovery upload for a document whose earlier file is missing, and a re-upload ALL target a freshly-minted, never-written key, so the same S3 conditional-write precondition applies uniformly (S3's own documented conditional-write behavior): a replay after the object already exists (from your own completed PUT, or anyone else's) fails with `412 Precondition Failed`; a replay after the object was deleted and never re-created finds an absent key again and can succeed — single-use is a property of the object's existence, not a one-time token. **Either precondition can also fail with `409 Conflict` if your PUT genuinely races a concurrent delete on the same object.** Whichever of `412` or `409` you receive, the remedy is the same: call this endpoint again for a fresh URL rather than retrying the same PUT — a fresh call always targets a new key, so it is never subject to whatever raced the previous one.
|
|
7522
|
+
* Starts a file-based document by returning a short-lived presigned S3 PUT URL. Upload the file bytes directly to `uploadUrl`; the document is then automatically queued for text extraction and asynchronous indexing. Supplying an `externalId` makes this idempotent — re-initiating an upload with the same `externalId` re-issues a fresh presigned URL for the SAME existing document, targeting a newly-staged object rather than the document's current one — your PUT lands there first, and the document only adopts it once the upload is validated (so a re-upload inherently replaces the file body) rather than creating a duplicate. The response's `created` field (and the HTTP status — 201 when a new document was minted, 200 when an existing one was matched) tells the two apart. With `?upsert=true`, the submitted `payload`/`title` are also applied to the matched document (file-body divergence cannot be diffed at upload-init — the bytes have not arrived yet — so the re-upload itself replaces the body). Creating a NEW document requires the `documents:c` scope. Re-uploading over an EXISTING document overwrites (and re-indexes) its body, so it is an update: it requires the `documents:u` scope (as does `?upsert=true` for the metadata). **Your PUT to `uploadUrl` MUST include the header named in the response's `requiredHeaderName`, set to `requiredHeaderValue` exactly.** It is a signed part of the presigned request — the PUT never reaches this service either way, so neither failure uses this API's JSON error shape. Omitting it, or sending a different value, invalidates the request's signature and S3 rejects it with a 403 `SignatureDoesNotMatch`. Sending it correctly makes the URL single-use: `requiredHeaderName` is always `If-None-Match` and `requiredHeaderValue` is always `*` — a create, a recovery upload for a document whose earlier file is missing, and a re-upload ALL target a freshly-minted, never-written key, so the same S3 conditional-write precondition applies uniformly (S3's own documented conditional-write behavior): a replay after the object already exists (from your own completed PUT, or anyone else's) fails with `412 Precondition Failed`; a replay after the object was deleted and never re-created finds an absent key again and can succeed — single-use is a property of the object's existence, not a one-time token. **Either precondition can also fail with `409 Conflict` if your PUT genuinely races a concurrent delete on the same object.** Whichever of `412` or `409` you receive, the remedy is the same: call this endpoint again for a fresh URL rather than retrying the same PUT — a fresh call always targets a new key, so it is never subject to whatever raced the previous one. Supplying `externalId` WITHOUT `schemaId` is refused (`400`) unless you also pass `confirmUntyped=true` — externalId uniqueness is scoped per schema, so omitting `schemaId` by mistake would otherwise silently create a separate untyped document instead of targeting the typed one you likely meant.
|
|
7513
7523
|
*
|
|
7514
7524
|
* @param {Vectros.FileUploadRequest} request
|
|
7515
7525
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -7528,9 +7538,10 @@ var DocumentsClient = class {
|
|
|
7528
7538
|
return HttpResponsePromise.fromPromise(this.__uploadDocument(request, requestOptions));
|
|
7529
7539
|
}
|
|
7530
7540
|
async __uploadDocument(request, requestOptions) {
|
|
7531
|
-
const { upsert, ..._body } = request;
|
|
7541
|
+
const { upsert, confirmUntyped, ..._body } = request;
|
|
7532
7542
|
const _queryParams = {
|
|
7533
|
-
upsert
|
|
7543
|
+
upsert,
|
|
7544
|
+
confirmUntyped
|
|
7534
7545
|
};
|
|
7535
7546
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
7536
7547
|
const _headers = mergeHeaders(
|
|
@@ -12546,7 +12557,11 @@ var BlueprintIssuerSchema = z.object({
|
|
|
12546
12557
|
* actually scoped. An unverified domain fails the apply with the platform's `400`.
|
|
12547
12558
|
*
|
|
12548
12559
|
* Omit to register a domain-less issuer. Omitting it on a re-apply leaves an existing scope
|
|
12549
|
-
* unchanged
|
|
12560
|
+
* unchanged. On platform 0.45.0 and later a scope is removed only from a registration that
|
|
12561
|
+
* already holds the unrestricted `(issuer, audience)` pair, which one created with a domain
|
|
12562
|
+
* normally does not (`vectros issuers update --clear-restricted-to-domain` is refused for it):
|
|
12563
|
+
* replace it with a new issuer declared without `restrictedToDomain` and verify that, under a
|
|
12564
|
+
* different app context if the old issuer has served users.
|
|
12550
12565
|
*/
|
|
12551
12566
|
restrictedToDomain: z.string().regex(RESTRICTED_DOMAIN_RE, {
|
|
12552
12567
|
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"
|
|
@@ -15660,7 +15675,7 @@ async function devRouteError(res, action) {
|
|
|
15660
15675
|
}
|
|
15661
15676
|
function formatCliError(err, opts) {
|
|
15662
15677
|
const info = extractServerError(err);
|
|
15663
|
-
const actionable = renderActionableError(info);
|
|
15678
|
+
const actionable = renderActionableError(info, opts.notFoundHint);
|
|
15664
15679
|
const block = actionable ?? ` \u2716 ${opts.action}: ${info.message}
|
|
15665
15680
|
`;
|
|
15666
15681
|
const debug = opts.debug ?? Boolean(process.env.VECTROS_MCP_DEBUG);
|
|
@@ -15674,7 +15689,7 @@ function formatCliError(err, opts) {
|
|
|
15674
15689
|
}
|
|
15675
15690
|
return block;
|
|
15676
15691
|
}
|
|
15677
|
-
function renderActionableError(info) {
|
|
15692
|
+
function renderActionableError(info, notFoundHint) {
|
|
15678
15693
|
const msg = info.message;
|
|
15679
15694
|
switch (info.errorCode) {
|
|
15680
15695
|
case "SESSION_REFRESH_REQUIRED":
|
|
@@ -15712,7 +15727,7 @@ function renderActionableError(info) {
|
|
|
15712
15727
|
}
|
|
15713
15728
|
if (info.statusCode === 404) {
|
|
15714
15729
|
return ` \u2716 ${msg}
|
|
15715
|
-
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."}
|
|
15716
15731
|
`;
|
|
15717
15732
|
}
|
|
15718
15733
|
if (/migration-locked|cannot be (changed|modified)|immutable/i.test(msg) && /schema|field|lookup|type/i.test(msg)) {
|
|
@@ -19270,8 +19285,8 @@ async function withIndexLock(dir, fn, timings = LOCK_OPTS) {
|
|
|
19270
19285
|
// sections are ~1ms, so in practice the holder is gone before the first tick
|
|
19271
19286
|
// and nothing is ever detected. (The probe that observed the no-op ran at
|
|
19272
19287
|
// stale:2000 with the section artificially held open — a configuration we do
|
|
19273
|
-
// not ship
|
|
19274
|
-
//
|
|
19288
|
+
// not ship, and a measurement taken in a configuration you don't ship is not
|
|
19289
|
+
// evidence about the configuration you do.) The
|
|
19275
19290
|
// honest shape: our release runs unconditionally, and after a stale-break
|
|
19276
19291
|
// collision it CAN remove a successor's fresh lock — one more route to two
|
|
19277
19292
|
// writers at once, on top of the rmdir race below. The backstop for both is
|
|
@@ -24313,12 +24328,12 @@ function parseRestrictedToDomain(raw) {
|
|
|
24313
24328
|
const domain = raw.trim();
|
|
24314
24329
|
if (domain.length === 0) {
|
|
24315
24330
|
throw new Error(
|
|
24316
|
-
"--restricted-to-domain must be a non-blank domain (e.g. 'acme.com'). To remove the scope from an existing issuer, use --clear-restricted-to-domain."
|
|
24331
|
+
"--restricted-to-domain must be a non-blank domain (e.g. 'acme.com'). To remove the scope from an existing issuer, use --clear-restricted-to-domain (the platform refuses that unless the registration already holds the unrestricted (issuer, audience) pair)."
|
|
24317
24332
|
);
|
|
24318
24333
|
}
|
|
24319
24334
|
if (!isRestrictedToDomainName(domain.toLowerCase())) {
|
|
24320
24335
|
throw new Error(
|
|
24321
|
-
`--restricted-to-domain must be a DNS name such as 'acme.com' \u2014 letters, digits and dashes in dot-separated labels, at least two labels, no scheme, path or port (write an internationalized domain in its punycode xn-- form); got '${domain}'. To remove the scope from an existing issuer, use --clear-restricted-to-domain.`
|
|
24336
|
+
`--restricted-to-domain must be a DNS name such as 'acme.com' \u2014 letters, digits and dashes in dot-separated labels, at least two labels, no scheme, path or port (write an internationalized domain in its punycode xn-- form); got '${domain}'. To remove the scope from an existing issuer, use --clear-restricted-to-domain (the platform refuses that unless the registration already holds the unrestricted (issuer, audience) pair).`
|
|
24322
24337
|
);
|
|
24323
24338
|
}
|
|
24324
24339
|
return domain;
|
|
@@ -24771,6 +24786,541 @@ ${renderIssuer(issuer)}
|
|
|
24771
24786
|
});
|
|
24772
24787
|
}
|
|
24773
24788
|
|
|
24789
|
+
// src/commands/provider-config.ts
|
|
24790
|
+
import { createReadStream as createReadStream2 } from "fs";
|
|
24791
|
+
import * as readline6 from "readline/promises";
|
|
24792
|
+
var PROVIDER_TYPES = ["ANTHROPIC_NATIVE", "OPENAI_COMPATIBLE"];
|
|
24793
|
+
var BILLING_MODES = ["PLATFORM_BILLED", "CUSTOMER_DIRECT"];
|
|
24794
|
+
var STATUSES = ["ACTIVE", "DISABLED"];
|
|
24795
|
+
var MAX_SECRET_BYTES = 8 * 1024;
|
|
24796
|
+
async function readBoundedSecret(stream) {
|
|
24797
|
+
try {
|
|
24798
|
+
return await readAllFrom(stream, MAX_SECRET_BYTES);
|
|
24799
|
+
} catch (err) {
|
|
24800
|
+
if (err instanceof TokenTooLargeError) {
|
|
24801
|
+
throw new Error(
|
|
24802
|
+
`the input is larger than ${MAX_SECRET_BYTES / 1024} KB, which no API key is; nothing was read further and nothing was sent.`
|
|
24803
|
+
);
|
|
24804
|
+
}
|
|
24805
|
+
throw err;
|
|
24806
|
+
}
|
|
24807
|
+
}
|
|
24808
|
+
function fileFailureReason2(err) {
|
|
24809
|
+
switch (err?.code) {
|
|
24810
|
+
case "ENOENT":
|
|
24811
|
+
return "no such file.";
|
|
24812
|
+
case "EISDIR":
|
|
24813
|
+
return "that is a directory.";
|
|
24814
|
+
case "EACCES":
|
|
24815
|
+
case "EPERM":
|
|
24816
|
+
return "permission denied.";
|
|
24817
|
+
default:
|
|
24818
|
+
return "it could not be read.";
|
|
24819
|
+
}
|
|
24820
|
+
}
|
|
24821
|
+
async function resolveApiKeySecret(fileFlag, deps, mode) {
|
|
24822
|
+
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
|
|
24823
|
+
const readStdin = deps.readStdin ?? (() => readBoundedSecret(process.stdin));
|
|
24824
|
+
const readTextFile = deps.readTextFile ?? ((path4) => readBoundedSecret(createReadStream2(path4)));
|
|
24825
|
+
let raw;
|
|
24826
|
+
if (fileFlag !== void 0 && fileFlag !== "-") {
|
|
24827
|
+
if (looksLikeCredential(fileFlag)) {
|
|
24828
|
+
throw new Error(
|
|
24829
|
+
"--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 -."
|
|
24830
|
+
);
|
|
24831
|
+
}
|
|
24832
|
+
try {
|
|
24833
|
+
raw = await readTextFile(fileFlag);
|
|
24834
|
+
} catch (err) {
|
|
24835
|
+
const name = safeBaseName(fileFlag);
|
|
24836
|
+
throw new Error(
|
|
24837
|
+
`could not read the file given to --api-key-secret-file${name ? ` (${name})` : ""}: ${fileFailureReason2(err)}`
|
|
24838
|
+
);
|
|
24839
|
+
}
|
|
24840
|
+
} else if (fileFlag === "-") {
|
|
24841
|
+
if (isTty) {
|
|
24842
|
+
throw new Error(
|
|
24843
|
+
"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>."
|
|
24844
|
+
);
|
|
24845
|
+
}
|
|
24846
|
+
raw = await readStdin();
|
|
24847
|
+
} else if (mode === "required" && !isTty) {
|
|
24848
|
+
process.stderr.write(" \u2026 no --api-key-secret-file given: reading the secret from stdin.\n");
|
|
24849
|
+
raw = await readStdin();
|
|
24850
|
+
} else if (mode === "required") {
|
|
24851
|
+
throw new Error(
|
|
24852
|
+
"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)."
|
|
24853
|
+
);
|
|
24854
|
+
} else {
|
|
24855
|
+
return void 0;
|
|
24856
|
+
}
|
|
24857
|
+
const secret = raw.trim();
|
|
24858
|
+
if (secret.length === 0) throw new Error("the secret is empty.");
|
|
24859
|
+
return secret;
|
|
24860
|
+
}
|
|
24861
|
+
function renderProviderConfig(c) {
|
|
24862
|
+
return formatKeyValues([
|
|
24863
|
+
["id", c.id],
|
|
24864
|
+
["tenantId", c.tenantId],
|
|
24865
|
+
["alias", c.alias],
|
|
24866
|
+
["providerType", c.providerType],
|
|
24867
|
+
["endpoint", c.endpoint],
|
|
24868
|
+
["declaredProcessingRegion", c.declaredProcessingRegion],
|
|
24869
|
+
["defaultProviderModelId", c.defaultProviderModelId],
|
|
24870
|
+
["waiverType", c.waiverType],
|
|
24871
|
+
["billingMode", c.billingMode],
|
|
24872
|
+
["platformTokenFactor", c.platformTokenFactor],
|
|
24873
|
+
["platformOutputWeight", c.platformOutputWeight],
|
|
24874
|
+
["status", c.status],
|
|
24875
|
+
["createdAt", c.createdAt]
|
|
24876
|
+
]);
|
|
24877
|
+
}
|
|
24878
|
+
function invalidEnum(flag, value, allowed) {
|
|
24879
|
+
return `${flag} must be one of ${allowed.join(", ")} (got '${value}').`;
|
|
24880
|
+
}
|
|
24881
|
+
var NOT_FOUND_HINT = "'vectros provider-config list --tenant <test|live>' enumerates configs in a tenant.";
|
|
24882
|
+
async function runProviderConfigCreate(opts, deps = {}) {
|
|
24883
|
+
if (!PROVIDER_TYPES.includes(opts.providerType)) {
|
|
24884
|
+
process.stderr.write(` \u2716 ${invalidEnum("--provider-type", opts.providerType, PROVIDER_TYPES)}
|
|
24885
|
+
`);
|
|
24886
|
+
return 2;
|
|
24887
|
+
}
|
|
24888
|
+
if (!BILLING_MODES.includes(opts.billingMode)) {
|
|
24889
|
+
process.stderr.write(` \u2716 ${invalidEnum("--billing-mode", opts.billingMode, BILLING_MODES)}
|
|
24890
|
+
`);
|
|
24891
|
+
return 2;
|
|
24892
|
+
}
|
|
24893
|
+
if (opts.status !== void 0 && !STATUSES.includes(opts.status)) {
|
|
24894
|
+
process.stderr.write(` \u2716 ${invalidEnum("--status", opts.status, STATUSES)}
|
|
24895
|
+
`);
|
|
24896
|
+
return 2;
|
|
24897
|
+
}
|
|
24898
|
+
if (opts.billingMode === "PLATFORM_BILLED") {
|
|
24899
|
+
if (opts.platformTokenFactor === void 0 || opts.platformOutputWeight === void 0) {
|
|
24900
|
+
process.stderr.write(
|
|
24901
|
+
" \u2716 --platform-token-factor and --platform-output-weight are required when --billing-mode PLATFORM_BILLED.\n"
|
|
24902
|
+
);
|
|
24903
|
+
return 2;
|
|
24904
|
+
}
|
|
24905
|
+
} else if (opts.platformTokenFactor !== void 0 || opts.platformOutputWeight !== void 0) {
|
|
24906
|
+
process.stderr.write(
|
|
24907
|
+
" \u2716 --platform-token-factor/--platform-output-weight only apply with --billing-mode PLATFORM_BILLED.\n"
|
|
24908
|
+
);
|
|
24909
|
+
return 2;
|
|
24910
|
+
}
|
|
24911
|
+
let apiKeySecret;
|
|
24912
|
+
try {
|
|
24913
|
+
apiKeySecret = await resolveApiKeySecret(opts.apiKeySecretFile, deps, "required");
|
|
24914
|
+
} catch (err) {
|
|
24915
|
+
process.stderr.write(` \u2716 ${formatError(err)}
|
|
24916
|
+
`);
|
|
24917
|
+
return 2;
|
|
24918
|
+
}
|
|
24919
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
24920
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
24921
|
+
try {
|
|
24922
|
+
const tenantId = requireTenantId(auth);
|
|
24923
|
+
const res = await fetchImpl(`${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs`, {
|
|
24924
|
+
method: "POST",
|
|
24925
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}`, "Content-Type": "application/json" },
|
|
24926
|
+
body: JSON.stringify({
|
|
24927
|
+
tenantId,
|
|
24928
|
+
alias: opts.alias,
|
|
24929
|
+
providerType: opts.providerType,
|
|
24930
|
+
endpoint: opts.endpoint,
|
|
24931
|
+
declaredProcessingRegion: opts.declaredProcessingRegion,
|
|
24932
|
+
defaultProviderModelId: opts.defaultProviderModelId,
|
|
24933
|
+
apiKeySecret,
|
|
24934
|
+
waiverType: opts.waiverType,
|
|
24935
|
+
billingMode: opts.billingMode,
|
|
24936
|
+
platformTokenFactor: opts.platformTokenFactor,
|
|
24937
|
+
platformOutputWeight: opts.platformOutputWeight,
|
|
24938
|
+
status: opts.status
|
|
24939
|
+
})
|
|
24940
|
+
});
|
|
24941
|
+
if (!res.ok) throw await devRouteError(res, "provider-config create");
|
|
24942
|
+
const config = await res.json();
|
|
24943
|
+
if (opts.json) {
|
|
24944
|
+
process.stdout.write(`${formatJson(config)}
|
|
24945
|
+
`);
|
|
24946
|
+
return 0;
|
|
24947
|
+
}
|
|
24948
|
+
process.stdout.write(`
|
|
24949
|
+
\u2714 provider config '${opts.alias}' created
|
|
24950
|
+
${renderProviderConfig(config)}
|
|
24951
|
+
`);
|
|
24952
|
+
return 0;
|
|
24953
|
+
} catch (err) {
|
|
24954
|
+
process.stderr.write(formatCliError(err, { action: "provider-config create failed" }));
|
|
24955
|
+
return 1;
|
|
24956
|
+
}
|
|
24957
|
+
});
|
|
24958
|
+
}
|
|
24959
|
+
async function runProviderConfigList(opts, deps = {}) {
|
|
24960
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
24961
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
24962
|
+
try {
|
|
24963
|
+
const tenantId = requireTenantId(auth);
|
|
24964
|
+
const url = `${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs?tenantId=${encodeURIComponent(tenantId)}`;
|
|
24965
|
+
const res = await fetchImpl(url, { method: "GET", headers: { Authorization: `Bearer ${auth.cognitoToken}` } });
|
|
24966
|
+
if (!res.ok) throw await devRouteError(res, "provider-config list");
|
|
24967
|
+
const rows = await res.json();
|
|
24968
|
+
if (opts.json) {
|
|
24969
|
+
process.stdout.write(`${formatJson(rows)}
|
|
24970
|
+
`);
|
|
24971
|
+
return 0;
|
|
24972
|
+
}
|
|
24973
|
+
process.stdout.write(
|
|
24974
|
+
"\n" + formatTable(rows, [
|
|
24975
|
+
{ header: "id", get: (c) => c.id },
|
|
24976
|
+
{ header: "alias", get: (c) => c.alias },
|
|
24977
|
+
{ header: "provider type", get: (c) => c.providerType },
|
|
24978
|
+
{ header: "billing mode", get: (c) => c.billingMode },
|
|
24979
|
+
{ header: "status", get: (c) => c.status }
|
|
24980
|
+
]) + "\n"
|
|
24981
|
+
);
|
|
24982
|
+
return 0;
|
|
24983
|
+
} catch (err) {
|
|
24984
|
+
process.stderr.write(formatCliError(err, { action: "provider-config list failed" }));
|
|
24985
|
+
return 1;
|
|
24986
|
+
}
|
|
24987
|
+
});
|
|
24988
|
+
}
|
|
24989
|
+
async function runProviderConfigGet(opts, deps = {}) {
|
|
24990
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
24991
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
24992
|
+
try {
|
|
24993
|
+
const url = `${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs/${encodeURIComponent(opts.id)}`;
|
|
24994
|
+
const res = await fetchImpl(url, { method: "GET", headers: { Authorization: `Bearer ${auth.cognitoToken}` } });
|
|
24995
|
+
if (!res.ok) throw await devRouteError(res, "provider-config get");
|
|
24996
|
+
const config = await res.json();
|
|
24997
|
+
if (opts.json) {
|
|
24998
|
+
process.stdout.write(`${formatJson(config)}
|
|
24999
|
+
`);
|
|
25000
|
+
return 0;
|
|
25001
|
+
}
|
|
25002
|
+
process.stdout.write(`
|
|
25003
|
+
${renderProviderConfig(config)}
|
|
25004
|
+
`);
|
|
25005
|
+
return 0;
|
|
25006
|
+
} catch (err) {
|
|
25007
|
+
process.stderr.write(formatCliError(err, { action: "provider-config get failed", notFoundHint: NOT_FOUND_HINT }));
|
|
25008
|
+
return 1;
|
|
25009
|
+
}
|
|
25010
|
+
});
|
|
25011
|
+
}
|
|
25012
|
+
async function runProviderConfigUpdate(opts, deps = {}) {
|
|
25013
|
+
if (opts.providerType !== void 0 && !PROVIDER_TYPES.includes(opts.providerType)) {
|
|
25014
|
+
process.stderr.write(` \u2716 ${invalidEnum("--provider-type", opts.providerType, PROVIDER_TYPES)}
|
|
25015
|
+
`);
|
|
25016
|
+
return 2;
|
|
25017
|
+
}
|
|
25018
|
+
if (opts.billingMode !== void 0 && !BILLING_MODES.includes(opts.billingMode)) {
|
|
25019
|
+
process.stderr.write(` \u2716 ${invalidEnum("--billing-mode", opts.billingMode, BILLING_MODES)}
|
|
25020
|
+
`);
|
|
25021
|
+
return 2;
|
|
25022
|
+
}
|
|
25023
|
+
if (opts.status !== void 0 && !STATUSES.includes(opts.status)) {
|
|
25024
|
+
process.stderr.write(` \u2716 ${invalidEnum("--status", opts.status, STATUSES)}
|
|
25025
|
+
`);
|
|
25026
|
+
return 2;
|
|
25027
|
+
}
|
|
25028
|
+
let apiKeySecret;
|
|
25029
|
+
try {
|
|
25030
|
+
apiKeySecret = await resolveApiKeySecret(opts.apiKeySecretFile, deps, "optional");
|
|
25031
|
+
} catch (err) {
|
|
25032
|
+
process.stderr.write(` \u2716 ${formatError(err)}
|
|
25033
|
+
`);
|
|
25034
|
+
return 2;
|
|
25035
|
+
}
|
|
25036
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25037
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25038
|
+
try {
|
|
25039
|
+
const url = `${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs/${encodeURIComponent(opts.id)}`;
|
|
25040
|
+
const res = await fetchImpl(url, {
|
|
25041
|
+
method: "PUT",
|
|
25042
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}`, "Content-Type": "application/json" },
|
|
25043
|
+
body: JSON.stringify({
|
|
25044
|
+
alias: opts.alias,
|
|
25045
|
+
providerType: opts.providerType,
|
|
25046
|
+
endpoint: opts.endpoint,
|
|
25047
|
+
declaredProcessingRegion: opts.declaredProcessingRegion,
|
|
25048
|
+
defaultProviderModelId: opts.defaultProviderModelId,
|
|
25049
|
+
apiKeySecret,
|
|
25050
|
+
waiverType: opts.waiverType,
|
|
25051
|
+
billingMode: opts.billingMode,
|
|
25052
|
+
platformTokenFactor: opts.platformTokenFactor,
|
|
25053
|
+
platformOutputWeight: opts.platformOutputWeight,
|
|
25054
|
+
status: opts.status
|
|
25055
|
+
})
|
|
25056
|
+
});
|
|
25057
|
+
if (!res.ok) throw await devRouteError(res, "provider-config update");
|
|
25058
|
+
const config = await res.json();
|
|
25059
|
+
if (opts.json) {
|
|
25060
|
+
process.stdout.write(`${formatJson(config)}
|
|
25061
|
+
`);
|
|
25062
|
+
return 0;
|
|
25063
|
+
}
|
|
25064
|
+
process.stdout.write(`
|
|
25065
|
+
\u2714 provider config '${opts.id}' updated
|
|
25066
|
+
${renderProviderConfig(config)}
|
|
25067
|
+
`);
|
|
25068
|
+
return 0;
|
|
25069
|
+
} catch (err) {
|
|
25070
|
+
process.stderr.write(formatCliError(err, { action: "provider-config update failed", notFoundHint: NOT_FOUND_HINT }));
|
|
25071
|
+
return 1;
|
|
25072
|
+
}
|
|
25073
|
+
});
|
|
25074
|
+
}
|
|
25075
|
+
async function runProviderConfigDelete(opts, deps = {}) {
|
|
25076
|
+
const { id } = opts;
|
|
25077
|
+
if (!opts.force) {
|
|
25078
|
+
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
|
|
25079
|
+
if (!isTty) {
|
|
25080
|
+
process.stderr.write(
|
|
25081
|
+
` \u2716 provider-config delete needs an interactive confirmation.
|
|
25082
|
+
Re-run with --force to delete '${id}' without a prompt.
|
|
25083
|
+
`
|
|
25084
|
+
);
|
|
25085
|
+
return 2;
|
|
25086
|
+
}
|
|
25087
|
+
const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
|
|
25088
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
25089
|
+
return { prompt: (q) => rl.question(q), close: () => rl.close() };
|
|
25090
|
+
})();
|
|
25091
|
+
try {
|
|
25092
|
+
process.stdout.write(
|
|
25093
|
+
`
|
|
25094
|
+
\u26A0 Deleting provider config '${id}' removes it permanently. Any inference call that resolves
|
|
25095
|
+
a provider by this config's alias starts failing immediately. To disable it instead (reversible),
|
|
25096
|
+
run \`vectros provider-config update ${id} --status DISABLED\`.
|
|
25097
|
+
|
|
25098
|
+
`
|
|
25099
|
+
);
|
|
25100
|
+
const typed = await promptCtx.prompt(` Type the config id ('${id}') to confirm: `);
|
|
25101
|
+
if (typed.trim() !== id) {
|
|
25102
|
+
process.stderr.write(` \u2716 Confirmation did not match \u2014 nothing deleted.
|
|
25103
|
+
`);
|
|
25104
|
+
return 1;
|
|
25105
|
+
}
|
|
25106
|
+
} finally {
|
|
25107
|
+
promptCtx.close();
|
|
25108
|
+
}
|
|
25109
|
+
}
|
|
25110
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25111
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25112
|
+
try {
|
|
25113
|
+
const url = `${auth.baseUrl.replace(/\/$/, "")}/developer/provider-configs/${encodeURIComponent(id)}`;
|
|
25114
|
+
const res = await fetchImpl(url, { method: "DELETE", headers: { Authorization: `Bearer ${auth.cognitoToken}` } });
|
|
25115
|
+
if (!res.ok) throw await devRouteError(res, "provider-config delete");
|
|
25116
|
+
if (opts.json) {
|
|
25117
|
+
process.stdout.write(`${formatJson({ id, deleted: true })}
|
|
25118
|
+
`);
|
|
25119
|
+
return 0;
|
|
25120
|
+
}
|
|
25121
|
+
process.stdout.write(`
|
|
25122
|
+
\u2714 provider config '${id}' deleted
|
|
25123
|
+
`);
|
|
25124
|
+
return 0;
|
|
25125
|
+
} catch (err) {
|
|
25126
|
+
process.stderr.write(formatCliError(err, { action: "provider-config delete failed", notFoundHint: NOT_FOUND_HINT }));
|
|
25127
|
+
return 1;
|
|
25128
|
+
}
|
|
25129
|
+
});
|
|
25130
|
+
}
|
|
25131
|
+
|
|
25132
|
+
// src/commands/domains.ts
|
|
25133
|
+
import * as readline7 from "readline/promises";
|
|
25134
|
+
function renderDomainVerifyResult(d) {
|
|
25135
|
+
return formatKeyValues([
|
|
25136
|
+
["id", d.id],
|
|
25137
|
+
["domain", d.domain],
|
|
25138
|
+
["status", d.status]
|
|
25139
|
+
]);
|
|
25140
|
+
}
|
|
25141
|
+
var NOT_FOUND_HINT2 = "'vectros domains list' enumerates your registered domains.";
|
|
25142
|
+
async function runDomainsRegister(opts, deps = {}) {
|
|
25143
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25144
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25145
|
+
try {
|
|
25146
|
+
const res = await fetchImpl(`${auth.baseUrl.replace(/\/$/, "")}/developer/domains`, {
|
|
25147
|
+
method: "POST",
|
|
25148
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}`, "Content-Type": "application/json" },
|
|
25149
|
+
body: JSON.stringify({ domain: opts.domain })
|
|
25150
|
+
});
|
|
25151
|
+
if (res.status === 409) {
|
|
25152
|
+
const raw = await res.text().catch(() => "");
|
|
25153
|
+
let message = "Domain already registered";
|
|
25154
|
+
let existingId;
|
|
25155
|
+
let existingStatus;
|
|
25156
|
+
try {
|
|
25157
|
+
const parsed = JSON.parse(raw);
|
|
25158
|
+
if (typeof parsed.message === "string" && parsed.message.trim()) message = parsed.message.trim();
|
|
25159
|
+
if (typeof parsed.id === "string") existingId = parsed.id;
|
|
25160
|
+
if (typeof parsed.status === "string") existingStatus = parsed.status;
|
|
25161
|
+
} catch {
|
|
25162
|
+
}
|
|
25163
|
+
if (opts.json) {
|
|
25164
|
+
process.stdout.write(`${formatJson({ message, id: existingId, status: existingStatus })}
|
|
25165
|
+
`);
|
|
25166
|
+
return 1;
|
|
25167
|
+
}
|
|
25168
|
+
process.stderr.write(
|
|
25169
|
+
` \u2716 ${message}${existingId ? ` (id: ${existingId}${existingStatus ? `, status: ${existingStatus}` : ""})` : ""}
|
|
25170
|
+
` + (existingStatus === "PENDING" && existingId ? ` Still pending verification. Run: vectros domains verify ${existingId}
|
|
25171
|
+
` : " Run `vectros domains list` to find it.\n")
|
|
25172
|
+
);
|
|
25173
|
+
return 1;
|
|
25174
|
+
}
|
|
25175
|
+
if (!res.ok) throw await devRouteError(res, "domains register");
|
|
25176
|
+
const result = await res.json();
|
|
25177
|
+
if (opts.json) {
|
|
25178
|
+
process.stdout.write(`${formatJson(result)}
|
|
25179
|
+
`);
|
|
25180
|
+
return 0;
|
|
25181
|
+
}
|
|
25182
|
+
process.stdout.write(
|
|
25183
|
+
`
|
|
25184
|
+
\u2714 domain '${result.domain ?? opts.domain}' registered (id: ${result.id ?? "?"}, status: ${result.status ?? "PENDING"})
|
|
25185
|
+
|
|
25186
|
+
${result.instructions ?? `Add a DNS TXT record: _vectros-verify.${result.domain ?? opts.domain} \u2192 "${result.verificationToken ?? ""}"`}
|
|
25187
|
+
|
|
25188
|
+
Once the record is published, run:
|
|
25189
|
+
vectros domains verify ${result.id ?? "<id>"}
|
|
25190
|
+
`
|
|
25191
|
+
);
|
|
25192
|
+
return 0;
|
|
25193
|
+
} catch (err) {
|
|
25194
|
+
process.stderr.write(formatCliError(err, { action: "domains register failed" }));
|
|
25195
|
+
return 1;
|
|
25196
|
+
}
|
|
25197
|
+
});
|
|
25198
|
+
}
|
|
25199
|
+
async function runDomainsList(opts, deps = {}) {
|
|
25200
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25201
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25202
|
+
try {
|
|
25203
|
+
const res = await fetchImpl(`${auth.baseUrl.replace(/\/$/, "")}/developer/domains`, {
|
|
25204
|
+
method: "GET",
|
|
25205
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}` }
|
|
25206
|
+
});
|
|
25207
|
+
if (!res.ok) throw await devRouteError(res, "domains list");
|
|
25208
|
+
const rows = await res.json();
|
|
25209
|
+
if (opts.json) {
|
|
25210
|
+
process.stdout.write(`${formatJson(rows)}
|
|
25211
|
+
`);
|
|
25212
|
+
return 0;
|
|
25213
|
+
}
|
|
25214
|
+
process.stdout.write(
|
|
25215
|
+
"\n" + formatTable(rows, [
|
|
25216
|
+
{ header: "id", get: (d) => d.id },
|
|
25217
|
+
{ header: "domain", get: (d) => d.domain },
|
|
25218
|
+
{ header: "status", get: (d) => d.status },
|
|
25219
|
+
{ header: "verified at", get: (d) => d.verifiedAt }
|
|
25220
|
+
]) + "\n"
|
|
25221
|
+
);
|
|
25222
|
+
return 0;
|
|
25223
|
+
} catch (err) {
|
|
25224
|
+
process.stderr.write(formatCliError(err, { action: "domains list failed" }));
|
|
25225
|
+
return 1;
|
|
25226
|
+
}
|
|
25227
|
+
});
|
|
25228
|
+
}
|
|
25229
|
+
async function runDomainsDelete(opts, deps = {}) {
|
|
25230
|
+
const { id } = opts;
|
|
25231
|
+
if (!opts.force) {
|
|
25232
|
+
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
|
|
25233
|
+
if (!isTty) {
|
|
25234
|
+
process.stderr.write(
|
|
25235
|
+
` \u2716 domains delete needs an interactive confirmation.
|
|
25236
|
+
Re-run with --force to delete '${id}' without a prompt.
|
|
25237
|
+
`
|
|
25238
|
+
);
|
|
25239
|
+
return 2;
|
|
25240
|
+
}
|
|
25241
|
+
const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
|
|
25242
|
+
const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
|
|
25243
|
+
return { prompt: (q) => rl.question(q), close: () => rl.close() };
|
|
25244
|
+
})();
|
|
25245
|
+
try {
|
|
25246
|
+
process.stdout.write(
|
|
25247
|
+
`
|
|
25248
|
+
\u26A0 Deleting domain '${id}' removes its proof-of-control record. The platform refuses this while
|
|
25249
|
+
any active webhook still references it \u2014 disable or delete those first if this fails.
|
|
25250
|
+
|
|
25251
|
+
`
|
|
25252
|
+
);
|
|
25253
|
+
const typed = await promptCtx.prompt(` Type the domain id ('${id}') to confirm: `);
|
|
25254
|
+
if (typed.trim() !== id) {
|
|
25255
|
+
process.stderr.write(` \u2716 Confirmation did not match \u2014 nothing deleted.
|
|
25256
|
+
`);
|
|
25257
|
+
return 1;
|
|
25258
|
+
}
|
|
25259
|
+
} finally {
|
|
25260
|
+
promptCtx.close();
|
|
25261
|
+
}
|
|
25262
|
+
}
|
|
25263
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25264
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25265
|
+
try {
|
|
25266
|
+
const res = await fetchImpl(`${auth.baseUrl.replace(/\/$/, "")}/developer/domains/${encodeURIComponent(id)}`, {
|
|
25267
|
+
method: "DELETE",
|
|
25268
|
+
headers: { Authorization: `Bearer ${auth.cognitoToken}` }
|
|
25269
|
+
});
|
|
25270
|
+
if (!res.ok) throw await devRouteError(res, "domains delete");
|
|
25271
|
+
if (opts.json) {
|
|
25272
|
+
process.stdout.write(`${formatJson({ id, deleted: true })}
|
|
25273
|
+
`);
|
|
25274
|
+
return 0;
|
|
25275
|
+
}
|
|
25276
|
+
process.stdout.write(`
|
|
25277
|
+
\u2714 domain '${id}' deleted
|
|
25278
|
+
`);
|
|
25279
|
+
return 0;
|
|
25280
|
+
} catch (err) {
|
|
25281
|
+
process.stderr.write(formatCliError(err, { action: "domains delete failed", notFoundHint: NOT_FOUND_HINT2 }));
|
|
25282
|
+
return 1;
|
|
25283
|
+
}
|
|
25284
|
+
});
|
|
25285
|
+
}
|
|
25286
|
+
async function runDomainsVerify(opts, deps = {}) {
|
|
25287
|
+
return withAuth({ ...opts, provisioning: true }, deps, async (auth) => {
|
|
25288
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
25289
|
+
try {
|
|
25290
|
+
const res = await fetchImpl(
|
|
25291
|
+
`${auth.baseUrl.replace(/\/$/, "")}/developer/domains/${encodeURIComponent(opts.id)}/verify`,
|
|
25292
|
+
{ method: "POST", headers: { Authorization: `Bearer ${auth.cognitoToken}` } }
|
|
25293
|
+
);
|
|
25294
|
+
if (!res.ok) throw await devRouteError(res, "domains verify");
|
|
25295
|
+
const result = await res.json();
|
|
25296
|
+
const verified = result.verified === true && result.status === "VERIFIED";
|
|
25297
|
+
if (opts.json) {
|
|
25298
|
+
process.stdout.write(`${formatJson(result)}
|
|
25299
|
+
`);
|
|
25300
|
+
return verified ? 0 : 1;
|
|
25301
|
+
}
|
|
25302
|
+
if (!verified) {
|
|
25303
|
+
process.stderr.write(
|
|
25304
|
+
` \u2716 domain '${result.domain ?? opts.id}' is not verified (status: ${result.status ?? "(none)"}).
|
|
25305
|
+
Check the DNS TXT record is published, allow time for it to propagate, then re-run this command.
|
|
25306
|
+
`
|
|
25307
|
+
);
|
|
25308
|
+
return 1;
|
|
25309
|
+
}
|
|
25310
|
+
process.stdout.write(
|
|
25311
|
+
`
|
|
25312
|
+
\u2714 domain '${result.domain ?? opts.id}' verified.
|
|
25313
|
+
${renderDomainVerifyResult(result)}
|
|
25314
|
+
`
|
|
25315
|
+
);
|
|
25316
|
+
return 0;
|
|
25317
|
+
} catch (err) {
|
|
25318
|
+
process.stderr.write(formatCliError(err, { action: "domains verify failed", notFoundHint: NOT_FOUND_HINT2 }));
|
|
25319
|
+
return 1;
|
|
25320
|
+
}
|
|
25321
|
+
});
|
|
25322
|
+
}
|
|
25323
|
+
|
|
24774
25324
|
// src/commands/namespace.ts
|
|
24775
25325
|
function renderNamespace(n) {
|
|
24776
25326
|
return formatKeyValues([
|
|
@@ -24875,7 +25425,7 @@ ${renderNamespace(ns)}
|
|
|
24875
25425
|
|
|
24876
25426
|
// src/commands/scripts.ts
|
|
24877
25427
|
import { readFile as readFile3 } from "fs/promises";
|
|
24878
|
-
import * as
|
|
25428
|
+
import * as readline8 from "readline/promises";
|
|
24879
25429
|
var SOURCE_MAX_BYTES = 3e5;
|
|
24880
25430
|
var INPUT_CONTRACT_MAX_BYTES = 5e4;
|
|
24881
25431
|
var utf8Bytes2 = (s) => new TextEncoder().encode(s).length;
|
|
@@ -25094,7 +25644,7 @@ async function runScriptsDelete(opts, deps = {}) {
|
|
|
25094
25644
|
return 2;
|
|
25095
25645
|
}
|
|
25096
25646
|
const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
|
|
25097
|
-
const rl =
|
|
25647
|
+
const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
|
|
25098
25648
|
return { prompt: (q) => rl.question(q), close: () => rl.close() };
|
|
25099
25649
|
})();
|
|
25100
25650
|
try {
|
|
@@ -26253,9 +26803,9 @@ function emitJoined(opts) {
|
|
|
26253
26803
|
|
|
26254
26804
|
// src/build-info.ts
|
|
26255
26805
|
var BUILD_INFO = {
|
|
26256
|
-
cli: true ? "0.
|
|
26257
|
-
sdk: true ? "0.
|
|
26258
|
-
blueprints: true ? "0.20.
|
|
26806
|
+
cli: true ? "0.24.0" : "dev",
|
|
26807
|
+
sdk: true ? "0.46.0" : "dev",
|
|
26808
|
+
blueprints: true ? "0.20.1" : "dev"
|
|
26259
26809
|
};
|
|
26260
26810
|
function formatBuildInfo() {
|
|
26261
26811
|
return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
|
|
@@ -26292,6 +26842,18 @@ function parseTenantFlag(raw) {
|
|
|
26292
26842
|
}
|
|
26293
26843
|
return raw;
|
|
26294
26844
|
}
|
|
26845
|
+
var NUMBER_FLAG_INVALID = /* @__PURE__ */ Symbol("parseOptionalNumberFlag:invalid");
|
|
26846
|
+
function parseOptionalNumberFlag(flag, raw) {
|
|
26847
|
+
if (raw === void 0) return void 0;
|
|
26848
|
+
const n = Number(raw);
|
|
26849
|
+
if (!Number.isFinite(n)) {
|
|
26850
|
+
process.stderr.write(` \u2716 ${flag} must be a number (got '${raw}')
|
|
26851
|
+
`);
|
|
26852
|
+
process.exitCode = 2;
|
|
26853
|
+
return NUMBER_FLAG_INVALID;
|
|
26854
|
+
}
|
|
26855
|
+
return n;
|
|
26856
|
+
}
|
|
26295
26857
|
function toCommonOptions(flags) {
|
|
26296
26858
|
return {
|
|
26297
26859
|
env: flags.env,
|
|
@@ -26340,6 +26902,8 @@ function buildProgram(deps = {}) {
|
|
|
26340
26902
|
registerRoleCommands(program, deps);
|
|
26341
26903
|
registerContextCommands(program, deps);
|
|
26342
26904
|
registerIssuersCommands(program, deps);
|
|
26905
|
+
registerProviderConfigCommands(program, deps);
|
|
26906
|
+
registerDomainsCommands(program, deps);
|
|
26343
26907
|
registerNamespaceCommands(program, deps);
|
|
26344
26908
|
registerScriptsCommands(program, deps);
|
|
26345
26909
|
registerAssumeCommand(program, deps);
|
|
@@ -26550,7 +27114,10 @@ function registerIssuersCommands(program, deps) {
|
|
|
26550
27114
|
).option("--clear-captured-claims", "capture nothing beyond the email claim (sends an empty list; XOR --captured-claim)").option(
|
|
26551
27115
|
"--restricted-to-domain <domain>",
|
|
26552
27116
|
"scope the registration to a domain already verified for your account; a token with no matching 'hd' claim then stops matching \u2014 omit to leave unchanged. Refused on a registration that is pending verification: verify it first"
|
|
26553
|
-
).option(
|
|
27117
|
+
).option(
|
|
27118
|
+
"--clear-restricted-to-domain",
|
|
27119
|
+
"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"
|
|
27120
|
+
).option("--tenant <tenant>", "tenant the context lives in: 'test' or 'live' (default live)");
|
|
26554
27121
|
addCommonAuthOptions(update).action(
|
|
26555
27122
|
async (issuerId, flags) => {
|
|
26556
27123
|
const tenant = parseTenantFlag(flags.tenant);
|
|
@@ -26611,6 +27178,101 @@ function registerIssuersCommands(program, deps) {
|
|
|
26611
27178
|
}
|
|
26612
27179
|
);
|
|
26613
27180
|
}
|
|
27181
|
+
function registerProviderConfigCommands(program, deps) {
|
|
27182
|
+
const providerConfig = program.command("provider-config").description("Manage BYOM (bring-your-own-model) tenant-level provider configs.");
|
|
27183
|
+
const create = providerConfig.command("create").description("Create a new BYOM provider config (not idempotent \u2014 a repeat --alias 400s).").requiredOption("--alias <alias>", "operator-facing name used to select this config; unique per tenant").requiredOption("--provider-type <type>", "'ANTHROPIC_NATIVE' or 'OPENAI_COMPATIBLE'").option("--endpoint <url>", "required for OPENAI_COMPATIBLE (no default); an optional override for ANTHROPIC_NATIVE").option("--declared-processing-region <region>", "customer-asserted, free-text, Vectros-unverified transparency field").option("--default-provider-model-id <id>", "convenience default only \u2014 a per-request model id, when the caller supplies one, always takes precedence").requiredOption("--api-key-secret-file <path>", "path to a file holding the provider credential ('-' for stdin) \u2014 never pass the secret as a bare flag value").requiredOption("--waiver-type <type>", "the waiver code this config is entitled under (e.g. byo-model-inference:customer-key)").requiredOption("--billing-mode <mode>", "'PLATFORM_BILLED' or 'CUSTOMER_DIRECT'").option("--platform-token-factor <n>", "required when --billing-mode PLATFORM_BILLED; refused otherwise").option("--platform-output-weight <n>", "required when --billing-mode PLATFORM_BILLED; refused otherwise").option("--status <status>", "'ACTIVE' or 'DISABLED' (default ACTIVE) \u2014 ACTIVE requires the tenant already hold a granted waiver for --waiver-type").option("--tenant <tenant>", "tenant to create the config in: 'test' or 'live' (default live)");
|
|
27184
|
+
addCommonAuthOptions(create).action(
|
|
27185
|
+
async (flags) => {
|
|
27186
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27187
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27188
|
+
const platformTokenFactor = parseOptionalNumberFlag("--platform-token-factor", flags.platformTokenFactor);
|
|
27189
|
+
if (platformTokenFactor === NUMBER_FLAG_INVALID) return;
|
|
27190
|
+
const platformOutputWeight = parseOptionalNumberFlag("--platform-output-weight", flags.platformOutputWeight);
|
|
27191
|
+
if (platformOutputWeight === NUMBER_FLAG_INVALID) return;
|
|
27192
|
+
process.exitCode = await runProviderConfigCreate(
|
|
27193
|
+
{
|
|
27194
|
+
...toCommonOptions(flags),
|
|
27195
|
+
alias: flags.alias,
|
|
27196
|
+
providerType: flags.providerType,
|
|
27197
|
+
endpoint: flags.endpoint,
|
|
27198
|
+
declaredProcessingRegion: flags.declaredProcessingRegion,
|
|
27199
|
+
defaultProviderModelId: flags.defaultProviderModelId,
|
|
27200
|
+
apiKeySecretFile: flags.apiKeySecretFile,
|
|
27201
|
+
waiverType: flags.waiverType,
|
|
27202
|
+
billingMode: flags.billingMode,
|
|
27203
|
+
platformTokenFactor,
|
|
27204
|
+
platformOutputWeight,
|
|
27205
|
+
status: flags.status,
|
|
27206
|
+
tenant
|
|
27207
|
+
},
|
|
27208
|
+
deps
|
|
27209
|
+
);
|
|
27210
|
+
}
|
|
27211
|
+
);
|
|
27212
|
+
const list = providerConfig.command("list").description("List provider configs in one tenant.").option("--tenant <tenant>", "tenant to read: 'test' or 'live' (default live)");
|
|
27213
|
+
addCommonAuthOptions(list).action(async (flags) => {
|
|
27214
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
27215
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
27216
|
+
process.exitCode = await runProviderConfigList({ ...toCommonOptions(flags), tenant }, deps);
|
|
27217
|
+
});
|
|
27218
|
+
const get = providerConfig.command("get").description("Show one provider config's metadata (no secret \u2014 the platform never returns it).").argument("<id>");
|
|
27219
|
+
addCommonAuthOptions(get).action(async (id, flags) => {
|
|
27220
|
+
process.exitCode = await runProviderConfigGet({ ...toCommonOptions(flags), id }, deps);
|
|
27221
|
+
});
|
|
27222
|
+
const update = providerConfig.command("update").description("Patch a provider config (partial update \u2014 an omitted field is left unchanged).").argument("<id>").option("--alias <alias>", "rename this config (still unique per tenant)").option("--provider-type <type>", "'ANTHROPIC_NATIVE' or 'OPENAI_COMPATIBLE'").option("--endpoint <url>", "update the provider endpoint").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").option("--waiver-type <type>", "update the waiver code this config is entitled under").option("--billing-mode <mode>", "'PLATFORM_BILLED' or 'CUSTOMER_DIRECT' \u2014 switching to CUSTOMER_DIRECT clears the platform pricing fields server-side").option("--platform-token-factor <n>", "update the platform pricing factor (PLATFORM_BILLED only)").option("--platform-output-weight <n>", "update the platform pricing weight (PLATFORM_BILLED only)").option("--status <status>", "'ACTIVE' or 'DISABLED' \u2014 activating requires a granted waiver for this config's waiverType");
|
|
27223
|
+
addCommonAuthOptions(update).action(
|
|
27224
|
+
async (id, flags) => {
|
|
27225
|
+
const platformTokenFactor = parseOptionalNumberFlag("--platform-token-factor", flags.platformTokenFactor);
|
|
27226
|
+
if (platformTokenFactor === NUMBER_FLAG_INVALID) return;
|
|
27227
|
+
const platformOutputWeight = parseOptionalNumberFlag("--platform-output-weight", flags.platformOutputWeight);
|
|
27228
|
+
if (platformOutputWeight === NUMBER_FLAG_INVALID) return;
|
|
27229
|
+
process.exitCode = await runProviderConfigUpdate(
|
|
27230
|
+
{
|
|
27231
|
+
...toCommonOptions(flags),
|
|
27232
|
+
id,
|
|
27233
|
+
alias: flags.alias,
|
|
27234
|
+
providerType: flags.providerType,
|
|
27235
|
+
endpoint: flags.endpoint,
|
|
27236
|
+
declaredProcessingRegion: flags.declaredProcessingRegion,
|
|
27237
|
+
defaultProviderModelId: flags.defaultProviderModelId,
|
|
27238
|
+
apiKeySecretFile: flags.apiKeySecretFile,
|
|
27239
|
+
waiverType: flags.waiverType,
|
|
27240
|
+
billingMode: flags.billingMode,
|
|
27241
|
+
platformTokenFactor,
|
|
27242
|
+
platformOutputWeight,
|
|
27243
|
+
status: flags.status
|
|
27244
|
+
},
|
|
27245
|
+
deps
|
|
27246
|
+
);
|
|
27247
|
+
}
|
|
27248
|
+
);
|
|
27249
|
+
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)");
|
|
27250
|
+
addCommonAuthOptions(del).action(async (id, flags) => {
|
|
27251
|
+
process.exitCode = await runProviderConfigDelete(
|
|
27252
|
+
{ ...toCommonOptions(flags), id, force: Boolean(flags.force) },
|
|
27253
|
+
deps
|
|
27254
|
+
);
|
|
27255
|
+
});
|
|
27256
|
+
}
|
|
27257
|
+
function registerDomainsCommands(program, deps) {
|
|
27258
|
+
const domains = program.command("domains").description("Manage account-level domain ownership verification (DNS TXT proof-of-control).");
|
|
27259
|
+
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)");
|
|
27260
|
+
addCommonAuthOptions(register).action(async (flags) => {
|
|
27261
|
+
process.exitCode = await runDomainsRegister({ ...toCommonOptions(flags), domain: flags.domain }, deps);
|
|
27262
|
+
});
|
|
27263
|
+
const list = domains.command("list").description("List domains registered for this account (both tenants share this list).");
|
|
27264
|
+
addCommonAuthOptions(list).action(async (flags) => {
|
|
27265
|
+
process.exitCode = await runDomainsList(toCommonOptions(flags), deps);
|
|
27266
|
+
});
|
|
27267
|
+
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)");
|
|
27268
|
+
addCommonAuthOptions(del).action(async (id, flags) => {
|
|
27269
|
+
process.exitCode = await runDomainsDelete({ ...toCommonOptions(flags), id, force: Boolean(flags.force) }, deps);
|
|
27270
|
+
});
|
|
27271
|
+
const verify = domains.command("verify").description("Perform one DNS TXT lookup and report VERIFIED/FAILED \u2014 always re-runnable, never polls.").argument("<id>");
|
|
27272
|
+
addCommonAuthOptions(verify).action(async (id, flags) => {
|
|
27273
|
+
process.exitCode = await runDomainsVerify({ ...toCommonOptions(flags), id }, deps);
|
|
27274
|
+
});
|
|
27275
|
+
}
|
|
26614
27276
|
function registerNamespaceCommands(program, deps) {
|
|
26615
27277
|
const namespace = program.command("namespace").description("Manage scope-namespace registrations (whether scope:<namespace> values resolve to identity entities).");
|
|
26616
27278
|
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");
|