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