@vectros-ai/cli 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AUTHORING.md CHANGED
@@ -625,14 +625,47 @@ version and compares the stored `source` and `declaredInputContract` against
625
625
  what the blueprint declares, and pushes only on a real difference. So a re-apply
626
626
  of an unedited blueprint writes nothing, and editing `source` stores exactly one
627
627
  new version, which every `version: latest` rule then picks up. The apply summary
628
- reports which of the two happened per script.
629
-
630
- Two consequences worth planning for. A concurrent apply of the same blueprint
631
- can collide on one script name; the loader re-reads and retries once, so two
632
- runners applying identical content converge rather than storing it twice, but a
633
- sustained collision fails the apply and asks you to re-run. And **removing a
634
- `scripts:` entry does not delete anything** — every stored version stays, because
635
- a script carries no record of which blueprint pushed it. Remove one deliberately
628
+ reports which of `created` (no prior version) / `superseded` (this blueprint's
629
+ own prior version, replaced) / `adopted` (see below) / `unchanged` happened per
630
+ script.
631
+
632
+ **A THIRD qualification: pushing onto a name that already has a version is
633
+ gated on OWNING it.** A script name is a flat, per-context namespace shared
634
+ with every other blueprint and every hand-pushed script, so before superseding
635
+ one, the loader checks who pushed the current version:
636
+ - **owned by a different blueprint** — refused outright, no override. Ship
637
+ under a different name instead, or coordinate with whoever owns it.
638
+ - **no owner recorded** (hand-authored via `vectros scripts push`, or pushed
639
+ before this check existed) — refused unless the OPERATOR re-runs
640
+ `bootstrap` with `--allow-script-adoption`. This is deliberately a
641
+ human-supplied CLI flag, never something a blueprint's own YAML/JSON can
642
+ set — the blueprint is untrusted input, and "I already own this name" is not
643
+ something it gets to assert for itself. A successful adopting push reports
644
+ `adopted`, distinct from an ordinary `superseded`.
645
+
646
+ **Know before you rely on this: adoption is safe for an account owner,
647
+ and refused for a Dev Portal sub-user adopting someone else's hand-push.**
648
+ The platform's own authorization still applies underneath this flag, and it
649
+ resolves caller identity differently for the two shapes. An account owner
650
+ always authenticates as the same identity regardless of which login session
651
+ minted the token, so adopting a script you (the owner) hand-pushed earlier —
652
+ or one nobody's identity is recorded against at all — succeeds. A distinct
653
+ team member (a sub-user) resolves to their own, different identity, and the
654
+ platform refuses to supersede an unmarked name pushed by anyone else — so
655
+ one sub-user cannot adopt a script a DIFFERENT sub-user hand-pushed, even
656
+ with this flag. If you hit that, either have the original pusher run the
657
+ adopting apply, or push the script under the account owner's own login
658
+ first.
659
+ - **owned by this SAME blueprint, or the name is brand new** — no flag needed;
660
+ this is the ordinary re-apply / first-push path.
661
+
662
+ Two more consequences worth planning for. A concurrent apply of the same
663
+ blueprint can collide on one script name; the loader re-reads and retries once,
664
+ so two runners applying identical content converge rather than storing it
665
+ twice, but a sustained collision fails the apply and asks you to re-run. And
666
+ **removing a `scripts:` entry does not delete anything** — every stored version
667
+ stays, because there is no second, principal-shaped factor to safely gate a
668
+ DELETE the way a trigger row's provenance marker does. Remove one deliberately
636
669
  with `vectros scripts delete`.
637
670
 
638
671
  ## Distribution
package/CHANGELOG.md CHANGED
@@ -3,6 +3,192 @@
3
3
  All notable changes to `@vectros-ai/cli` are documented here.
4
4
  This project adheres to [Semantic Versioning](https://semver.org).
5
5
 
6
+ ## 0.22.0 — 2026-09-17
7
+
8
+ ### Changed
9
+
10
+ - **⚠️ BREAKING: `vectros bootstrap --blueprint` now refuses to push a script version onto a name it
11
+ cannot show it owns.** Previously, a blueprint declaring `scripts:` could push a new version onto
12
+ ANY existing script name in the target context — including one it never declared, hand-authored, or
13
+ belonging to a different blueprint — with no error. If a pre-existing trigger rule referenced that
14
+ name via `version: 'latest'` (the only form a blueprint-shipped script may use), the newly pushed
15
+ code would run under that rule's own, unrelated grant from its very next firing. An apply that used
16
+ to succeed against such a name now refuses before minting anything: a name already owned by a
17
+ DIFFERENT blueprint refuses unconditionally (push under a different name instead); a name with no
18
+ ownership marker at all (hand-authored, or pushed before this marker existed) refuses unless the new
19
+ `--allow-script-adoption` flag is passed, reporting the outcome as `adopted` rather than
20
+ `superseded`. **This is a MINOR bump, not a patch, for the same reason as the entry above:** an
21
+ operator or CI pipeline that did nothing wrong and passed no new flag can now see an apply that
22
+ previously succeeded fail instead. A blueprint re-applying onto a name it already owns, or shipping
23
+ a brand-new name, is unaffected either way. See `AUTHORING.md`'s *idempotency contract* section for
24
+ the full behavior.
25
+ - **⚠️ BREAKING: `--no-merge` no longer implies an unattended run.** `--no-merge`'s documented
26
+ purpose is only to change where the resulting config goes (printed vs. merged into your
27
+ client's config file) — it still mints/reconciles the same real state either way — but both
28
+ the "Apply this plan?" confirmation and the seed-data opt-out prompt used to treat it as an
29
+ implicit `--yes`, so an operator (or a script) that had been relying on `--no-merge` alone to
30
+ run without either prompt will now be asked, if the session is interactive. **This is a MINOR
31
+ bump, not a patch, for the same reason as `@vectros-ai/blueprints`' own bump below: narrowing
32
+ an UNATTENDED-RUN contract is a breaking change for a script or wrapper that passes no new
33
+ flag, arguably a sharper break than narrowing an input contract, since the caller did nothing
34
+ wrong and gets no error — it just now blocks on a prompt it never used to see.** Pass `--yes`
35
+ explicitly if you need `--no-merge` to keep running unattended; a genuinely non-interactive
36
+ session (no TTY — the common case for CI) is unaffected either way, since neither prompt ever
37
+ fires there. The help text is corrected to say so.
38
+
39
+ ### Security
40
+
41
+ - **`vectros login`'s persisted Cognito refresh token could be POSTed to an attacker-controlled
42
+ host.** `VECTROS_COGNITO_DOMAIN` (and `VECTROS_COGNITO_REGION`, interpolated into the default
43
+ domain) were read verbatim from the environment and never validated — unlike the partner-API base
44
+ URL, which already goes through the same credential-exfil guard (`validateBaseUrl`). A developer
45
+ with an env var influenced by a repo-scoped config they never read as executable (a `.envrc`, a
46
+ devcontainer, a CI variable, an `.mcp.json` snippet) could have their long-lived Cognito refresh
47
+ token — and, on the interactive flow, the authorization code + PKCE verifier — sent to that host
48
+ on the very next creds-bearing command, with no prompt and no warning.
49
+
50
+ The Cognito domain now goes through `validateBaseUrl` too, extended with an allow-list entry for
51
+ the two exact Cognito hosted-UI hosts this CLI actually talks to (the Cognito domain is never a
52
+ `vectros.ai` host, so it needed its own entry rather than the partner-API allow-list unchanged —
53
+ and a broad `*.amazoncognito.com` suffix was deliberately rejected as too wide: that apex is
54
+ AWS's shared namespace for every Cognito user pool of every AWS customer, not a Vectros-owned
55
+ one); `VECTROS_COGNITO_REGION` is checked against an AWS-region shape before it is ever interpolated
56
+ into a URL. Only affects anyone who has run `vectros login` and does not use
57
+ `--token`/`VECTROS_BOOTSTRAP_TOKEN`.
58
+
59
+ **This narrows the vector; it does not close it against an attacker with unconstrained
60
+ environment control.** `VECTROS_ALLOW_INSECURE_BASE_URL=1` still opts out of the new allow-list
61
+ (loudly — a warning is logged), same as the pre-existing partner-API base-url guard it mirrors —
62
+ so an attacker able to set `VECTROS_COGNITO_DOMAIN` can, in principle, also set that opt-out
63
+ variable in the same reach. The fix protects the common case (an env var landing without the
64
+ reach to also flip a second, differently-named one) but is not a guarantee against an attacker
65
+ who already controls the whole environment.
66
+ - **A blueprint-authored free-text field with no character-set constraint could forge a fake
67
+ extra line, hide/reorder text, or otherwise misrepresent what `vectros bootstrap --blueprint`'s
68
+ pre-apply plan preview AND its identity-disclosure/confirmation prompt show an operator before
69
+ they approve a real mint** — a raw newline/CR, an ANSI escape sequence, a Unicode line/paragraph
70
+ separator, a bidi-override/isolate/mark character, or an invisible/zero-width character embedded
71
+ in a value like a role's `allowedActions`/`dataScope`, a declared identity's `externalId`
72
+ (printed immediately before the "Apply this plan?" gate), or a blueprint's own `name`/
73
+ `contextName` could make the rendered plan (or the confirmation prompt itself) show something
74
+ other than what would actually be provisioned. **A separate, stronger check now also closes the
75
+ HOMOGLYPH class for an issuer's `issuer`/`jwksUri`/`userinfoUri`** — a Unicode look-alike domain
76
+ (e.g. a Cyrillic character standing in for a Latin one), invisible to any character blocklist —
77
+ by requiring those three fields to be printable-ASCII `https://` URLs whose AUTHORITY contains
78
+ only letters, digits, dot, dash, and an optional port (no userinfo, no percent-encoding, no
79
+ non-ASCII host); an internationalized domain must be supplied in its already-punycode (`xn--…`)
80
+ form. `http://` is explicitly rejected — the platform hard-refuses a non-`https` issuer at
81
+ registration, so accepting it here would repeat this ecosystem's own "lints clean, then 400s at
82
+ apply" anti-pattern for the exact field this check exists to catch problems in beforehand.
83
+
84
+ Requires **`@vectros-ai/blueprints@0.19.0`** (a MINOR bump, not patch — this narrows what the
85
+ library accepts, which is a breaking change for a validation library's published contract, even
86
+ though this repo's own blueprints are unaffected; see that package's own CHANGELOG entry for the
87
+ full field list, the named residuals of the URL check — a trailing-dot host and a bare IPv4
88
+ literal still pass, a bracketed IPv6 literal is rejected as an accepted side effect — and,
89
+ importantly, what is deliberately NOT covered — length is still unbounded, and the homoglyph
90
+ class stays open for non-URL fields like `name`/`contextName`/`fieldId`, since no enumerable
91
+ blocklist can close it there the way a positive URL-authority grammar can for a URL). The schema
92
+ closes both classes at parse time, once, for every current and future rendered surface, rather
93
+ than requiring each renderer to sanitize its own inputs. This preview's own per-field
94
+ sanitization (`jwksUri`/`userinfoUri`/`issuer`/`capturedClaims`, added in the previous entry
95
+ below) stays in place as a second line of defense for a caller that reaches `buildPlan` without
96
+ going through the parser — which is how every bundled/first-party blueprint (`--blueprint
97
+ task-management`, etc.) is loaded today.
98
+ - **The plan preview for `vectros bootstrap --blueprint` omitted an issuer's trust-anchor
99
+ fields.** `jwksUri` (whoever controls it can mint identities the platform accepts) and, when
100
+ set, `userinfoUri` (fetched with the presented token as bearer) were never rendered — an
101
+ operator approving the plan for a pack declaring a malicious issuer saw its context and a
102
+ self-signup policy count, never the field that actually decides trust. Both are now always
103
+ disclosed, and a self-signup policy is now listed by its actual `signupType→roleId` pairing
104
+ instead of a bare count, matching the standard the preview already applies to captured
105
+ identity-provider claims.
106
+
107
+ ### Fixed
108
+
109
+ - **A degraded credential store (file-backed, not your OS keychain) now warns — at write time,
110
+ and now at read time too — for BOTH ways this can be a fixable install accident, not just
111
+ one.** Previously: the optional `@napi-rs/keyring` package loading but being BROKEN (a
112
+ version/ABI mismatch, a corrupt native binary, an unrecognised module shape) already warned;
113
+ but the package being entirely ABSENT was always treated as an ordinary, nothing-to-fix
114
+ platform limitation — even on a platform/arch this CLI ships a prebuilt native binary for,
115
+ where the only way to end up without it is an npm install that silently skipped the optional
116
+ dependency. Both throw the identical `MODULE_NOT_FOUND` error, so telling them apart needed a
117
+ new check: whether a prebuilt is actually known to exist for the current `process.platform`/
118
+ `process.arch`, read from `@napi-rs/keyring`'s own published platform matrix — consulted
119
+ regardless of what the underlying failure looks like, not only for a recognised
120
+ `MODULE_NOT_FOUND`-shaped error (a genuinely-unsupported platform's own failure can carry no
121
+ error code at all, and every other error/code was previously treated as an install accident
122
+ unconditionally, which produced permanent, un-fixable "try reinstalling" noise on exactly the
123
+ platforms this feature must stay silent on). Now, a write about to land in the weaker store,
124
+ or a read of an entry already stamped for the OS keychain, prints one warning naming the
125
+ actual cause and the remedy on EITHER fixable path — install failure or silent absence —
126
+ while a genuinely-unsupported platform/arch stays quiet, whatever shape its failure takes.
127
+ **One accepted, disclosed residual:** this can't distinguish glibc from musl on Linux — `arm`
128
+ and `riscv64` each have a real GNU-libc prebuilt (so a glibc machine there is fully covered),
129
+ but neither has a MUSL prebuilt, and this check cannot tell the two libcs apart. A musl
130
+ machine on `arm`/`riscv64` will still get an unnecessary warning naming a remedy that won't
131
+ help — a false positive, the direction this control is meant to err in, not a silent miss.
132
+ - **`bootstrap --rotate` could no longer recover once the local key record it needs was itself
133
+ the thing missing.** `--rotate` revoked a prior key only by a `keyId` read from this machine's
134
+ own local record — so when that record was lost (the exact situation `--rotate` exists to
135
+ recover from), nothing was revoked, the subsequent mint hit the same already-exists response,
136
+ and the CLI re-threw the identical error telling the caller to run `--rotate` — which they just
137
+ had. `--rotate` now falls back to resolving the key to revoke server-side (listing this
138
+ principal's active keys in the context and matching by name) when no local record exists. The
139
+ error message for the case that still can't be resolved now points at `vectros key list` /
140
+ `vectros key revoke` instead of repeating the same `--rotate` advice a second time.
141
+ - **Any verb (not just credential minting) could abort the whole process with a native Windows
142
+ crash instead of the CLI's own exit code.** Every verb ended with `process.exit(await
143
+ runVerb(...))`; on Windows, a `fetch` still settling when `process.exit()` runs races libuv and
144
+ aborts the process, printing an `Assertion failed` line and replacing the real exit code with
145
+ `127` — which reads to a script, CI job, or wrapping agent as "command not found" rather than as
146
+ the CLI's own, already-printed, correctly-diagnosed failure (a bad `--context`, a suspended
147
+ member, an expired/invalid bearer, and so on). Every verb now sets `process.exitCode` and lets
148
+ the event loop drain naturally instead of hard-exiting, the same idiom `role`/`access` already
149
+ used for their own top-level exit.
150
+
151
+ **A related hazard was introduced by this conversion itself, and closed in the same pass —
152
+ not a pre-existing bug.** A shared helper, `parseTenantFlag()`, validates `--tenant` for 24 call
153
+ sites (`role`, `access`, `scripts`, `issuers`, `key`, `identity list-all`) and used to call
154
+ `process.exit(2)` directly: an unconditional stop from any call-stack depth, so no released
155
+ version could ever fall through past it. Converting the helper to the same `process.exitCode`
156
+ idiom broke that guarantee, because `exitCode` only unwinds the helper's own stack frame — a
157
+ caller that didn't check it after calling the helper would silently continue into the real
158
+ command with an invalid `--tenant` (defaulting to the **live** tenant, since `undefined` is also
159
+ this helper's valid "no `--tenant` given" result). All 24 call sites were audited and given an
160
+ explicit check immediately after the call, so no release ever shipped the fall-through — but it
161
+ is why this entry calls it out as a hazard of the refactor, not a latent defect the refactor
162
+ happened to discover.
163
+
164
+ - **Eight source comments named internal backend classes/methods, or an internal gotcha filename,
165
+ by name** (`src/commands/issuers.ts`, `src/loader.ts` x3, `src/scope-gate.ts`, `src/scope-grammar.ts`,
166
+ `src/keyring.ts`, `src/os-keychain.ts`). This package has no GitHub source mirror, but `dist/`'s
167
+ sourcemaps embed the original source comments verbatim, so all eight shipped in every npm
168
+ tarball. Reworded to describe the mechanism without the identifier; no behavioral change.
169
+
170
+ - **Six more shipped sources named a sibling package by monorepo-relative path or an internal
171
+ process stage** (`src/base-url.ts`'s doc comment, `src/scope-gate.ts` x3 — a backend method
172
+ name and two sibling-package references, one of them also a monorepo path —
173
+ `THIRD-PARTY-NOTICES.md`'s generation note naming an internal build-script path, and
174
+ `src/harness.ts`'s "the Tier-2 panel caught three times" wording) — found by a post-merge
175
+ audit. All reworded to describe the mechanism/sibling generically, keeping the substantive
176
+ rationale; no behavioral change.
177
+
178
+ - **`scripts delete`'s pre-delete warning no longer claims the platform doesn't check whether a
179
+ live trigger rule still references the version being deleted.** The platform now refuses that
180
+ delete (409, naming the offending rule) instead of silently allowing it; the warning described
181
+ the older, unguarded behavior. No behavior changed — the delete itself already surfaced whatever
182
+ the platform returned either way; only the pre-emptive warning text was stale.
183
+
184
+ ### Docs
185
+
186
+ - **`vectros issuers register`/`update`'s flag tables now document three refusal conditions
187
+ those commands can hit.** `--jwks-uri`/`--userinfo-uri` must be `https://` (an `http://` value
188
+ is refused with 400); a tenant is capped at 50 registered issuers; and a `--self-signup-policy`
189
+ naming a role that authors, updates, or deletes scripts is refused. No behavior changed — the
190
+ CLI already surfaced whatever the platform returned; the tables just didn't mention these yet.
191
+
6
192
  ## 0.21.0 — 2026-09-07
7
193
 
8
194
  ### Added
@@ -326,7 +512,7 @@ This project adheres to [Semantic Versioning](https://semver.org).
326
512
  install path) used to print only on `--no-merge` / a declined / a failed
327
513
  merge — a successful auto-merge silently hid it; it's now shown there too.
328
514
  - Scrubbed internal issue/MR references, review-process citations, and
329
- backend class names out of `packages/cli`'s comments — visible in the
515
+ backend class names out of this package's comments — visible in the
330
516
  published npm package's sourcemaps to anyone who reads mapped source.
331
517
  Comment-only; no behavior change.
332
518
  - **Keyring secret-store hardening.** A 0-byte salt file removed by
package/README.md CHANGED
@@ -174,8 +174,8 @@ vectros issuers delete primary --context my-app # re-types the id to confirm
174
174
 
175
175
  | Subcommand | Flags | Notes |
176
176
  |---|---|---|
177
- | `register` | `--context`, `--issuer-id`, `--issuer`, `--jwks-uri`, `--audience` (all required); `--sub-claim`, `--email-claim`, `--userinfo-uri`, `--self-signup-policy <type:roleId>` (repeatable), `--captured-claim <name>` (repeatable), `--tenant test\|live` | Idempotent by `--issuer-id` within the tenant — a repeat returns the existing registration unchanged, not an error. `--captured-claim` names additional OIDC claims to capture beyond the email claim on every successful token exchange, stored as your tenant's golden identity-provider-asserted copy for that user; omit it to capture nothing beyond email. At most 20 names of at most 64 characters, no duplicates. |
178
- | `update <issuerId>` | `--context <contextId>` (required); `--sub-claim`, `--email-claim`, `--userinfo-uri`, `--status active\|suspended`, `--self-signup-policy <type:roleId>` (repeatable), `--clear-self-signup-policies`, `--captured-claim <name>` (repeatable), `--clear-captured-claims`, `--tenant test\|live` | Omitting `--captured-claim` leaves the stored list UNCHANGED; `--clear-captured-claims` sends an explicit empty list (capture nothing beyond email). The two are mutually exclusive, and the same absent-vs-clear split applies to `--self-signup-policy`. Only ever sends the SAFE, mutable fields — `issuer`/`jwks-uri`/`audience`/`contextId` are trust-anchor fields, immutable via this route on the platform side; rotating one is `delete` + `register`. |
177
+ | `register` | `--context`, `--issuer-id`, `--issuer`, `--jwks-uri`, `--audience` (all required); `--sub-claim`, `--email-claim`, `--userinfo-uri`, `--self-signup-policy <type:roleId>` (repeatable), `--captured-claim <name>` (repeatable), `--tenant test\|live` | Idempotent by `--issuer-id` within the tenant — a repeat returns the existing registration unchanged, not an error. `--captured-claim` names additional OIDC claims to capture beyond the email claim on every successful token exchange, stored as your tenant's golden identity-provider-asserted copy for that user; omit it to capture nothing beyond email. At most 20 names of at most 64 characters, no duplicates. `--jwks-uri`/`--userinfo-uri` must be `https://` — an `http://` value is refused with 400. A tenant may hold at most 50 registrations at once; registering past that cap is also refused with 400. A `--self-signup-policy` naming a role that authors, updates, or deletes scripts (`scripts:c`/`u`/`d`) is refused — a self-signup role may still read or execute scripts. |
178
+ | `update <issuerId>` | `--context <contextId>` (required); `--sub-claim`, `--email-claim`, `--userinfo-uri`, `--status active\|suspended`, `--self-signup-policy <type:roleId>` (repeatable), `--clear-self-signup-policies`, `--captured-claim <name>` (repeatable), `--clear-captured-claims`, `--tenant test\|live` | Omitting `--captured-claim` leaves the stored list UNCHANGED; `--clear-captured-claims` sends an explicit empty list (capture nothing beyond email). The two are mutually exclusive, and the same absent-vs-clear split applies to `--self-signup-policy`. Only ever sends the SAFE, mutable fields — `issuer`/`jwks-uri`/`audience`/`contextId` are trust-anchor fields, immutable via this route on the platform side; rotating one is `delete` + `register`. `--sub-claim` is refused with 400 once any user has ever bound through this issuer — supplying the value it already has is still a no-op. `--userinfo-uri` must be `https://`, same as at registration. |
179
179
  | `delete <issuerId>` | `--context <contextId>` (required), `--force`, `--tenant test\|live` | Re-types the issuer id to confirm (mirrors `context destroy`); `--force` skips the prompt and is required when stdin isn't a TTY. Refused server-side while any user was ever created or matched through this issuer. |
180
180
  | `list` | `--context <contextId>` (required), `--tenant test\|live` | Every issuer registered in that context. `--context` has no default — an issuer read is confined to the context you name; there's no tenant-wide "every issuer in every context" view. |
181
181
  | `get <issuerId>` | `--context <contextId>` (required), `--tenant test\|live` | One issuer's full registration, including its self-signup policies and captured claims. |
@@ -311,12 +311,10 @@ rejects. Supersede a version by pushing a new one.
311
311
  the rule that wanted it still unresolvable. `--tenant` defaults to `live`; pass `--tenant test` to
312
312
  reach the versions a `blueprint-test --tenant test` run created.
313
313
 
314
- ⚠️ **`delete` is not reference-checked.** Nothing verifies whether a trigger rule still points at the
315
- version being removed, and the two outcomes differ: a rule pinned to that version fails on every
316
- subsequent firing, while a rule on `version: latest` silently resolves to the PREVIOUS version and
317
- carries on running older code with nothing to indicate it. This CLI has no verb that lists trigger
318
- rules, so it cannot answer that question for you — check against the API before removing a version a
319
- rule may still use.
314
+ ⚠️ **`delete` is permanent — the stored source is the only copy.** The platform refuses the delete
315
+ (409, naming the rule) if a trigger rule still references the version being removed — directly, or
316
+ via `version: latest` when the version is the current newest of its name — so it either succeeds
317
+ cleanly or is refused with an explanation, never silently.
320
318
 
321
319
  A blueprint can ship a script instead of pushing one — see `scripts:` in
322
320
  [`@vectros-ai/blueprints`](https://www.npmjs.com/package/@vectros-ai/blueprints). An apply is
@@ -4,9 +4,8 @@
4
4
  published bundle. Their licenses require that these notices travel with the code,
5
5
  so they are reproduced in full below.
6
6
 
7
- This file is GENERATED by `pipelines/third_party_notices.mjs` from `packages/cli/tsup.config.ts`'s
8
- `noExternal` list. Do not edit it by hand — run
9
- `node pipelines/third_party_notices.mjs --write`. CI fails if it is stale, because a
7
+ This file is GENERATED from `tsup.config.ts`'s `noExternal` list by an internal build
8
+ script. Do not edit it by hand. CI fails if it is stale, because a
10
9
  notices file that silently drifts is worse than none: it asserts a compliance that
11
10
  nobody checked.
12
11