@m-kopa/launchpad-cli 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +854 -0
- package/README.md +109 -0
- package/dist/auth/browser.d.ts +18 -0
- package/dist/auth/browser.d.ts.map +1 -0
- package/dist/auth/callback-server.d.ts +24 -0
- package/dist/auth/callback-server.d.ts.map +1 -0
- package/dist/auth/discovery.d.ts +25 -0
- package/dist/auth/discovery.d.ts.map +1 -0
- package/dist/auth/flow.d.ts +39 -0
- package/dist/auth/flow.d.ts.map +1 -0
- package/dist/auth/jwt.d.ts +27 -0
- package/dist/auth/jwt.d.ts.map +1 -0
- package/dist/auth/pkce.d.ts +26 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/registration.d.ts +8 -0
- package/dist/auth/registration.d.ts.map +1 -0
- package/dist/auth/session.d.ts +54 -0
- package/dist/auth/session.d.ts.map +1 -0
- package/dist/auth/token.d.ts +37 -0
- package/dist/auth/token.d.ts.map +1 -0
- package/dist/bundle/cron-bundle.d.ts +77 -0
- package/dist/bundle/cron-bundle.d.ts.map +1 -0
- package/dist/bundle/cwd-walker.d.ts +43 -0
- package/dist/bundle/cwd-walker.d.ts.map +1 -0
- package/dist/bundle/orchestrate.d.ts +51 -0
- package/dist/bundle/orchestrate.d.ts.map +1 -0
- package/dist/bundle/upload.d.ts +66 -0
- package/dist/bundle/upload.d.ts.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +9757 -0
- package/dist/clone/git-init.d.ts +18 -0
- package/dist/clone/git-init.d.ts.map +1 -0
- package/dist/clone/tar-extract.d.ts +59 -0
- package/dist/clone/tar-extract.d.ts.map +1 -0
- package/dist/commands/apps.d.ts +14 -0
- package/dist/commands/apps.d.ts.map +1 -0
- package/dist/commands/channel-auth.d.ts +31 -0
- package/dist/commands/channel-auth.d.ts.map +1 -0
- package/dist/commands/clone.d.ts +3 -0
- package/dist/commands/clone.d.ts.map +1 -0
- package/dist/commands/create.d.ts +27 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/deploy-flags.d.ts +75 -0
- package/dist/commands/deploy-flags.d.ts.map +1 -0
- package/dist/commands/deploy-modes.d.ts +59 -0
- package/dist/commands/deploy-modes.d.ts.map +1 -0
- package/dist/commands/deploy.d.ts +29 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/destroy.d.ts +14 -0
- package/dist/commands/destroy.d.ts.map +1 -0
- package/dist/commands/envvars.d.ts +28 -0
- package/dist/commands/envvars.d.ts.map +1 -0
- package/dist/commands/generate.d.ts +3 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/groups-whoami.d.ts +3 -0
- package/dist/commands/groups-whoami.d.ts.map +1 -0
- package/dist/commands/groups.d.ts +3 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/init.d.ts +44 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logs.d.ts +16 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/merge.d.ts +29 -0
- package/dist/commands/merge.d.ts.map +1 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.d.ts.map +1 -0
- package/dist/commands/pull.d.ts +12 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/review.d.ts +22 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/rollback.d.ts +3 -0
- package/dist/commands/rollback.d.ts.map +1 -0
- package/dist/commands/secrets-template.d.ts +3 -0
- package/dist/commands/secrets-template.d.ts.map +1 -0
- package/dist/commands/secrets.d.ts +3 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/skills.d.ts +13 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/status.d.ts +54 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/update.d.ts +114 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/validate.d.ts +3 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/deploy/apply.d.ts +29 -0
- package/dist/deploy/apply.d.ts.map +1 -0
- package/dist/deploy/dry-run.d.ts +13 -0
- package/dist/deploy/dry-run.d.ts.map +1 -0
- package/dist/deploy/git-files.d.ts +33 -0
- package/dist/deploy/git-files.d.ts.map +1 -0
- package/dist/deploy/group-pin.d.ts +66 -0
- package/dist/deploy/group-pin.d.ts.map +1 -0
- package/dist/deploy/manifest-state.d.ts +20 -0
- package/dist/deploy/manifest-state.d.ts.map +1 -0
- package/dist/deploy/manifest-status.d.ts +11 -0
- package/dist/deploy/manifest-status.d.ts.map +1 -0
- package/dist/deploy/resolve.d.ts +53 -0
- package/dist/deploy/resolve.d.ts.map +1 -0
- package/dist/deploy/rollback.d.ts +23 -0
- package/dist/deploy/rollback.d.ts.map +1 -0
- package/dist/deploy/runner.d.ts +29 -0
- package/dist/deploy/runner.d.ts.map +1 -0
- package/dist/deploy/stage-exit-codes.d.ts +41 -0
- package/dist/deploy/stage-exit-codes.d.ts.map +1 -0
- package/dist/deploy/status-polling.d.ts +37 -0
- package/dist/deploy/status-polling.d.ts.map +1 -0
- package/dist/deploy/tar-pack.d.ts +22 -0
- package/dist/deploy/tar-pack.d.ts.map +1 -0
- package/dist/detect/index.d.ts +53 -0
- package/dist/detect/index.d.ts.map +1 -0
- package/dist/dispatcher.d.ts +30 -0
- package/dist/dispatcher.d.ts.map +1 -0
- package/dist/groups/client.d.ts +62 -0
- package/dist/groups/client.d.ts.map +1 -0
- package/dist/http/api-client.d.ts +33 -0
- package/dist/http/api-client.d.ts.map +1 -0
- package/dist/http/errors.d.ts +31 -0
- package/dist/http/errors.d.ts.map +1 -0
- package/dist/manifest/load.d.ts +38 -0
- package/dist/manifest/load.d.ts.map +1 -0
- package/dist/manifest/schema.d.ts +3 -0
- package/dist/manifest/schema.d.ts.map +1 -0
- package/dist/postinstall.d.ts +3 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +37 -0
- package/dist/secrets/env-parse.d.ts +19 -0
- package/dist/secrets/env-parse.d.ts.map +1 -0
- package/dist/secrets/push.d.ts +13 -0
- package/dist/secrets/push.d.ts.map +1 -0
- package/dist/secrets/set.d.ts +19 -0
- package/dist/secrets/set.d.ts.map +1 -0
- package/dist/secrets/status.d.ts +19 -0
- package/dist/secrets/status.d.ts.map +1 -0
- package/dist/types/api.d.ts +112 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/update-notifier.d.ts +69 -0
- package/dist/update-notifier.d.ts.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +62 -0
- package/skills/README.md +100 -0
- package/skills/_partials/shell-contract.md +42 -0
- package/skills/launchpad-content-pr/SKILL.md +255 -0
- package/skills/launchpad-deploy/SKILL.md +415 -0
- package/skills/launchpad-deploy-status/SKILL.md +231 -0
- package/skills/launchpad-destroy/SKILL.md +317 -0
- package/skills/launchpad-onboard/SKILL.md +179 -0
- package/skills/launchpad-status/SKILL.md +263 -0
- package/skills/marquee-share/README.md +155 -0
- package/skills/marquee-share/SKILL.md +94 -0
- package/skills/marquee-share/SYNC.md +27 -0
- package/skills/marquee-share/dist/cli.js +896 -0
- package/skills/marquee-share/eslint.config.mjs +71 -0
- package/skills/marquee-share/install.sh +103 -0
- package/skills/marquee-share/package-lock.json +3946 -0
- package/skills/marquee-share/package.json +30 -0
- package/skills/marquee-share/src/auth/PROVENANCE.md +103 -0
- package/skills/marquee-share/src/auth/browser.ts +75 -0
- package/skills/marquee-share/src/auth/callback-server.ts +171 -0
- package/skills/marquee-share/src/auth/discovery.ts +171 -0
- package/skills/marquee-share/src/auth/flow.ts +262 -0
- package/skills/marquee-share/src/auth/index.ts +171 -0
- package/skills/marquee-share/src/auth/jwt.ts +77 -0
- package/skills/marquee-share/src/auth/pkce.ts +79 -0
- package/skills/marquee-share/src/auth/registration.ts +87 -0
- package/skills/marquee-share/src/auth/session.ts +205 -0
- package/skills/marquee-share/src/auth/token.ts +162 -0
- package/skills/marquee-share/src/cli.ts +246 -0
- package/skills/marquee-share/src/config.ts +101 -0
- package/skills/marquee-share/src/render/template.ts +171 -0
- package/skills/marquee-share/src/upload/index.ts +11 -0
- package/skills/marquee-share/src/upload/upload.ts +191 -0
- package/skills/marquee-share/tests/cli.test.ts +281 -0
- package/skills/marquee-share/tests/config.test.ts +119 -0
- package/skills/marquee-share/tests/flow.test.ts +356 -0
- package/skills/marquee-share/tests/no-token-leak.test.ts +240 -0
- package/skills/marquee-share/tests/pkce.test.ts +121 -0
- package/skills/marquee-share/tests/session.test.ts +173 -0
- package/skills/marquee-share/tests/template.test.ts +170 -0
- package/skills/marquee-share/tests/upload.test.ts +311 -0
- package/skills/marquee-share/tsconfig.json +23 -0
- package/skills/marquee-share/vitest.config.ts +15 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,854 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@m-kopa/launchpad-cli` are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html);
|
|
7
|
+
pre-1.0 minor bumps may carry breaking changes per ADR 0005.
|
|
8
|
+
|
|
9
|
+
## 0.23.0 — 2026-06-11
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **`--allowed-group` accepts an Entra Object-ID UUID** (sp-grpid1 / ADR 0024),
|
|
14
|
+
not just a symbolic key (`G_Product_Security`), on both `launchpad create` and
|
|
15
|
+
`launchpad deploy --new`. The bot resolves and validates either form against the
|
|
16
|
+
app-assigned group set at submit (and rejects a typo'd / unassigned group with a
|
|
17
|
+
clear error before provisioning).
|
|
18
|
+
|
|
19
|
+
## 0.22.0 — 2026-06-11
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **`launchpad status` shows provisioning progress** (sp-st5hw9). Status now
|
|
24
|
+
queries the app's lifecycle first, so a mid-provision app reads as
|
|
25
|
+
`provisioning (stage: …)` and a hard failure as `provisioning FAILED at stage
|
|
26
|
+
…: <reason>` — instead of both showing the misleading "no deployed manifest
|
|
27
|
+
yet". A live app still diffs local-vs-deployed (`live, in sync` / drift), and a
|
|
28
|
+
live app with no content deployed reads as `live, no content yet`.
|
|
29
|
+
Provisioning/failed render without a local `launchpad.yaml` (no more ENOENT
|
|
30
|
+
mid-provision). `--json` carries a discriminated `state`. Degrades gracefully
|
|
31
|
+
against an older bot.
|
|
32
|
+
- **`launchpad secrets status` accepts a `[<slug>]` positional** (AC0), matching
|
|
33
|
+
`launchpad status` — instead of erroring "unknown argument". A slug that
|
|
34
|
+
doesn't match the directory's manifest is a clear error.
|
|
35
|
+
|
|
36
|
+
## 0.21.0 — 2026-06-10
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **React+api (pages) apps can declare a D1 database** (sp-pgd1b7 / ADR 0022
|
|
41
|
+
amendment). `d1_binding` is now valid on a `pages` target in `launchpad.yaml`,
|
|
42
|
+
not just a `worker` target — so a pure react+api app (no cron Worker) can
|
|
43
|
+
declare its database and `launchpad deploy` create-or-adopts the shared
|
|
44
|
+
`<slug>` D1 and binds it to the Pages app (`env.<BINDING>`), with no manual
|
|
45
|
+
`wrangler d1 create`. `schedule` remains worker-only (cron is Workers-only).
|
|
46
|
+
The provisioned DB is empty — schema init stays the app's responsibility.
|
|
47
|
+
(Requires this CLI version: the manifest is validated client-side against the
|
|
48
|
+
bundled engine schema, so an older CLI rejects a pages `d1_binding` locally.)
|
|
49
|
+
|
|
50
|
+
## 0.20.0 — 2026-06-10
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **`launchpad update` self-updates through the auth gateway** (sp-gwpub1 /
|
|
55
|
+
ADR 0023). The platform CLI distribution channel
|
|
56
|
+
(`get.launchpad.m-kopa.us`) is moving from Cloudflare Access to the
|
|
57
|
+
platform Entra-OIDC gateway, so the previous `cloudflared access curl`
|
|
58
|
+
self-update path no longer applies. `launchpad update` now signs in with a
|
|
59
|
+
**loopback browser flow** (Authorization-Code + PKCE): it binds a localhost
|
|
60
|
+
listener, opens your browser to sign in once, exchanges a short-lived
|
|
61
|
+
installer token, fetches the still-staff-gated `install.sh`, and runs it
|
|
62
|
+
(shebang- + SHA-256-verified). On a headless/SSH host with no browser it
|
|
63
|
+
prints the browser-installer instruction instead of erroring.
|
|
64
|
+
`launchpad update --check` is unchanged (anonymous `version.json`).
|
|
65
|
+
Targeted failure guidance: no browser → "update in a browser at
|
|
66
|
+
get.launchpad.m-kopa.us"; sign-in didn't complete → retry or browser.
|
|
67
|
+
|
|
68
|
+
> **Cutover note:** the loopback flow activates once
|
|
69
|
+
> `get.launchpad.m-kopa.us` is served by the gateway. Until that cutover
|
|
70
|
+
> completes, `launchpad update` on this version falls back to the
|
|
71
|
+
> browser-installer message; `--check` and fresh installs are unaffected.
|
|
72
|
+
|
|
73
|
+
## 0.18.0 — 2026-05-31
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
|
|
77
|
+
- **`launchpad update` now self-updates on the platform channel** — no
|
|
78
|
+
hand-downloading shell scripts. The platform installer lives behind
|
|
79
|
+
Cloudflare Access, so a public package manager can't reach it; instead
|
|
80
|
+
the command runs the Access-gated `install.sh` through the user's
|
|
81
|
+
`cloudflared` session (`cloudflared access curl … | bash`), which is
|
|
82
|
+
self-contained + SHA-256 self-verifying. Targeted guidance on failure:
|
|
83
|
+
`no-cloudflared` → `brew install cloudflared`; no Access session →
|
|
84
|
+
`cloudflared access login https://get.launchpad.m-kopa.us`; installer
|
|
85
|
+
error → manual-install fallback. The GitHub-Packages channel is
|
|
86
|
+
unchanged (`npm i -g`). (One-time bootstrap: an install older than
|
|
87
|
+
0.18.0 still shows the old "re-run the installer" message — update once
|
|
88
|
+
to 0.18.0+ and `launchpad update` is self-driving thereafter.)
|
|
89
|
+
|
|
90
|
+
## 0.17.0 — 2026-05-31
|
|
91
|
+
|
|
92
|
+
### Added
|
|
93
|
+
|
|
94
|
+
- **Passive update notifier.** After any command, the CLI prints a
|
|
95
|
+
one-line notice to **stderr** when a newer version is published —
|
|
96
|
+
e.g. `▲ launchpad 0.17.0 → 0.18.0 available — …`. It reads a cached
|
|
97
|
+
marker (`~/.launchpad/update-check.json`) synchronously on the hot
|
|
98
|
+
path and refreshes it in a **detached background process** at most
|
|
99
|
+
once per 24h, so it never slows, blocks, or breaks a command. The
|
|
100
|
+
notice is channel-aware (GitHub Packages → `launchpad update`;
|
|
101
|
+
platform channel → the `get.launchpad.m-kopa.us` installer) and is
|
|
102
|
+
suppressed on non-interactive stderr, in CI, under
|
|
103
|
+
`LAUNCHPAD_NO_UPDATE_NOTIFIER`, with `--json`, and for the `update`
|
|
104
|
+
command itself. Output goes only to stderr, so scripts and piped /
|
|
105
|
+
`--json` consumers are unaffected.
|
|
106
|
+
|
|
107
|
+
## 0.15.0 — 2026-05-26
|
|
108
|
+
|
|
109
|
+
### Added
|
|
110
|
+
|
|
111
|
+
- **Model A deploy flow (M-1216).** `launchpad init` scaffolds a
|
|
112
|
+
`launchpad.yaml` from the current working directory, auto-detecting
|
|
113
|
+
app shape: Vite presence, package manager (npm / bun / pnpm / yarn),
|
|
114
|
+
the build command, the destination directory, and `react+api`
|
|
115
|
+
via a top-level `functions/` directory. Interactive prompts collect
|
|
116
|
+
slug / display name / team / owner / allowed group; non-interactive
|
|
117
|
+
flags (`--slug`, `--display-name`, `--app-type`, `--team`,
|
|
118
|
+
`--owner`, `--group`, `--force`, `--non-interactive`) cover CI use.
|
|
119
|
+
`launchpad deploy` from CWD bundles the working tree via a pure-JS
|
|
120
|
+
walker (honours `.gitignore` + a default-ignore list), gzip-streams
|
|
121
|
+
the tarball, and POSTs to the bot's new
|
|
122
|
+
`POST /apps/<slug>/deploy/bundle` endpoint.
|
|
123
|
+
- **Server-side bundle policy (M-1216 / bot).** AC8 ingest gate rejects
|
|
124
|
+
symlinks, traversal paths, special files, oversized files (5 MB),
|
|
125
|
+
excessive file counts (5000), and aggregate decompressed size over
|
|
126
|
+
100 MB. AC9 high-signal secret scan flags AWS access keys, GitHub
|
|
127
|
+
PAT / fine-grained / OAuth / App tokens, Slack tokens, SSH / RSA /
|
|
128
|
+
EC / PGP private keys, and generic `api_key` patterns at upload
|
|
129
|
+
time. AC10 build-command allowlist enforces the approved
|
|
130
|
+
install / build verbs (`npm ci`, `bun install --frozen-lockfile`,
|
|
131
|
+
`pnpm install --frozen-lockfile`, `yarn install --immutable`,
|
|
132
|
+
followed by the package's own `build` script), bans standalone `&`
|
|
133
|
+
background operators, and rejects env-prefix tricks.
|
|
134
|
+
- **In-Worker tar.gz extraction (M-1216 / bot).** Pure-JS USTAR parser
|
|
135
|
+
+ `DecompressionStream("gzip")` extracts the bundle inside the
|
|
136
|
+
Workers runtime — no `tar` binary, no native deps.
|
|
137
|
+
- **`deployment_verified` lifecycle gate (M-1217).** The bot polls
|
|
138
|
+
the Cloudflare Pages REST API after a deploy PR merges and only
|
|
139
|
+
flips the lifecycle to `done` once the production deployment
|
|
140
|
+
reports `success`. Failures and `canceled` deployments terminate
|
|
141
|
+
with a clear `cf-pages-poll-unrecoverable` audit event;
|
|
142
|
+
retryable failures (no-deployments, status 0, status >= 500) walk
|
|
143
|
+
the bounded poll budget (45 polls × 20 seconds = 15 min) before
|
|
144
|
+
surfacing as an explicit timeout. Closes the "lying-live" gap
|
|
145
|
+
where a deploy PR merged but the Pages build failed silently.
|
|
146
|
+
- **Zero-deps CI matrix (M-1216 / T6).** The `launchpad-cli-e2e`
|
|
147
|
+
workflow's `zero-deps-matrix` job runs every CLI smoke test under
|
|
148
|
+
a `PATH` that excludes `git`, `gh`, `jq` — guarantees the AC5
|
|
149
|
+
no-system-dependencies contract is testable, not aspirational. A
|
|
150
|
+
workflow-dispatch-only E2E job exercises the full
|
|
151
|
+
`init → deploy → 200 OK → destroy` flow against a committed
|
|
152
|
+
Vite/React fixture (gated on Cf-Access service-token infra).
|
|
153
|
+
|
|
154
|
+
### Changed
|
|
155
|
+
|
|
156
|
+
- **Claude Code skill bundle rewritten to use CLI verbs only
|
|
157
|
+
(M-1221).** The four legacy launchpad-* skill playbooks
|
|
158
|
+
(`launchpad-onboard`, `launchpad-deploy`,
|
|
159
|
+
`launchpad-deploy-status`, `launchpad-content-pr`) no longer shell
|
|
160
|
+
out to `gh`, `jq`, `curl`, or `git`. Every step routes through a
|
|
161
|
+
`launchpad` verb: session health via `launchpad whoami`, group
|
|
162
|
+
resolution via `launchpad groups list/search/show`, status via
|
|
163
|
+
`launchpad status <slug>` and `launchpad apps`, lifecycle drift
|
|
164
|
+
via `launchpad status <slug> --json`. External users with no
|
|
165
|
+
M-KOPA GitHub access are now first-class through every playbook.
|
|
166
|
+
The M-892 zero-touch modes (`--new` / `--resume` / `--abandon`)
|
|
167
|
+
remain available as a legacy appendix in `/launchpad-deploy` for
|
|
168
|
+
M-KOPA-internal apps stuck in the pre-Model-A flow.
|
|
169
|
+
|
|
170
|
+
## 0.14.1 — 2026-05-26
|
|
171
|
+
|
|
172
|
+
### Fixed
|
|
173
|
+
|
|
174
|
+
- **Critical: `install.sh` failed with `EUNSUPPORTEDPROTOCOL` for fresh
|
|
175
|
+
installs since 0.13.0.** The CLI's published tarball declared
|
|
176
|
+
`"@m-kopa/launchpad-engine": "workspace:*"` because `npm pack`
|
|
177
|
+
doesn't substitute workspace protocol references the way
|
|
178
|
+
`bun publish` does. Consumers running `npm install -g <tarball>`
|
|
179
|
+
via the get.launchpad.m-kopa.us channel choked on the
|
|
180
|
+
`workspace:*` token. Users who already had an earlier CLI
|
|
181
|
+
installed didn't notice because `launchpad update` works from the
|
|
182
|
+
existing install layout, not via a fresh `npm install`.
|
|
183
|
+
- Engine renamed to `@krsecurity/launchpad-engine` and published to
|
|
184
|
+
public npm; the CLI now declares a concrete dep
|
|
185
|
+
(`"@krsecurity/launchpad-engine": "0.3.0"`) so consumer-side
|
|
186
|
+
resolution works without GitHub Packages auth or workspace
|
|
187
|
+
context. The package is on `@krsecurity` for the same temporary
|
|
188
|
+
reason as `@krsecurity/platform-auth` — moves to `@m-kopa` once
|
|
189
|
+
the org claim lands on npmjs.com.
|
|
190
|
+
|
|
191
|
+
## 0.14.0 — 2026-05-26
|
|
192
|
+
|
|
193
|
+
### Added
|
|
194
|
+
|
|
195
|
+
- **`launchpad destroy [<slug>] [--confirm-slug <slug>] [--yes] [--json]`**
|
|
196
|
+
— tear down a Launchpad app end-to-end: Cloudflare Pages project,
|
|
197
|
+
Access app, custom hostname, platform-repo TF entries, and the app
|
|
198
|
+
repo (archive-renamed, not deleted). Owner-only verb; editor role
|
|
199
|
+
is rejected with `"you must be an owner — editor role is not
|
|
200
|
+
sufficient"`. Two-step destructive confirmation matrix per AC4:
|
|
201
|
+
TTY mode prompts for slug re-type + Y/N (flags short-circuit);
|
|
202
|
+
non-TTY (CI) mode requires BOTH `--confirm-slug` AND `--yes`,
|
|
203
|
+
exits 64 (`EX_USAGE`) otherwise. Idempotent re-run on a `destroyed`
|
|
204
|
+
slug exits 0 with `"already destroyed at <timestamp>"`. Distinct
|
|
205
|
+
error messages per bot code: `carve_out` (legacy main.tf-resident
|
|
206
|
+
apps fail closed with a platform-team teardown message),
|
|
207
|
+
`no_per_app_tf`, `destroy_failed` (suggests retry or platform-team
|
|
208
|
+
intervention). Thin HTTP client to `POST /apps/<slug>/destroy`;
|
|
209
|
+
no local Cloudflare, GitHub, or platform-repo credentials needed.
|
|
210
|
+
- **`launchpad-destroy` skill** — Claude Code slash command (and
|
|
211
|
+
fallback documentation) for the destroy verb. Documents the
|
|
212
|
+
confirmation matrix, owner-only authz, lifecycle states
|
|
213
|
+
(`destroying` / `destroyed` / `destroy_failed`), main.tf carve-outs,
|
|
214
|
+
open-PR handling on the app repo (bot PRs closed; human PRs
|
|
215
|
+
receive a heads-up comment), and the manual archive-recovery path
|
|
216
|
+
via `gh repo rename` + `gh repo unarchive`. Bundled by
|
|
217
|
+
`launchpad skills install`.
|
|
218
|
+
|
|
219
|
+
### Fixed
|
|
220
|
+
|
|
221
|
+
- `scripts/sync-skill-contract.sh` now includes `launchpad-status` in
|
|
222
|
+
its skills array. The launchpad-status SKILL.md was stuck at
|
|
223
|
+
version 0.13.0 because it was omitted from the sync loop; running
|
|
224
|
+
`bun run sync:skill-contract` now bumps it alongside the others.
|
|
225
|
+
|
|
226
|
+
## 0.13.0 — 2026-05-25
|
|
227
|
+
|
|
228
|
+
### Added
|
|
229
|
+
|
|
230
|
+
- **`launchpad pull <slug>`** — fetch the deployed `launchpad.yaml`
|
|
231
|
+
for an app. Thin HTTP client to the bot's
|
|
232
|
+
`/apps/<slug>/manifest/state?include=manifest` endpoint. No local
|
|
233
|
+
platform-repo clone, no terraform, no GitHub credentials. Slug
|
|
234
|
+
from positional arg, `--slug` flag, or cwd inference inside
|
|
235
|
+
`launchpad-app-<slug>/`. `--out <path>` redirects to a file.
|
|
236
|
+
- **`launchpad status [--slug] [--file] [--json] [--strict]`** —
|
|
237
|
+
compare local `launchpad.yaml` against the deployed manifest and
|
|
238
|
+
report drift over a closed v1 field set (metadata, deployment.type,
|
|
239
|
+
access.allowed_entra_group, hostnames[0], build.*, production_env.*).
|
|
240
|
+
Three-state surfacing: also reports whether the app-repo's main
|
|
241
|
+
HEAD is ahead of the deployed sha — surfaces "an apply may be
|
|
242
|
+
queued (see PR #N)" or "main is ahead of deployed" — the operator
|
|
243
|
+
equivalent of `git status` knowing about `git fetch`. Equivalence
|
|
244
|
+
relation is normalised-AST: whitespace, comments, and key ordering
|
|
245
|
+
are NOT drift. Defence-in-depth: warns on stderr when any
|
|
246
|
+
`production_env.*` value matches a secret-shape heuristic (known
|
|
247
|
+
prefix AND length ≥ 32 in a base64-shaped character set).
|
|
248
|
+
- **`/launchpad-status` skill** in the Claude Code skills bundle.
|
|
249
|
+
Walks operators through pull vs status, the three states, the
|
|
250
|
+
HEAD-vs-deployed annotation, and `--strict` for CI guards.
|
|
251
|
+
Cross-linked from `/launchpad-content-pr`'s "ongoing deploys"
|
|
252
|
+
section.
|
|
253
|
+
|
|
254
|
+
### Changed
|
|
255
|
+
|
|
256
|
+
- Bot's `GET /apps/<slug>/manifest/state` response now carries
|
|
257
|
+
`appRepoHeadSha` (additive field; older clients ignore unknown
|
|
258
|
+
fields). Powers the new three-state surfacing in status without
|
|
259
|
+
a second round-trip.
|
|
260
|
+
|
|
261
|
+
### Notes
|
|
262
|
+
|
|
263
|
+
- Per-app authz on the state endpoint is owner/editor-scoped (the
|
|
264
|
+
existing `authzRole` gate at the router level already enforced
|
|
265
|
+
this before M-1188 — confirmed in T1's investigation). Non-owners
|
|
266
|
+
get 403; clients distinguish 401 / 403 / 404 with actionable
|
|
267
|
+
messages.
|
|
268
|
+
- Exit codes on `launchpad status`: `0` = in sync OR drift in
|
|
269
|
+
default report-only mode. `1` = drift, but ONLY when `--strict`
|
|
270
|
+
is passed. `2` = error. Default is intentionally report-only so
|
|
271
|
+
interactive use never surprises; CI guards opt in via
|
|
272
|
+
`launchpad status --strict && launchpad deploy`.
|
|
273
|
+
|
|
274
|
+
Closes M-1188.
|
|
275
|
+
|
|
276
|
+
## 0.12.0 — 2026-05-21
|
|
277
|
+
|
|
278
|
+
### Added
|
|
279
|
+
|
|
280
|
+
- `container` is now an accepted value for `--app-type` on
|
|
281
|
+
`launchpad create` and `launchpad deploy --new`. Mirrors the
|
|
282
|
+
closed taxonomy in `portal-bot/src/review/gate.ts` after M-1071
|
|
283
|
+
chunk F (`#139`) added `container` as the fourth app-type for
|
|
284
|
+
container-shape apps on Cloudflare Containers (ADR 0015). The
|
|
285
|
+
CLI's `APP_TYPES` array — single-sourced into the `--app-type`
|
|
286
|
+
validator AND the usage-banner rendering on both `create` and
|
|
287
|
+
`deploy --new` — was the last hand-maintained mirror still
|
|
288
|
+
carrying the three-type list; this bump closes the chunk F
|
|
289
|
+
bot/CLI gap (chunk F-bis). The wire-format change is additive:
|
|
290
|
+
`static | react | react+api` submissions are unchanged, and the
|
|
291
|
+
bot's `POST /apps` already accepts `appType: "container"`. (M-1071)
|
|
292
|
+
|
|
293
|
+
### Changed
|
|
294
|
+
|
|
295
|
+
- The `--app-type` help line on both verbs is now rendered from
|
|
296
|
+
`APP_TYPES.join("|")` rather than a hand-edited literal, so a
|
|
297
|
+
future fifth app-type only needs one line in `create.ts` to
|
|
298
|
+
appear in every help banner the CLI emits.
|
|
299
|
+
|
|
300
|
+
## 0.11.7 — 2026-05-19
|
|
301
|
+
|
|
302
|
+
### Fixed
|
|
303
|
+
|
|
304
|
+
- Skills no longer fail on Windows when the agent tries to translate
|
|
305
|
+
the fenced `bash` blocks into PowerShell. Claude Code's `Bash` tool
|
|
306
|
+
always invokes `bash` (Git for Windows / MSYS bash on Windows), so
|
|
307
|
+
PowerShell idioms like `Test-Path`, `$env:USERPROFILE`, `Get-Content`
|
|
308
|
+
produced `/usr/bin/bash: syntax error` instead of running. Each
|
|
309
|
+
launchpad-* SKILL.md now opens with an explicit **Shell contract**
|
|
310
|
+
preamble telling the agent not to transliterate, and the bash
|
|
311
|
+
inside the skills has been hardened so every block is unambiguously
|
|
312
|
+
bash (`$HOME` instead of `~`, quoted expansions, `${TMPDIR:-/tmp}`
|
|
313
|
+
instead of bare `/tmp`, etc.). The contract block is single-sourced
|
|
314
|
+
from `skills/_partials/shell-contract.md` and stamped into each
|
|
315
|
+
skill by `scripts/sync-skill-contract.sh`; CI rejects any drift.
|
|
316
|
+
(M-1070)
|
|
317
|
+
|
|
318
|
+
### Changed
|
|
319
|
+
|
|
320
|
+
- Skill `version:` frontmatter is now stamped from `package.json`
|
|
321
|
+
by `scripts/sync-skill-contract.sh`, not hand-bumped. Historically
|
|
322
|
+
the four `launchpad-*` skills tagged `version: 0.11.0` while the
|
|
323
|
+
CLI shipped at `0.11.6`, surfacing as a spurious "drift" warning
|
|
324
|
+
in `/launchpad-onboard`. The sync step and a new CI gate
|
|
325
|
+
(`bun run check:skills`) make that class of drift impossible to
|
|
326
|
+
ship. (M-1070)
|
|
327
|
+
- `/launchpad-onboard` step 2 (`gh / jq / curl`) now branches on
|
|
328
|
+
`uname -s` *inside bash* (`Darwin`, `Linux` with apt/dnf probe,
|
|
329
|
+
`MINGW*|MSYS*|CYGWIN*` for Git Bash on Windows) instead of
|
|
330
|
+
printing per-OS prose. The agent can execute the right command
|
|
331
|
+
directly without having to interpret the prose.
|
|
332
|
+
- `/launchpad-onboard` step 4 (session freshness) now computes age
|
|
333
|
+
in days using `jq`'s `fromdate` builtin, which is portable across
|
|
334
|
+
macOS BSD `date`, GNU `date`, and Git Bash on Windows. The previous
|
|
335
|
+
check only verified file existence.
|
|
336
|
+
- `/launchpad-deploy` pre-flight now also verifies `gh auth status`
|
|
337
|
+
succeeds against `github.com`, not just that the `gh` binary is on
|
|
338
|
+
PATH. The resume/abandon branches need a working `gh`, and the
|
|
339
|
+
earlier check missed the most common failure mode (binary present,
|
|
340
|
+
not authenticated).
|
|
341
|
+
- `/launchpad-deploy` work-directory is now `${TMPDIR:-/tmp}/launchpad-<slug>`
|
|
342
|
+
instead of a hard-coded `/tmp/launchpad-<slug>`. `$TMPDIR` is the
|
|
343
|
+
cross-platform canonical knob (macOS auto-sets it; Git Bash points
|
|
344
|
+
it at a Windows-side temp area).
|
|
345
|
+
- `/launchpad-content-pr` clone target is now
|
|
346
|
+
`${LAUNCHPAD_WORKDIR:-$HOME/projects}` so users who don't keep code
|
|
347
|
+
under `~/projects` can override without forking the skill.
|
|
348
|
+
- Every `curl` against the bot now extracts the token via
|
|
349
|
+
`jq -r '.accessToken // empty' "$HOME/.launchpad/session.json"` and
|
|
350
|
+
bails with a clear "run /launchpad-onboard" message on empty
|
|
351
|
+
output, instead of silently sending `Authorization: Bearer null`
|
|
352
|
+
and getting a confusing 401 back.
|
|
353
|
+
|
|
354
|
+
### Added
|
|
355
|
+
|
|
356
|
+
- `scripts/sync-skill-contract.sh` — re-stamps the Shell Contract
|
|
357
|
+
block + `version:` frontmatter into each `launchpad-*/SKILL.md`
|
|
358
|
+
from canonical sources (`skills/_partials/shell-contract.md` +
|
|
359
|
+
`package.json`). Idempotent. Run via `bun run sync:skill-contract`.
|
|
360
|
+
- `scripts/check-skill-bash-dialect.sh` — fails the build if any
|
|
361
|
+
`launchpad-*/SKILL.md` contains a PowerShell token inside a fenced
|
|
362
|
+
`bash` block. Wired via `bun run check:skill-bash-dialect`.
|
|
363
|
+
- `scripts/check-skill-bash-parse.sh` — runs `bash -n` against every
|
|
364
|
+
fenced `bash` block in each `launchpad-*/SKILL.md`, with
|
|
365
|
+
`<placeholder>` segments sanitised so the parser doesn't misread
|
|
366
|
+
them as here-strings. Catches stray syntax errors at PR time.
|
|
367
|
+
Wired via `bun run check:skill-bash-parse`.
|
|
368
|
+
- `tests/skill-contract.test.ts` — twelve vitest cases asserting the
|
|
369
|
+
contract is present, byte-identical, and free of forbidden tokens
|
|
370
|
+
in each `launchpad-*` skill. Surfaces violations in
|
|
371
|
+
`vitest --watch` the moment a SKILL.md is saved, ahead of the
|
|
372
|
+
CI shell gate.
|
|
373
|
+
- `launchpad-cli` CI gate composite action runs
|
|
374
|
+
`bun run check:skills` (the three skill guards in one step)
|
|
375
|
+
immediately after the existing `check:version-sync` step.
|
|
376
|
+
|
|
377
|
+
## 0.11.6 — 2026-05-19
|
|
378
|
+
|
|
379
|
+
### Changed
|
|
380
|
+
|
|
381
|
+
- `launchpad skills install` now creates `~/.claude/skills/` (and any
|
|
382
|
+
missing parent — typically `~/.claude/`) when the target directory
|
|
383
|
+
does not yet exist, instead of aborting with an `mkdir -p` hint.
|
|
384
|
+
The platform-channel installer (`install.sh` / `install.ps1` step
|
|
385
|
+
7) invokes `skills install` on every fresh CLI install, and the
|
|
386
|
+
abort path produced confusing output on machines where Claude Code
|
|
387
|
+
had never been launched — the user saw "launchpad-cli installed"
|
|
388
|
+
immediately followed by "skills install failed: directory does not
|
|
389
|
+
exist", with no indication that the CLI itself was fine. The auto-
|
|
390
|
+
create is safe: the command was explicitly invoked, the directory
|
|
391
|
+
is named after a product the user is clearly setting up, and
|
|
392
|
+
`fs.mkdir({ recursive: true })` is idempotent. (M-1061)
|
|
393
|
+
- Platform-channel `install.ps1` on Windows now adds the npm global
|
|
394
|
+
directory to the user's PATH automatically when it isn't already
|
|
395
|
+
there, instead of printing a warning and leaving the user to
|
|
396
|
+
manage it. Previously the installer printed
|
|
397
|
+
`'C:\Users\...\AppData\Roaming\npm\launchpad.cmd' was installed,
|
|
398
|
+
but 'launchpad' is not yet on your PATH` and stopped — completing
|
|
399
|
+
the install but leaving `launchpad --version` broken until the
|
|
400
|
+
user found the System Properties dialog and added the entry by
|
|
401
|
+
hand. The fix writes to user-scope `HKCU\Environment` via
|
|
402
|
+
`[Environment]::SetEnvironmentVariable('PATH', …, 'User')` (not
|
|
403
|
+
`setx`, which truncates user PATH to 1024 chars and silently
|
|
404
|
+
mangles long PATHs), updates the current process's `$env:PATH`
|
|
405
|
+
so an immediate follow-up command in the same shell works, and
|
|
406
|
+
detects the case where the entry is already in the user / machine
|
|
407
|
+
PATH but the current PowerShell session was spawned before the
|
|
408
|
+
registry write — in that case no second write happens, just a
|
|
409
|
+
clear "open a new PowerShell window" message. The PATH-shadow
|
|
410
|
+
branch (M-1058) is unchanged: when a different `launchpad` binary
|
|
411
|
+
is winning the PATH race, adding to PATH won't help — the user
|
|
412
|
+
has to remove the shadowing binary, and the existing warning
|
|
413
|
+
tells them how. (M-1062)
|
|
414
|
+
|
|
415
|
+
## 0.11.5 — 2026-05-19
|
|
416
|
+
|
|
417
|
+
### Fixed
|
|
418
|
+
|
|
419
|
+
- Platform-channel `install.ps1` aborted on Windows during the very
|
|
420
|
+
first prerequisite check with `SyntaxError: Unexpected token '.'`
|
|
421
|
+
from a `node -p` invocation, followed by a spurious
|
|
422
|
+
`Node.js >= 20 required; found vX.Y.Z` (even on Node 24 — comfortably
|
|
423
|
+
above the floor). Root cause: the Node-major-detection line read
|
|
424
|
+
`& node -p 'process.versions.node.split(".")[0]'`. PowerShell on
|
|
425
|
+
Windows builds the Win32 command line for native executables via a
|
|
426
|
+
separate arg-quoting pass that mishandles embedded double quotes —
|
|
427
|
+
the inner `"."` were stripped, so node actually received
|
|
428
|
+
`process.versions.node.split(.)[0]`, which is invalid in both JS
|
|
429
|
+
and (on Node 22.6+) the new built-in TypeScript evaluator. The fix
|
|
430
|
+
reads `node --version` and parses the major in PowerShell, with
|
|
431
|
+
no native-command argument crossing the boundary — only pure
|
|
432
|
+
PowerShell single-quoted literals (`'v'`, `'.'`). This bug had
|
|
433
|
+
been latent in the script since the first release of the platform
|
|
434
|
+
channel (M-1042); the prior M-1059 PowerShell-parser bug masked it
|
|
435
|
+
on Windows until the script could actually be loaded. (M-1060)
|
|
436
|
+
|
|
437
|
+
### Added
|
|
438
|
+
|
|
439
|
+
- `packages/launchpad-cli/scripts/check-installer-syntax.sh` — runs
|
|
440
|
+
`bash -n` on `install.sh` and the PowerShell parser
|
|
441
|
+
(`[System.Management.Automation.Language.Parser]::ParseFile`) on
|
|
442
|
+
`install.ps1`. Prefers system `pwsh` (pre-installed on GitHub's
|
|
443
|
+
`ubuntu-latest`) and falls back to the official
|
|
444
|
+
`mcr.microsoft.com/powershell` Docker image so the check still runs
|
|
445
|
+
on macOS contributor laptops without PowerShell installed. Wired
|
|
446
|
+
into the `launchpad-cli-gate` composite action, so every PR and
|
|
447
|
+
pre-publish gate now parse-checks the installer templates before
|
|
448
|
+
the channel deploys them. This wouldn't have caught M-1059 (the
|
|
449
|
+
encoding bug is a Windows-PowerShell-5.1 runtime behaviour the
|
|
450
|
+
cross-platform pwsh parser does not reproduce) but it
|
|
451
|
+
unambiguously catches M-1060 and any future syntax regression.
|
|
452
|
+
|
|
453
|
+
## 0.11.4 — 2026-05-19
|
|
454
|
+
|
|
455
|
+
### Fixed
|
|
456
|
+
|
|
457
|
+
- Platform-channel `install.ps1` failed to parse on Windows with a
|
|
458
|
+
cascading "TerminatorExpectedAtEndOfString / Missing closing }"
|
|
459
|
+
error far from any real syntax problem. Root cause: the file ships
|
|
460
|
+
as UTF-8 without a BOM, and Windows PowerShell 5.1 reads BOM-less
|
|
461
|
+
script files using the system ANSI codepage (CP1252 on most English
|
|
462
|
+
Windows installs), not UTF-8. The script contains UTF-8 multibyte
|
|
463
|
+
sequences for box-drawing characters (`─`, `U+2500` = `e2 94 80`)
|
|
464
|
+
in section dividers and em dashes (`—`, `U+2014` = `e2 80 94`) in
|
|
465
|
+
comments and `Write-Host` strings. Under CP1252, byte `\x94`
|
|
466
|
+
decodes to `U+201D` (RIGHT DOUBLE QUOTATION MARK), which
|
|
467
|
+
PowerShell's parser recognises as a string delimiter — every such
|
|
468
|
+
character injects a phantom quote, the running parity of double
|
|
469
|
+
quotes desynchronises across the whole file, and the failure
|
|
470
|
+
surfaces at the final `Write-Host "…"` even though the script is
|
|
471
|
+
structurally correct. The channel workflow now prepends a UTF-8
|
|
472
|
+
BOM to `install.ps1` only (the `.sh` installer is unaffected — bash
|
|
473
|
+
on macOS/Linux reads UTF-8 unconditionally), forcing PS 5.1 to
|
|
474
|
+
decode the file as UTF-8 regardless of host locale. Without this
|
|
475
|
+
fix, no Windows user can complete a platform-channel install — and
|
|
476
|
+
therefore no Windows user gets the `~/.launchpad/channel` marker
|
|
477
|
+
written, so every `launchpad update` falls through to the GitHub
|
|
478
|
+
Packages path and re-asks for a `read:packages` token, defeating
|
|
479
|
+
the whole point of ADR 0014. (M-1059)
|
|
480
|
+
|
|
481
|
+
## 0.11.3 — 2026-05-19
|
|
482
|
+
|
|
483
|
+
### Fixed
|
|
484
|
+
|
|
485
|
+
- `launchpad update` on Windows. The earlier M-1056 fix resolved the
|
|
486
|
+
`npm` shim name to `npm.cmd` so libuv could find it, but
|
|
487
|
+
`CreateProcess` still cannot execute a `.cmd` script directly — every
|
|
488
|
+
Windows invocation failed with `spawn EINVAL` instead of the previous
|
|
489
|
+
`ENOENT`. The fix adds a `pmSpawnOptions` helper that returns
|
|
490
|
+
`{ shell: true }` on win32 and applies it at all three PM spawn sites
|
|
491
|
+
(`npm view` for the registry query, `<pm> root -g` for PM detection,
|
|
492
|
+
`<pm> install -g` for the upgrade). Non-Windows platforms are
|
|
493
|
+
unchanged. Every PM arg in `update.ts` is hardcoded, so no user input
|
|
494
|
+
reaches the shell.
|
|
495
|
+
|
|
496
|
+
## 0.11.2 — 2026-05-19
|
|
497
|
+
|
|
498
|
+
### Fixed
|
|
499
|
+
|
|
500
|
+
- `launchpad login` on Windows. The M-1055 fix swapped the broken
|
|
501
|
+
`cmd /c start "" <url>` opener for `explorer.exe <url>`, but
|
|
502
|
+
`explorer.exe` interprets argv as a filesystem path, not a URL.
|
|
503
|
+
When `https://...` failed to resolve as a path, explorer silently
|
|
504
|
+
fell back to opening File Explorer at the user's Documents folder
|
|
505
|
+
— never the browser. The opener now invokes
|
|
506
|
+
`rundll32.exe url.dll,FileProtocolHandler <url>`, the
|
|
507
|
+
Microsoft-documented Windows URL-protocol entrypoint: no shell, so
|
|
508
|
+
cmd metacharacters (`&`, `|`, `^`) are never parsed, and argv is
|
|
509
|
+
passed verbatim to the URL handler which dispatches to the
|
|
510
|
+
user's registered default browser. macOS (`open`) and Linux
|
|
511
|
+
(`xdg-open`) are unchanged. (M-1057)
|
|
512
|
+
|
|
513
|
+
- Platform-channel installers (`install.sh` / `install.ps1`) silently
|
|
514
|
+
installed `launchpad-cli` "successfully" while `launchpad
|
|
515
|
+
--version` continued to report the previous version. Root cause:
|
|
516
|
+
the install ran `npm install -g <tarball>` which writes to npm's
|
|
517
|
+
global prefix, but the user's shell resolved `launchpad` to an
|
|
518
|
+
older shim earlier on PATH (typically a previous `bun add -g` /
|
|
519
|
+
`pnpm add -g` install, or a stale npm prefix). The new install
|
|
520
|
+
was correct on disk; PATH ordering hid it. The installers now (a)
|
|
521
|
+
call the just-installed binary by absolute path for
|
|
522
|
+
`skills install` — so the bundled skills always match the CLI
|
|
523
|
+
being installed, not the shadowed one — and (b) compare the
|
|
524
|
+
resolved `launchpad` against `$(npm prefix -g)/bin/launchpad`
|
|
525
|
+
(`<prefix>\launchpad.cmd` on Windows) and print a loud, actionable
|
|
526
|
+
warning when they differ, telling the user exactly which binary is
|
|
527
|
+
shadowing and how to remove it. (M-1058)
|
|
528
|
+
|
|
529
|
+
## 0.11.1 — 2026-05-19
|
|
530
|
+
|
|
531
|
+
### Fixed
|
|
532
|
+
|
|
533
|
+
- `launchpad update` on Windows. `update.ts` invoked `execFile("npm",
|
|
534
|
+
…)` / `spawn("npm", …)`, but Node's `child_process` on Windows uses
|
|
535
|
+
`CreateProcess`, which does NOT consult `PATHEXT` — it only tries
|
|
536
|
+
the literal filename and `<name>.exe`. `npm` (and `pnpm`) ship as
|
|
537
|
+
`.cmd` shims on Windows, so the call always failed with `ENOENT`,
|
|
538
|
+
and the catch branch surfaced this as the misleading message
|
|
539
|
+
"`npm` was not found on PATH (it ships with Node.js)" — even on
|
|
540
|
+
machines where `npm` worked perfectly from `cmd` / PowerShell. A
|
|
541
|
+
new `resolvePmBin` helper appends `.cmd` for `npm` / `pnpm` on
|
|
542
|
+
`win32`; the registry query, the `<pm> root -g` detection probes,
|
|
543
|
+
and the upgrade `spawn` all go through it. `bun` (a real
|
|
544
|
+
`bun.exe`) and every non-Windows platform are unchanged. The
|
|
545
|
+
ENOENT branch's user-facing message has been rewritten to point at
|
|
546
|
+
a missing Node install rather than mis-blaming PATH. (M-1056)
|
|
547
|
+
|
|
548
|
+
## 0.11.0 — 2026-05-19
|
|
549
|
+
|
|
550
|
+
### Added
|
|
551
|
+
|
|
552
|
+
- Platform distribution channel. The CLI can now be installed from
|
|
553
|
+
`get.launchpad.m-kopa.us` behind M-KOPA SSO — download one
|
|
554
|
+
self-extracting installer for your OS (`install.sh` / `install.ps1`)
|
|
555
|
+
and run it; no GitHub-org membership and no npm-registry setup
|
|
556
|
+
required. GitHub Packages remains the engineer / CI channel. See
|
|
557
|
+
`docs/adr/0014-cli-distribution-channel.md`.
|
|
558
|
+
- `launchpad update` resolves the latest version from whichever
|
|
559
|
+
channel installed the CLI. A platform-channel install reads the
|
|
560
|
+
public `version.json` over plain HTTP with no GitHub Packages auth;
|
|
561
|
+
the GitHub Packages path is unchanged. (SCOPE-M-1042)
|
|
562
|
+
|
|
563
|
+
### Fixed
|
|
564
|
+
|
|
565
|
+
- `launchpad login` on Windows. The browser opener ran
|
|
566
|
+
`cmd /c start "" <url>`, and `cmd.exe` split the OAuth URL on its
|
|
567
|
+
`&` characters — the browser received only the URL up to the first
|
|
568
|
+
`&`, dropping the PKCE `code_challenge`, so Cloudflare Access
|
|
569
|
+
rejected the login (`code_challenge is required for public
|
|
570
|
+
clients`). The CLI now opens the browser via `explorer.exe`, which
|
|
571
|
+
involves no shell. (M-1055)
|
|
572
|
+
|
|
573
|
+
## 0.10.0 — 2026-05-17
|
|
574
|
+
|
|
575
|
+
### Added
|
|
576
|
+
|
|
577
|
+
- `launchpad update` — self-update the CLI to the latest published
|
|
578
|
+
release. Reads the latest version from GitHub Packages via
|
|
579
|
+
`npm view @m-kopa/launchpad-cli version --registry=https://npm.pkg.github.com`,
|
|
580
|
+
reusing the
|
|
581
|
+
user's existing `~/.npmrc` auth — the command never writes npmrc or
|
|
582
|
+
handles tokens. Detects which package manager owns the global
|
|
583
|
+
install by prefix-matching the running package directory against the
|
|
584
|
+
npm / pnpm / bun global roots; on a unique match it runs that PM's
|
|
585
|
+
global upgrade, and on an ambiguous match it prints the three
|
|
586
|
+
explicit upgrade commands rather than guessing. `--via npm|bun|pnpm`
|
|
587
|
+
forces the package manager. On success it reminds the user to run
|
|
588
|
+
`launchpad skills update`, since the CLI and the skill bundle are
|
|
589
|
+
version-locked.
|
|
590
|
+
- `launchpad update --check` — report the running version against the
|
|
591
|
+
latest published; install nothing. Exit `0` when up to date, exit
|
|
592
|
+
`10` when an update is available — scriptable.
|
|
593
|
+
|
|
594
|
+
### Notes
|
|
595
|
+
|
|
596
|
+
- Closes SCOPE-M-1007, the follow-up to SCOPE-M-760's deferred "CLI
|
|
597
|
+
autoupdate" out-of-scope item. Zero new runtime dependencies — the
|
|
598
|
+
command uses only `node:child_process` + `node:*` stdlib.
|
|
599
|
+
|
|
600
|
+
## 0.8.0 — 2026-05-13
|
|
601
|
+
|
|
602
|
+
### Added
|
|
603
|
+
|
|
604
|
+
- `launchpad create` — file a new app from the terminal, mirroring
|
|
605
|
+
the portal wizard's `POST /apps` wire format byte-for-byte. The
|
|
606
|
+
flag set is `--slug`, `--display-name`, `--app-type`,
|
|
607
|
+
`--allowed-group` (repeatable), and optional `--description`.
|
|
608
|
+
Same closed three-type taxonomy as the wizard
|
|
609
|
+
(`static | react | react+api`); no slug pre-flight (the bot's
|
|
610
|
+
registry is the authoritative gate); no env-var seeding (use
|
|
611
|
+
`launchpad envvars set` post-creation). Unblocks SCOPE-M-881's
|
|
612
|
+
AC5 self-service path for users who can't or won't open the SPA.
|
|
613
|
+
|
|
614
|
+
## 0.7.3 — 2026-05-11
|
|
615
|
+
|
|
616
|
+
### Fixed
|
|
617
|
+
|
|
618
|
+
- `launchpad deploy` failed mid-run with `invalid_client: Client ID
|
|
619
|
+
is required` (HTTP 401 from Cf Access token endpoint) whenever
|
|
620
|
+
the access token expired before the deploy POST. The bytes-fetcher
|
|
621
|
+
used by `deploy` overrode `init.body` on every request the
|
|
622
|
+
caller-supplied fetcher saw — including the silent refresh-token
|
|
623
|
+
call that `api-client` threads through `getValidAccessToken`.
|
|
624
|
+
Cf then received the gzip tarball with an
|
|
625
|
+
`application/x-www-form-urlencoded` content-type and rejected the
|
|
626
|
+
request because it couldn't find `client_id` in the binary body.
|
|
627
|
+
Make the bytes-fetcher URL-aware: only override `body` when the
|
|
628
|
+
request targets the deploy-pr path; pass everything else (token
|
|
629
|
+
endpoint, anything else the auth stack fires) through untouched.
|
|
630
|
+
|
|
631
|
+
## 0.7.2 — 2026-05-11
|
|
632
|
+
|
|
633
|
+
### Fixed
|
|
634
|
+
|
|
635
|
+
- `launchpad deploy` failed with `protected_paths` whenever the
|
|
636
|
+
cloned working tree contained `.github/workflows/ci.yml`,
|
|
637
|
+
`wrangler.toml`, or any other bot-owned file — i.e. every clone,
|
|
638
|
+
every time. The bot rejects the deploy if those paths appear in
|
|
639
|
+
the tarball regardless of content; the CLI was happily shipping
|
|
640
|
+
the full working tree via `git ls-files`. Mirror the bot's
|
|
641
|
+
`PROTECTED_PATHS` set client-side and filter them out before
|
|
642
|
+
packing. The bot uses `base_tree` mode on the commit, so files
|
|
643
|
+
we omit keep their base-branch contents — no destructive
|
|
644
|
+
consequence to the filter.
|
|
645
|
+
|
|
646
|
+
## 0.7.1 — 2026-05-11
|
|
647
|
+
|
|
648
|
+
### Fixed
|
|
649
|
+
|
|
650
|
+
- `launchpad login` failed against Cloudflare Access with
|
|
651
|
+
`error=invalid_target: No resource parameter found`. Cf Access
|
|
652
|
+
requires the [RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)
|
|
653
|
+
resource indicator on the authorization request; the previous
|
|
654
|
+
release built the auth URL without it. The protected-resource
|
|
655
|
+
metadata's `resource` field is now read at discovery time and
|
|
656
|
+
threaded through the auth URL, the code-for-token exchange, AND
|
|
657
|
+
the refresh-token grant. Cached in the session so silent refresh
|
|
658
|
+
can reuse it.
|
|
659
|
+
|
|
660
|
+
### Migration
|
|
661
|
+
|
|
662
|
+
- Sessions written by 0.7.0 don't carry the `resource` field, so a
|
|
663
|
+
silent refresh would fail. Those sessions surface as
|
|
664
|
+
`LoginRequiredError` on the next bot call; the user re-runs
|
|
665
|
+
`launchpad login` to mint a fresh session in the new shape.
|
|
666
|
+
|
|
667
|
+
## 0.7.0 — 2026-05-11
|
|
668
|
+
|
|
669
|
+
### Added
|
|
670
|
+
|
|
671
|
+
- `launchpad envvars list [--slug <slug>]` — render production env
|
|
672
|
+
vars on the app's Pages project as a `NAME / TYPE / VALUE` table.
|
|
673
|
+
`secret_text` values are rendered as `•••` because the bot never
|
|
674
|
+
returns them (Cf redacts server-side).
|
|
675
|
+
- `launchpad envvars set <KEY>=<value> [<KEY>=<value> ...] [--secret] [--slug <slug>]`
|
|
676
|
+
— write env vars. `--secret` flips all pairs in the call to
|
|
677
|
+
`secret_text`; default is `plain_text`. Mirrors the bot's
|
|
678
|
+
`[A-Z][A-Z0-9_]*` key constraint client-side so bad keys fail
|
|
679
|
+
before the round-trip.
|
|
680
|
+
- `launchpad envvars rm <KEY> [<KEY> ...] [--slug <slug>]` — drop
|
|
681
|
+
env vars by name. PATCHes `{envVars: {<KEY>: null}}` per the
|
|
682
|
+
bot's edit contract.
|
|
683
|
+
- `launchpad logs [--slug <slug>] [--lines <n>]` — render recent
|
|
684
|
+
Pages deployment history (ID / ENV / STATUS / STAGE / CREATED).
|
|
685
|
+
Default 10 lines, max 25 per call. **Not a runtime log tail** —
|
|
686
|
+
the bot's surface today is deployment metadata only. Helpful for
|
|
687
|
+
the common "my deploy didn't go live, what happened?" failure
|
|
688
|
+
mode; the CHANGELOG flags the gap explicitly so users aren't
|
|
689
|
+
surprised.
|
|
690
|
+
- `.github/workflows/launchpad-cli-publish.yml` — publish workflow
|
|
691
|
+
modelled on `package-publish.yml`. Path-filter on
|
|
692
|
+
`packages/launchpad-cli/package.json`. Re-runs the full gate
|
|
693
|
+
(typecheck + lint + test + audit + version-sync + license +
|
|
694
|
+
build), checks whether the version is already published, and
|
|
695
|
+
publishes + tags `launchpad-cli@<version>` if new.
|
|
696
|
+
- `scripts/check-version-sync.sh` — fail-fast guard that ensures
|
|
697
|
+
`version` in package.json and `CLI_VERSION` in src/version.ts
|
|
698
|
+
agree. Wired in as `check:version-sync` so the publish workflow
|
|
699
|
+
catches a forgotten bump before pushing to the registry.
|
|
700
|
+
- Wire types in `src/types/api.ts`: `EnvVarEntry`,
|
|
701
|
+
`EnvVarsListResponse`, `DeploymentSummary`, `DeploymentsResponse`.
|
|
702
|
+
|
|
703
|
+
### Deferred
|
|
704
|
+
|
|
705
|
+
- Runtime log tailing. Cf Pages doesn't expose a stable HTTP API
|
|
706
|
+
for tailing function logs; the dashboard uses an authenticated
|
|
707
|
+
WebSocket that the bot doesn't proxy. v1's `launchpad logs`
|
|
708
|
+
surfaces deployment history instead — the highest-signal
|
|
709
|
+
diagnostic the bot CAN return today. Tracked as a follow-up
|
|
710
|
+
scope once Cf publishes a tailable surface (or once the bot
|
|
711
|
+
acquires a log-relay shim).
|
|
712
|
+
|
|
713
|
+
## 0.6.0 — 2026-05-10
|
|
714
|
+
|
|
715
|
+
### Added
|
|
716
|
+
|
|
717
|
+
- `launchpad review [<prNumber>] [--slug <slug>]` — render the
|
|
718
|
+
per-PR review state (aggregate status + per-source findings).
|
|
719
|
+
- If `<prNumber>` is omitted, defaults to the newest open PR
|
|
720
|
+
via `GET /apps/<slug>/prs` (server-sorted `updated_at desc`).
|
|
721
|
+
- Findings are rendered ranked by severity (blocker_hard first).
|
|
722
|
+
- Empty findings list collapses to a single-line "No findings."
|
|
723
|
+
- `launchpad merge <prNumber> [--slug <slug>]` — squash-merge a
|
|
724
|
+
review-passed PR via the bot.
|
|
725
|
+
- PR number is REQUIRED (no inference) — merging the wrong PR
|
|
726
|
+
is a load-bearing mistake, the extra keystroke is cheap.
|
|
727
|
+
- Tailored exit messages per `error` discriminator:
|
|
728
|
+
`review_running` (wait), `review_blocked` (see findings),
|
|
729
|
+
`merge_conflict` (rebase), `sha_mismatch` (retry),
|
|
730
|
+
`pipeline_unavailable` (push to trigger workflow), etc.
|
|
731
|
+
- `src/http/api-client.ts` — `nonThrowingStatuses` option on
|
|
732
|
+
`apiRaw` so callers (currently just `merge`) can opt out of the
|
|
733
|
+
shared `ApiError` mapping for statuses where the structured
|
|
734
|
+
envelope is the load-bearing signal. 401 / 403 / 404 still
|
|
735
|
+
throw typed errors regardless.
|
|
736
|
+
- `src/types/api.ts` — wire shapes for the review tab + merge
|
|
737
|
+
endpoint (`ReviewStatePayload`, `PrSummary`, `MergeSuccessResponse`,
|
|
738
|
+
`BotErrorEnvelope`). Hand-maintained alongside the portal SPA's
|
|
739
|
+
copy of the same types.
|
|
740
|
+
|
|
741
|
+
## 0.5.0 — 2026-05-10
|
|
742
|
+
|
|
743
|
+
### Added
|
|
744
|
+
|
|
745
|
+
- `launchpad deploy [--message <text>] [--slug <slug>]` — package
|
|
746
|
+
the working tree + open/update a PR via the bot.
|
|
747
|
+
- Enumerates files via `git ls-files -co --exclude-standard` so
|
|
748
|
+
`.gitignore` semantics are honoured exactly the way git does.
|
|
749
|
+
No hand-rolled matcher.
|
|
750
|
+
- Packs into a POSIX ustar tarball; gzips via Web Streams
|
|
751
|
+
`CompressionStream`. Deterministic byte output (zero mtimes,
|
|
752
|
+
zero uid/gid, lex-sorted entries).
|
|
753
|
+
- Executable bit preserved end-to-end so committed scripts at
|
|
754
|
+
100755 stay executable through deploy → review → live.
|
|
755
|
+
- 50MB compressed cap enforced client-side; the bot's
|
|
756
|
+
100MB-uncompressed + 5MB-per-file caps apply server-side too.
|
|
757
|
+
- Per-file 5MB cap on the client matches the bot's reader.
|
|
758
|
+
- Slug defaults to the cwd's `launchpad-app-<slug>` suffix;
|
|
759
|
+
`--slug` overrides.
|
|
760
|
+
- `--message` threaded as `x-launchpad-message` header for
|
|
761
|
+
forward-compat with a bot that consumes it.
|
|
762
|
+
- `src/deploy/git-files.ts` — `git ls-files -co --exclude-standard
|
|
763
|
+
-z` wrapper. Sorts lex for reproducible tarballs.
|
|
764
|
+
- `src/deploy/tar-pack.ts` — POSIX ustar writer + gzip pipeline.
|
|
765
|
+
Round-trips against `src/clone/tar-extract.ts` (slice-4 reader)
|
|
766
|
+
so we don't independently mis-implement the format on both
|
|
767
|
+
sides.
|
|
768
|
+
|
|
769
|
+
### Deferred
|
|
770
|
+
|
|
771
|
+
- `git merge-base` staleness check against bot-served `main` HEAD.
|
|
772
|
+
No bot endpoint exposes the HEAD SHA cheaply today; the bot's
|
|
773
|
+
deploy-pr handler produces a reasonable PR diff regardless.
|
|
774
|
+
Tracked for a follow-up; clearly marked TODO in
|
|
775
|
+
`src/commands/deploy.ts`.
|
|
776
|
+
|
|
777
|
+
## 0.4.0 — 2026-05-10
|
|
778
|
+
|
|
779
|
+
### Added
|
|
780
|
+
|
|
781
|
+
- `launchpad clone <slug>` — streams the app's source tarball
|
|
782
|
+
from the bot's `/apps/<slug>/source-bundle`, decompresses gzip,
|
|
783
|
+
walks the POSIX ustar entries, strips GitHub's
|
|
784
|
+
`<owner>-<repo>-<sha>/` prefix, writes files to
|
|
785
|
+
`./launchpad-app-<slug>/`, and runs `git init -b main` + an
|
|
786
|
+
initial commit. No remote configured (deploys go via
|
|
787
|
+
`launchpad deploy`, not `git push`).
|
|
788
|
+
- `src/clone/tar-extract.ts` — Node-side port of the bot's
|
|
789
|
+
hand-rolled ustar reader. Same MAX_ENTRY_SIZE / MAX_ENTRY_COUNT
|
|
790
|
+
caps. Path-traversal guard rejects entries that resolve outside
|
|
791
|
+
the destination dir. `DestinationNotEmptyError` is distinct from
|
|
792
|
+
`TarballParseError` so the verb refuses cleanup when the dest is
|
|
793
|
+
the user's pre-existing dir.
|
|
794
|
+
- `src/clone/git-init.ts` — minimal `git init` runner via
|
|
795
|
+
`node:child_process.spawn`. Uses inline `-c user.email` /
|
|
796
|
+
`-c user.name` so a missing global git config doesn't break
|
|
797
|
+
the initial commit. `--no-gpg-sign` defends against
|
|
798
|
+
mandatory-sign configs failing on a fresh repo.
|
|
799
|
+
|
|
800
|
+
## 0.3.0 — 2026-05-10
|
|
801
|
+
|
|
802
|
+
### Added
|
|
803
|
+
|
|
804
|
+
- `launchpad apps` — list apps the caller has access to via
|
|
805
|
+
`GET /me/apps`. Renders a fixed-width table sorted by the bot's
|
|
806
|
+
`provisioning → failed → live` order. Empty result prints a
|
|
807
|
+
friendly "no apps yet" hint rather than a blank table.
|
|
808
|
+
- `launchpad whoami` — show the current session's email + sub
|
|
809
|
+
(decoded from the access-token JWT payload, no signature check —
|
|
810
|
+
display only), session expiry both relative and absolute, and a
|
|
811
|
+
per-app role summary.
|
|
812
|
+
- Shared HTTP layer (`src/http/api-client.ts`):
|
|
813
|
+
- `apiJson<T>` for typed JSON-returning calls.
|
|
814
|
+
- `apiRaw` for streaming consumers (slice 4's `clone` will use
|
|
815
|
+
this; the body stays a stream rather than a buffered string).
|
|
816
|
+
- Status mapping: 401 → `UnauthenticatedError` ("run `launchpad
|
|
817
|
+
login`"), 403 → `ForbiddenError`, 404 → `NotFoundError`,
|
|
818
|
+
other non-2xx → `ApiError` with status, network failures →
|
|
819
|
+
`TransportError`.
|
|
820
|
+
- `getValidAccessToken` is called up front, so the bearer is
|
|
821
|
+
refreshed silently before every request.
|
|
822
|
+
- Tiny payload-only JWT decoder (`src/auth/jwt.ts`) — used by
|
|
823
|
+
`whoami`. Does NOT validate signatures; the bot does that.
|
|
824
|
+
|
|
825
|
+
## 0.2.0 — 2026-05-10
|
|
826
|
+
|
|
827
|
+
### Added
|
|
828
|
+
|
|
829
|
+
- `launchpad login` — Cloudflare Access Managed OAuth (PKCE) flow:
|
|
830
|
+
discovery, dynamic client registration, code-challenge regen quirk
|
|
831
|
+
(Cloudflare requires the challenge to start with `[a-zA-Z0-9]`),
|
|
832
|
+
loopback-bound callback server (`127.0.0.1:0`, OS-assigned port),
|
|
833
|
+
cross-platform browser opener, code-for-token exchange, persisted
|
|
834
|
+
session at `~/.launchpad/session.json` (mode `0600`).
|
|
835
|
+
- `launchpad logout` — zero-out the persisted session. Idempotent.
|
|
836
|
+
- `getValidAccessToken(sessionPath)` — symmetric read path for
|
|
837
|
+
subsequent slices: returns the current access token if still valid,
|
|
838
|
+
otherwise silently refreshes via `refresh_token`. 4xx on refresh
|
|
839
|
+
surfaces as `LoginRequiredError` ("session expired — run
|
|
840
|
+
`launchpad login`"); 5xx propagates the underlying error.
|
|
841
|
+
- `LAUNCHPAD_BOT_URL` and `LAUNCHPAD_SESSION_PATH` env-var overrides
|
|
842
|
+
for local-dev / preview / integration-test scenarios.
|
|
843
|
+
|
|
844
|
+
## 0.1.0 — 2026-05-10
|
|
845
|
+
|
|
846
|
+
### Added
|
|
847
|
+
|
|
848
|
+
- Initial package scaffolding under SCOPE-M-760 / T5 (M-765) slice 1.
|
|
849
|
+
- Bin entry `launchpad` with `--help` / `-h` and `--version` / `-v`.
|
|
850
|
+
- Hand-rolled argument dispatcher (no `commander` / `yargs` dep) —
|
|
851
|
+
the verb table is small enough that a switch is more legible than
|
|
852
|
+
another runtime dependency. Real commands wire in via subsequent
|
|
853
|
+
slices.
|
|
854
|
+
- TypeScript strict, ESM-only, Node ≥ 20.
|