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