@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.
Files changed (191) hide show
  1. package/CHANGELOG.md +854 -0
  2. package/README.md +109 -0
  3. package/dist/auth/browser.d.ts +18 -0
  4. package/dist/auth/browser.d.ts.map +1 -0
  5. package/dist/auth/callback-server.d.ts +24 -0
  6. package/dist/auth/callback-server.d.ts.map +1 -0
  7. package/dist/auth/discovery.d.ts +25 -0
  8. package/dist/auth/discovery.d.ts.map +1 -0
  9. package/dist/auth/flow.d.ts +39 -0
  10. package/dist/auth/flow.d.ts.map +1 -0
  11. package/dist/auth/jwt.d.ts +27 -0
  12. package/dist/auth/jwt.d.ts.map +1 -0
  13. package/dist/auth/pkce.d.ts +26 -0
  14. package/dist/auth/pkce.d.ts.map +1 -0
  15. package/dist/auth/registration.d.ts +8 -0
  16. package/dist/auth/registration.d.ts.map +1 -0
  17. package/dist/auth/session.d.ts +54 -0
  18. package/dist/auth/session.d.ts.map +1 -0
  19. package/dist/auth/token.d.ts +37 -0
  20. package/dist/auth/token.d.ts.map +1 -0
  21. package/dist/bundle/cron-bundle.d.ts +77 -0
  22. package/dist/bundle/cron-bundle.d.ts.map +1 -0
  23. package/dist/bundle/cwd-walker.d.ts +43 -0
  24. package/dist/bundle/cwd-walker.d.ts.map +1 -0
  25. package/dist/bundle/orchestrate.d.ts +51 -0
  26. package/dist/bundle/orchestrate.d.ts.map +1 -0
  27. package/dist/bundle/upload.d.ts +66 -0
  28. package/dist/bundle/upload.d.ts.map +1 -0
  29. package/dist/cli.d.ts +3 -0
  30. package/dist/cli.d.ts.map +1 -0
  31. package/dist/cli.js +9757 -0
  32. package/dist/clone/git-init.d.ts +18 -0
  33. package/dist/clone/git-init.d.ts.map +1 -0
  34. package/dist/clone/tar-extract.d.ts +59 -0
  35. package/dist/clone/tar-extract.d.ts.map +1 -0
  36. package/dist/commands/apps.d.ts +14 -0
  37. package/dist/commands/apps.d.ts.map +1 -0
  38. package/dist/commands/channel-auth.d.ts +31 -0
  39. package/dist/commands/channel-auth.d.ts.map +1 -0
  40. package/dist/commands/clone.d.ts +3 -0
  41. package/dist/commands/clone.d.ts.map +1 -0
  42. package/dist/commands/create.d.ts +27 -0
  43. package/dist/commands/create.d.ts.map +1 -0
  44. package/dist/commands/deploy-flags.d.ts +75 -0
  45. package/dist/commands/deploy-flags.d.ts.map +1 -0
  46. package/dist/commands/deploy-modes.d.ts +59 -0
  47. package/dist/commands/deploy-modes.d.ts.map +1 -0
  48. package/dist/commands/deploy.d.ts +29 -0
  49. package/dist/commands/deploy.d.ts.map +1 -0
  50. package/dist/commands/destroy.d.ts +14 -0
  51. package/dist/commands/destroy.d.ts.map +1 -0
  52. package/dist/commands/envvars.d.ts +28 -0
  53. package/dist/commands/envvars.d.ts.map +1 -0
  54. package/dist/commands/generate.d.ts +3 -0
  55. package/dist/commands/generate.d.ts.map +1 -0
  56. package/dist/commands/groups-whoami.d.ts +3 -0
  57. package/dist/commands/groups-whoami.d.ts.map +1 -0
  58. package/dist/commands/groups.d.ts +3 -0
  59. package/dist/commands/groups.d.ts.map +1 -0
  60. package/dist/commands/init.d.ts +44 -0
  61. package/dist/commands/init.d.ts.map +1 -0
  62. package/dist/commands/login.d.ts +3 -0
  63. package/dist/commands/login.d.ts.map +1 -0
  64. package/dist/commands/logout.d.ts +3 -0
  65. package/dist/commands/logout.d.ts.map +1 -0
  66. package/dist/commands/logs.d.ts +16 -0
  67. package/dist/commands/logs.d.ts.map +1 -0
  68. package/dist/commands/merge.d.ts +29 -0
  69. package/dist/commands/merge.d.ts.map +1 -0
  70. package/dist/commands/plan.d.ts +3 -0
  71. package/dist/commands/plan.d.ts.map +1 -0
  72. package/dist/commands/pull.d.ts +12 -0
  73. package/dist/commands/pull.d.ts.map +1 -0
  74. package/dist/commands/review.d.ts +22 -0
  75. package/dist/commands/review.d.ts.map +1 -0
  76. package/dist/commands/rollback.d.ts +3 -0
  77. package/dist/commands/rollback.d.ts.map +1 -0
  78. package/dist/commands/secrets-template.d.ts +3 -0
  79. package/dist/commands/secrets-template.d.ts.map +1 -0
  80. package/dist/commands/secrets.d.ts +3 -0
  81. package/dist/commands/secrets.d.ts.map +1 -0
  82. package/dist/commands/skills.d.ts +13 -0
  83. package/dist/commands/skills.d.ts.map +1 -0
  84. package/dist/commands/status.d.ts +54 -0
  85. package/dist/commands/status.d.ts.map +1 -0
  86. package/dist/commands/update.d.ts +114 -0
  87. package/dist/commands/update.d.ts.map +1 -0
  88. package/dist/commands/validate.d.ts +3 -0
  89. package/dist/commands/validate.d.ts.map +1 -0
  90. package/dist/commands/whoami.d.ts +3 -0
  91. package/dist/commands/whoami.d.ts.map +1 -0
  92. package/dist/config.d.ts +11 -0
  93. package/dist/config.d.ts.map +1 -0
  94. package/dist/deploy/apply.d.ts +29 -0
  95. package/dist/deploy/apply.d.ts.map +1 -0
  96. package/dist/deploy/dry-run.d.ts +13 -0
  97. package/dist/deploy/dry-run.d.ts.map +1 -0
  98. package/dist/deploy/git-files.d.ts +33 -0
  99. package/dist/deploy/git-files.d.ts.map +1 -0
  100. package/dist/deploy/group-pin.d.ts +66 -0
  101. package/dist/deploy/group-pin.d.ts.map +1 -0
  102. package/dist/deploy/manifest-state.d.ts +20 -0
  103. package/dist/deploy/manifest-state.d.ts.map +1 -0
  104. package/dist/deploy/manifest-status.d.ts +11 -0
  105. package/dist/deploy/manifest-status.d.ts.map +1 -0
  106. package/dist/deploy/resolve.d.ts +53 -0
  107. package/dist/deploy/resolve.d.ts.map +1 -0
  108. package/dist/deploy/rollback.d.ts +23 -0
  109. package/dist/deploy/rollback.d.ts.map +1 -0
  110. package/dist/deploy/runner.d.ts +29 -0
  111. package/dist/deploy/runner.d.ts.map +1 -0
  112. package/dist/deploy/stage-exit-codes.d.ts +41 -0
  113. package/dist/deploy/stage-exit-codes.d.ts.map +1 -0
  114. package/dist/deploy/status-polling.d.ts +37 -0
  115. package/dist/deploy/status-polling.d.ts.map +1 -0
  116. package/dist/deploy/tar-pack.d.ts +22 -0
  117. package/dist/deploy/tar-pack.d.ts.map +1 -0
  118. package/dist/detect/index.d.ts +53 -0
  119. package/dist/detect/index.d.ts.map +1 -0
  120. package/dist/dispatcher.d.ts +30 -0
  121. package/dist/dispatcher.d.ts.map +1 -0
  122. package/dist/groups/client.d.ts +62 -0
  123. package/dist/groups/client.d.ts.map +1 -0
  124. package/dist/http/api-client.d.ts +33 -0
  125. package/dist/http/api-client.d.ts.map +1 -0
  126. package/dist/http/errors.d.ts +31 -0
  127. package/dist/http/errors.d.ts.map +1 -0
  128. package/dist/manifest/load.d.ts +38 -0
  129. package/dist/manifest/load.d.ts.map +1 -0
  130. package/dist/manifest/schema.d.ts +3 -0
  131. package/dist/manifest/schema.d.ts.map +1 -0
  132. package/dist/postinstall.d.ts +3 -0
  133. package/dist/postinstall.d.ts.map +1 -0
  134. package/dist/postinstall.js +37 -0
  135. package/dist/secrets/env-parse.d.ts +19 -0
  136. package/dist/secrets/env-parse.d.ts.map +1 -0
  137. package/dist/secrets/push.d.ts +13 -0
  138. package/dist/secrets/push.d.ts.map +1 -0
  139. package/dist/secrets/set.d.ts +19 -0
  140. package/dist/secrets/set.d.ts.map +1 -0
  141. package/dist/secrets/status.d.ts +19 -0
  142. package/dist/secrets/status.d.ts.map +1 -0
  143. package/dist/types/api.d.ts +112 -0
  144. package/dist/types/api.d.ts.map +1 -0
  145. package/dist/update-notifier.d.ts +69 -0
  146. package/dist/update-notifier.d.ts.map +1 -0
  147. package/dist/version.d.ts +2 -0
  148. package/dist/version.d.ts.map +1 -0
  149. package/package.json +62 -0
  150. package/skills/README.md +100 -0
  151. package/skills/_partials/shell-contract.md +42 -0
  152. package/skills/launchpad-content-pr/SKILL.md +255 -0
  153. package/skills/launchpad-deploy/SKILL.md +415 -0
  154. package/skills/launchpad-deploy-status/SKILL.md +231 -0
  155. package/skills/launchpad-destroy/SKILL.md +317 -0
  156. package/skills/launchpad-onboard/SKILL.md +179 -0
  157. package/skills/launchpad-status/SKILL.md +263 -0
  158. package/skills/marquee-share/README.md +155 -0
  159. package/skills/marquee-share/SKILL.md +94 -0
  160. package/skills/marquee-share/SYNC.md +27 -0
  161. package/skills/marquee-share/dist/cli.js +896 -0
  162. package/skills/marquee-share/eslint.config.mjs +71 -0
  163. package/skills/marquee-share/install.sh +103 -0
  164. package/skills/marquee-share/package-lock.json +3946 -0
  165. package/skills/marquee-share/package.json +30 -0
  166. package/skills/marquee-share/src/auth/PROVENANCE.md +103 -0
  167. package/skills/marquee-share/src/auth/browser.ts +75 -0
  168. package/skills/marquee-share/src/auth/callback-server.ts +171 -0
  169. package/skills/marquee-share/src/auth/discovery.ts +171 -0
  170. package/skills/marquee-share/src/auth/flow.ts +262 -0
  171. package/skills/marquee-share/src/auth/index.ts +171 -0
  172. package/skills/marquee-share/src/auth/jwt.ts +77 -0
  173. package/skills/marquee-share/src/auth/pkce.ts +79 -0
  174. package/skills/marquee-share/src/auth/registration.ts +87 -0
  175. package/skills/marquee-share/src/auth/session.ts +205 -0
  176. package/skills/marquee-share/src/auth/token.ts +162 -0
  177. package/skills/marquee-share/src/cli.ts +246 -0
  178. package/skills/marquee-share/src/config.ts +101 -0
  179. package/skills/marquee-share/src/render/template.ts +171 -0
  180. package/skills/marquee-share/src/upload/index.ts +11 -0
  181. package/skills/marquee-share/src/upload/upload.ts +191 -0
  182. package/skills/marquee-share/tests/cli.test.ts +281 -0
  183. package/skills/marquee-share/tests/config.test.ts +119 -0
  184. package/skills/marquee-share/tests/flow.test.ts +356 -0
  185. package/skills/marquee-share/tests/no-token-leak.test.ts +240 -0
  186. package/skills/marquee-share/tests/pkce.test.ts +121 -0
  187. package/skills/marquee-share/tests/session.test.ts +173 -0
  188. package/skills/marquee-share/tests/template.test.ts +170 -0
  189. package/skills/marquee-share/tests/upload.test.ts +311 -0
  190. package/skills/marquee-share/tsconfig.json +23 -0
  191. 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.