@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
@@ -0,0 +1,415 @@
1
+ ---
2
+ name: launchpad-deploy
3
+ description: Walk a Launchpad user through deploying an app from their local working directory (Model A — `launchpad init` + `launchpad deploy`). Wraps the CLI verbs end-to-end: detects the app shape, scaffolds `launchpad.yaml`, resolves the allowed Entra group via `launchpad groups`, bundles the CWD via `launchpad deploy`, and tails the resulting content PR. Use when someone says "deploy a new app", "ship my app to Launchpad", "/launchpad-deploy", "I have an app locally — get it on Launchpad", or any variant. Resume/abandon for legacy in-flight provisioning is at the bottom.
4
+ version: 0.23.0
5
+ ---
6
+
7
+ <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
8
+ ## Shell contract — read this first
9
+
10
+ Every fenced `bash` block below MUST be sent to the `Bash` tool **verbatim**.
11
+ Do not rewrite into PowerShell, cmd, zsh-isms, or "equivalent" forms.
12
+
13
+ - macOS / Linux: the `Bash` tool runs system bash.
14
+ - Windows: the `Bash` tool runs Git for Windows (MSYS) bash. `$HOME`,
15
+ forward slashes, `test -f`, `command -v`, heredocs, and `[[ … ]]` all
16
+ work. There is no reason to translate to `Test-Path`, `$env:USERPROFILE`,
17
+ `Get-Content`, `Where-Object`, `Get-ChildItem`, or backslash paths —
18
+ doing so will fail with `/usr/bin/bash: syntax error`.
19
+
20
+ If a step genuinely needs OS branching, branch *inside* bash:
21
+
22
+ ```bash
23
+ case "$(uname -s)" in
24
+ Darwin) : ;;
25
+ Linux) : ;;
26
+ MINGW*|MSYS*|CYGWIN*) : ;;
27
+ esac
28
+ ```
29
+ <!-- END shell-contract -->
30
+
31
+ # /launchpad-deploy
32
+
33
+ Model A deploy flow: the user already has an app in their working
34
+ directory (Vite/React, static, or container-shape), and wants it
35
+ running on Launchpad. The CLI handles everything end-to-end —
36
+ detection, scaffolding, group resolution, bundling, upload, and the
37
+ content PR the bot opens on their behalf. **No `gh`, `jq`, or `curl`
38
+ required; no M-KOPA GitHub access required.** External users with a
39
+ Cf Access account are first-class.
40
+
41
+ ## Constants (single source of truth)
42
+
43
+ These values are referenced by `/launchpad-deploy-status`,
44
+ `/launchpad-content-pr`, and `/launchpad-status`; if the bot's
45
+ contract changes, update them **here only** and the other skills
46
+ inherit the change.
47
+
48
+ ### Bot URL
49
+
50
+ ```
51
+ https://launchpad-portal-bot.mkopa-launchpad.workers.dev
52
+ ```
53
+
54
+ Override with `$LAUNCHPAD_BOT_URL` for staging. The CLI reads this
55
+ itself; the playbook never speaks to the bot directly.
56
+
57
+ ### App types
58
+
59
+ ```
60
+ static · react · react+api · container
61
+ ```
62
+
63
+ ## Pre-flight
64
+
65
+ ```bash
66
+ launchpad whoami
67
+ ```
68
+
69
+ - **Exit 0** → identity + session expiry + role per app printed.
70
+ Proceed.
71
+ - **Any failure** → tell the user to run `/launchpad-onboard` first
72
+ (it covers the install + login dance) and exit.
73
+
74
+ If you need to remind a returning user what they already have:
75
+
76
+ ```bash
77
+ launchpad apps
78
+ ```
79
+
80
+ This lists every app the caller can see (own + editor + break-glass).
81
+
82
+ ## Action selector
83
+
84
+ Use `AskUserQuestion` to pick the path. Two options, single-select:
85
+
86
+ | Label | What it does |
87
+ |---|---|
88
+ | Deploy from this directory (Model A) | The recommended path. Runs `launchpad init` then `launchpad deploy` against the CWD. Works for external users. |
89
+ | Recover a legacy in-flight deploy | Resume or abandon a slug stuck in the pre-Model-A zero-touch flow. M-KOPA-internal only. |
90
+
91
+ > Note: `AskUserQuestion` is a deferred tool. Before the first call,
92
+ > run `ToolSearch` with query `select:AskUserQuestion` to load its
93
+ > schema.
94
+
95
+ ## Model A — deploy from this directory
96
+
97
+ This is the primary flow. The user's local working directory is the
98
+ source of truth; the CLI bundles it and ships it.
99
+
100
+ ### A.1 Confirm the working directory
101
+
102
+ Ask: "Is the app you want to deploy in your current directory? If
103
+ not, `cd` there first and re-invoke the skill."
104
+
105
+ If the user is unsure, `launchpad init` itself runs an auto-detector
106
+ (M-1216 T2): it looks for `package.json`, `vite.config.{ts,js}`, a
107
+ lockfile, a `functions/` subdirectory, etc., and infers app-type +
108
+ package manager + build command + dest dir. Surface what it
109
+ discovered before committing to a slug.
110
+
111
+ ### A.2 Scaffold `launchpad.yaml`
112
+
113
+ ```bash
114
+ launchpad init
115
+ ```
116
+
117
+ This is interactive by default. It will ask for:
118
+
119
+ - **slug** — `lowercase-with-hyphens`, 3–30 chars, must not start or
120
+ end with a hyphen. Reuse-rejection is server-side; you don't need
121
+ to pre-check.
122
+ - **display name** — free text, shown in the portal catalogue.
123
+ - **app type** — `static`, `react`, `react+api`, or `container`. The
124
+ detector pre-selects the right one for Vite/React layouts; the
125
+ user overrides if needed.
126
+ - **team / owner** — for the registry.
127
+ - **allowed Entra group** — see §A.3.
128
+
129
+ Non-interactive form for scripting:
130
+
131
+ ```bash
132
+ launchpad init --non-interactive \
133
+ --slug <slug> \
134
+ --display-name "<display name>" \
135
+ --app-type <apptype> \
136
+ --team <team> \
137
+ --owner <owner-email> \
138
+ --group <group>
139
+ ```
140
+
141
+ `--app-type` must be one of `static`, `react`, `react+api`, or
142
+ `container`. `--group` accepts the Entra group's display name or
143
+ UUID (the bot canonicalises to the UUID).
144
+
145
+ `launchpad init` writes `./launchpad.yaml` and exits 0. Re-running
146
+ against an existing file is rejected unless `--force` is passed —
147
+ the manifest is the user's authored artefact, the CLI does not
148
+ silently overwrite it.
149
+
150
+ **Stack constraints by app-type** (single source of truth:
151
+ `launchpad-platform/ARCHITECTURE.md § Tech Stack` and `PATTERNS.md §
152
+ Approved Libraries`). Surface this table to the user **before** they
153
+ commit to a slug:
154
+
155
+ | `appType` | Server framework | Data layer | Background work | Notes |
156
+ |-------------|-----------------------|-----------------------------------------|------------------------|--------------------------------------------------------|
157
+ | `static` | n/a | n/a | n/a | No bundler, no TS, no React. |
158
+ | `react` | n/a | client-side or remote-fetch only | n/a | SPA only — no server-side anything. |
159
+ | `react+api` | `hono` (exclusive) | Cloudflare D1 / Neon (HTTP) / KV / R2 | **Sibling cron Worker** (Pages has no `scheduled` handler — see "Two-tier apps" below) | Requires `compatibility_flags = ["nodejs_compat"]` in `wrangler.toml` (ADR-0011 carve-out). |
160
+ | `container` | any HTTP server | any (container-local or HTTP backend) | container-local | Single HTTP port; deployed via Cloudflare Containers. |
161
+
162
+ **Will not run on Launchpad** (representative — not exhaustive):
163
+ `fastify` / `@fastify/*` / `express` / `koa` / `@koa/*` /
164
+ `@nestjs/*` / `hapi` / `@hapi/*`, `better-sqlite3` / native
165
+ `sqlite3`, native TCP drivers (`pg`, `mysql`, `mysql2`, `mongodb`,
166
+ `mongoose`, `redis`, `ioredis`), `dotenv`, `setInterval` /
167
+ `setTimeout` daemons, top-level `Dockerfile` / `docker-compose.yml`
168
+ on non-container app-types, `nginx.conf`, `Procfile`, `pm2`,
169
+ `pm2.config.js`, `ecosystem.config.js`, `forever`, `nodemon`. The
170
+ **canonical validation list** is `/launchpad-content-pr` § Stack-fit
171
+ pre-flight — that skill enforces the gate at deploy time. If the
172
+ existing app uses any of these, plan the swap **before** picking a
173
+ slug — do not deploy first and port later.
174
+
175
+ ### A.3 Allowed Entra group
176
+
177
+ The CLI resolves Entra groups via the bot's `/groups` endpoint
178
+ (Entra-canonical, per M-940). The playbook never speaks to that
179
+ endpoint directly. Two helpers:
180
+
181
+ ```bash
182
+ launchpad groups list # every group the caller can see
183
+ launchpad groups search <query> # fuzzy match by name / nickname / id
184
+ launchpad groups show <name> # UUID + displayName + mailNickname
185
+ ```
186
+
187
+ When the user picks a group, pass either the **displayName** or the
188
+ **UUID** to `launchpad init --group <…>` — the CLI accepts both and
189
+ the bot canonicalises to the UUID.
190
+
191
+ If `launchpad groups list` fails with:
192
+
193
+ - `503 graph_not_configured` → the bot's Graph credentials aren't
194
+ wired; route the user to platform-team. `ENTRA_GRAPH_TENANT_ID`
195
+ and `ENTRA_GRAPH_CLIENT_ID` are non-secret identifiers (live in
196
+ `wrangler.toml` `[vars]`); only `ENTRA_GRAPH_CLIENT_SECRET`
197
+ requires `wrangler secret put`.
198
+ - `502 graph_auth_failed` / `graph_fetch_failed` → the Entra app's
199
+ Graph permission grant (`Group.Read.All` or
200
+ `GroupMember.Read.All`) is missing admin consent, or Graph is
201
+ unreachable. Surface the error body.
202
+ - empty list → no groups visible to the bot; check the Graph
203
+ permission scope.
204
+
205
+ Use `launchpad groups whoami` to remind the user which groups
206
+ **they** are currently a member of — handy when an app is gated and
207
+ they want to confirm they'll still be able to reach it.
208
+
209
+ ### A.4 (Optional) Validate before shipping
210
+
211
+ ```bash
212
+ launchpad validate
213
+ ```
214
+
215
+ Parses `launchpad.yaml` against the v1alpha1 schema and reports
216
+ problems. Doesn't talk to the bot. Useful when the user wants a
217
+ second look before paying for an upload + PR round-trip.
218
+
219
+ ```bash
220
+ launchpad plan
221
+ ```
222
+
223
+ Same, but also summarises what the manifest would deploy: hostnames,
224
+ build command, destination directory, allowed group. Still offline.
225
+
226
+ ### A.5 Deploy
227
+
228
+ ```bash
229
+ launchpad deploy
230
+ ```
231
+
232
+ Bundles the working tree (using `git ls-files -co --exclude-standard`
233
+ where available; falls back to a pure-FS walker honouring
234
+ `.gitignore` and a default-ignore set), gzips it, and POSTs to the
235
+ bot's `/apps/<slug>/deploy/bundle` endpoint.
236
+
237
+ **First deploy vs subsequent deploys** (M-1234). Under Model A there
238
+ is no separate "create the app" step — `launchpad deploy` against a
239
+ fresh slug auto-provisions:
240
+
241
+ - **First deploy** (slug never seen): the bot reads the manifest,
242
+ claims the slug in its registry, and kicks the provisioning
243
+ workflow (TF apply, cert issuance, edge auth, app-repo scaffold).
244
+ New apps default to the platform **Entra-OIDC gateway**
245
+ (`auth: gateway`); pass `--auth access` to `launchpad init` to use a
246
+ per-app Cloudflare Access app instead. The CLI prints `✓ First-time deploy — provisioning
247
+ workflow started` and exits 0. Provisioning typically takes
248
+ 5–10 minutes. Watch it with `launchpad status <slug>` and re-run
249
+ `launchpad deploy` once lifecycle hits `live`.
250
+ - **Subsequent deploys** (slug already live): the bot extracts the
251
+ tarball, runs the ingest gates (forbidden file types, oversized
252
+ binaries, secret patterns, build-command allowlist), commits the
253
+ bundle into `launchpad-app-<slug>` via the GitHub App, and CF Pages
254
+ auto-deploys on the push. The CLI prints `✓ Bundle accepted —
255
+ committed as <sha>`.
256
+
257
+ Concurrent first-deploys against the same slug: the first request
258
+ wins (gets the `provisioning_started` response); the second gets HTTP
259
+ 409 `slug_in_flight`. The CLI surfaces this as "slug is already
260
+ provisioning" — wait for the existing workflow to land before
261
+ re-trying.
262
+
263
+ The CLI exits as soon as the bot acknowledges the upload (202). For
264
+ subsequent deploys it prints the commit short-SHA + repo; for first
265
+ deploys it prints provisioning guidance. Use `launchpad status
266
+ <slug>` to watch lifecycle progress to its terminal state.
267
+
268
+ Common flags:
269
+
270
+ - **`--slug <slug>`** — explicit override. Defaults to the slug from
271
+ `./launchpad.yaml`.
272
+ - **`--message <text>`** — threaded as the PR description. Useful
273
+ for change logs.
274
+ - **`--file <path>`** — point at a non-default manifest path.
275
+
276
+ ### A.6 Terminal handling
277
+
278
+ - **`done`** → "🎉 demo-X is live at `https://<slug>.launchpad.m-kopa.us`.
279
+ Run `/launchpad-status` to confirm; `/launchpad-content-pr` is no
280
+ longer needed under Model A (the first deploy already shipped your
281
+ content)."
282
+ - **`failed` / `bundle_rejected` / `cf-pages-poll-unrecoverable`** →
283
+ run `/launchpad-deploy-status <slug>` and surface the failure
284
+ reason. The bundle policy errors (oversized files, forbidden
285
+ symlinks, secret-pattern hits, build-command violations) are
286
+ self-describing in the CLI's stderr; surface them verbatim and let
287
+ the user fix and re-`launchpad deploy`.
288
+ - **Anything else terminal** → run `/launchpad-deploy-status <slug>`
289
+ and surface the diagnostic.
290
+
291
+ ## Two-tier apps (Pages + sibling cron Worker)
292
+
293
+ A `react+api` app that needs **scheduled background work** (e.g. an hourly
294
+ ingest) is **two-tier**: the Pages `react+api` dashboard **plus a sibling cron
295
+ Worker**. Pages Functions have **no `scheduled` handler** — a cron trigger
296
+ lives only on a Worker (ADR 0022). The operator stays **CLI-only**: `launchpad
297
+ deploy` bundles the Worker locally (esbuild) and ships it; the bot does every
298
+ Cloudflare write. **No `wrangler`.**
299
+
300
+ Declare the Worker in the manifest's `targets[]`. The `schedule` is what makes
301
+ it a **deploy** surface (a worker target *without* a schedule is a secrets-only
302
+ target, not deployed):
303
+
304
+ ```yaml
305
+ targets:
306
+ - kind: worker
307
+ script: <slug>-ingest-cron # MUST equal `name` in the worker's cron/wrangler.toml
308
+ schedule:
309
+ - "0 * * * *" # one or more cron expressions
310
+ d1_binding: DB # shared D1 binding name (optional; omit for a pure-fetch cron)
311
+ ```
312
+
313
+ On `launchpad deploy` (a *subsequent* deploy, after the slug is `live`) the bot:
314
+ ensures the shared D1 (named after the slug — **create-or-adopt, never
315
+ deleted**), uploads the CLI-built Worker module, sets its cron trigger, and
316
+ binds the **same** D1 on **both** tiers. The Worker's `cron/wrangler.toml`
317
+ supplies `main` + `compatibility_date` + `compatibility_flags`; its
318
+ `[[d1_databases]]` block is **ignored** on the launchpad path (the bot owns the
319
+ binding + id).
320
+
321
+ **Secrets to both tiers.** A secret with `targets: all` in the manifest fans to
322
+ the Pages tier **and** the cron Worker via `launchpad secrets push` — the bot
323
+ pushes both with its broker token. Verify with `launchpad secrets status
324
+ <slug>` (PRESENT on both surfaces). **Never `wrangler secret put`** — the
325
+ operator has no `wrangler`.
326
+
327
+ ## Gateway auth — the failure classes (assert these; do not relearn them)
328
+
329
+ New apps default to `auth: gateway` (the platform Entra-OIDC gateway). For a
330
+ `react+api` (verifier-bearing) app the per-app Terraform emits the verifier
331
+ wiring automatically — **do not hand-edit or fight it**:
332
+
333
+ - **`confine_origin = false` is intentional, keep it.** CF Access service-token
334
+ confinement **strips the gateway's user JWT** from `Cf-Access-Jwt-Assertion`
335
+ → the app sees only the service token and returns **401** for every user. The
336
+ per-app TF sets `confine_origin = false` for gateway react+api apps so the
337
+ user JWT reaches the origin. Re-enabling confinement re-breaks auth.
338
+ - **Canonical JWKS, not the CF Access certs path.** The verifier must fetch keys
339
+ from the gateway's `JWKS_URL`
340
+ (`https://auth.launchpad.m-kopa.us/.well-known/jwks.json`), **not**
341
+ `${team}/cdn-cgi/access/certs`. App code prefers `env.JWKS_URL` when set.
342
+ - **Auth env MUST be `secret_text`, never `plain_text`.** `CF_ACCESS_TEAM_DOMAIN`
343
+ (= the gateway issuer), `CF_ACCESS_AUD` (= the app host), and `JWKS_URL` are
344
+ emitted as `secret_text` by the per-app TF. **`plain_text` env vars are wiped
345
+ on every `launchpad deploy`; `secret_text` survives.** A verifier env var as
346
+ `plain_text` ⇒ the app is one deploy away from 401-ing.
347
+ - **`aud`/`iss` are exact-matched.** The gateway mints `iss` = the gateway
348
+ issuer and `aud` = the app's own host; a wrong or duplicate `CF_ACCESS_AUD`
349
+ ⇒ `AUD_MISMATCH`. One audience, the app's host.
350
+ - **`wrangler kv` defaults to LOCAL.** If you ever inspect gateway KV manually,
351
+ `wrangler kv ... --remote` is required — without `--remote` you read/write a
352
+ local sandbox namespace and see phantom/empty results.
353
+
354
+ ## Legacy — recover an in-flight provisioning
355
+
356
+ These modes are for **M-KOPA-internal apps stuck in the pre-Model-A
357
+ zero-touch flow** (M-892). They require M-KOPA GH access and assume
358
+ the bot already opened a `launchpad-platform` TF PR for the slug.
359
+ External users will never need this branch.
360
+
361
+ ### Resume
362
+
363
+ ```bash
364
+ launchpad deploy --resume <slug>
365
+ ```
366
+
367
+ Re-fires the bot's resume endpoint against an existing failed slug.
368
+ Terminal handling is the same as §A.6.
369
+
370
+ ### Abandon
371
+
372
+ Confirm via `AskUserQuestion`:
373
+
374
+ | Label | What it does |
375
+ |---|---|
376
+ | Yes, abandon `<slug>` (Recommended) | Frees the slug. Any in-flight provisioning is marked terminal. **Does NOT delete the GitHub repo or any Cloudflare resources** — those need separate cleanup. |
377
+ | No, keep | Aborts the abandon. |
378
+
379
+ ```bash
380
+ launchpad deploy --abandon <slug>
381
+ ```
382
+
383
+ For destructive teardown of a fully-provisioned app, the right verb
384
+ is `launchpad destroy <slug>` (covered in `/launchpad-destroy`) —
385
+ that one cleans up Cloudflare + the platform-repo TF entries +
386
+ archive-renames the app repo. `--abandon` only frees the slug for
387
+ the in-flight case.
388
+
389
+ ## Don'ts
390
+
391
+ - Do **not** hardcode app-type or group choices — `launchpad init`'s
392
+ auto-detect and `launchpad groups list` are the source of truth.
393
+ - Do **not** assume a `git clone` or local platform-repo checkout is
394
+ needed. The CLI bundles the CWD directly and the bot owns every
395
+ downstream side-effect.
396
+ - Do **not** shell out to `gh`, `jq`, `curl`, or `git` from this
397
+ skill — the CLI verbs cover every step. A regression that
398
+ re-introduces a system dependency surfaces in the
399
+ `launchpad-cli-e2e` zero-deps matrix.
400
+ - Do **not** run `--abandon` without explicit `AskUserQuestion`
401
+ confirmation.
402
+ - Do **not** mix `--new` / `--resume` / `--abandon` with the Model A
403
+ flow. The legacy modes target the M-892 zero-touch path; Model A's
404
+ primary flow (`launchpad init` + `launchpad deploy` from CWD) is
405
+ what new users want.
406
+ - Do **not** add a cron trigger to a Pages `react+api` app directly —
407
+ Pages has no `scheduled` handler. Scheduled work needs a sibling cron
408
+ **Worker** declared in `targets[]` (see "Two-tier apps").
409
+ - Do **not** set verifier env (`CF_ACCESS_TEAM_DOMAIN`, `CF_ACCESS_AUD`,
410
+ `JWKS_URL`) as `plain_text` — it is wiped on the next deploy. The per-app
411
+ TF emits them as `secret_text`; trust it.
412
+ - Do **not** re-enable `confine_origin` on a gateway `react+api` app — it
413
+ strips the user JWT and 401s every user (see "Gateway auth").
414
+ - Do **not** `wrangler secret put` for a two-tier app — `launchpad secrets
415
+ push` with `targets: all` seeds both tiers via the bot.
@@ -0,0 +1,231 @@
1
+ ---
2
+ name: launchpad-deploy-status
3
+ description: Show the current provisioning stage + failure reason for a Launchpad app via `launchpad status` (Model A drift + deployment_verified) and `launchpad apps` (lifecycle bucket). Renders the M-892 stage trace for legacy in-flight provisioning. Use when someone says "what's the status of demo-X", "/launchpad-deploy-status", "is my deploy stuck", or after `/launchpad-deploy` reports a non-`done` terminal stage.
4
+ version: 0.23.0
5
+ ---
6
+
7
+ <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
8
+ ## Shell contract — read this first
9
+
10
+ Every fenced `bash` block below MUST be sent to the `Bash` tool **verbatim**.
11
+ Do not rewrite into PowerShell, cmd, zsh-isms, or "equivalent" forms.
12
+
13
+ - macOS / Linux: the `Bash` tool runs system bash.
14
+ - Windows: the `Bash` tool runs Git for Windows (MSYS) bash. `$HOME`,
15
+ forward slashes, `test -f`, `command -v`, heredocs, and `[[ … ]]` all
16
+ work. There is no reason to translate to `Test-Path`, `$env:USERPROFILE`,
17
+ `Get-Content`, `Where-Object`, `Get-ChildItem`, or backslash paths —
18
+ doing so will fail with `/usr/bin/bash: syntax error`.
19
+
20
+ If a step genuinely needs OS branching, branch *inside* bash:
21
+
22
+ ```bash
23
+ case "$(uname -s)" in
24
+ Darwin) : ;;
25
+ Linux) : ;;
26
+ MINGW*|MSYS*|CYGWIN*) : ;;
27
+ esac
28
+ ```
29
+ <!-- END shell-contract -->
30
+
31
+ # /launchpad-deploy-status
32
+
33
+ Read-only status check for a Launchpad app. Routes the user to the
34
+ right CLI verb depending on whether the app is being deployed under
35
+ Model A (the normal path) or stuck in a legacy M-892 provisioning
36
+ flow.
37
+
38
+ ## Pre-flight
39
+
40
+ ```bash
41
+ launchpad whoami
42
+ ```
43
+
44
+ If that fails, run `/launchpad-onboard` first.
45
+
46
+ ## Input
47
+
48
+ Accept a **slug** (e.g. `demo-9`) as a positional argument. If the
49
+ user invokes the skill without one, ask for it. If the user is
50
+ sitting inside a `launchpad-app-<slug>/` clone, the slug can be
51
+ inferred from cwd by every verb below.
52
+
53
+ ## Quick triage — which verb do you want?
54
+
55
+ | Question | Verb |
56
+ |---|---|
57
+ | Is my local manifest in sync with what's deployed? | `launchpad status <slug>` |
58
+ | What lifecycle bucket is the app in? | `launchpad apps` |
59
+ | What was actually deployed? | `launchpad pull <slug>` |
60
+ | Is the app stuck in the legacy M-892 zero-touch flow? | See § Legacy below |
61
+ | What broke the most recent deploy? | `launchpad status <slug> --json` + the bot's PR check trail |
62
+
63
+ The Model A default is `launchpad status <slug>`. The other verbs
64
+ are specialisations.
65
+
66
+ ## Standard (Model A) status
67
+
68
+ ```bash
69
+ launchpad status <slug>
70
+ ```
71
+
72
+ Output is one of three states (see `/launchpad-status` for the
73
+ canonical reference):
74
+
75
+ - **`in sync`** — local `./launchpad.yaml` matches what's deployed.
76
+ Nothing pending. App is live and the most-recent deploy verified.
77
+ - **`drift: <field list>`** — local and deployed differ on at least
78
+ one v1 closed-set field (`metadata.name`, `metadata.team`,
79
+ `metadata.owner`, `metadata.description`, `deployment.type`,
80
+ `access.allowed_entra_group`, `hostnames[0]`, `build.command`,
81
+ `build.destination_dir`, `build.root_dir`, `production_env.*`).
82
+ Run `launchpad deploy` to roll the local manifest out.
83
+ - **`no deployed manifest yet`** — the bot reports no
84
+ `output "<slug>_manifest_sha"`. Either the first deploy is still
85
+ in flight, or it failed. Check `launchpad apps` for the lifecycle
86
+ bucket.
87
+
88
+ Add `--json` for structured output:
89
+
90
+ ```bash
91
+ launchpad status <slug> --json
92
+ ```
93
+
94
+ The JSON envelope includes `deployedSha`, `headSha`, `hasOpenPr`,
95
+ `openPrNumber`, `driftFields`, and per-field `driftDetails`. This
96
+ is the shape downstream tooling should parse — never grep the prose
97
+ output.
98
+
99
+ ## Lifecycle bucket
100
+
101
+ ```bash
102
+ launchpad apps
103
+ ```
104
+
105
+ Renders every app the caller can see, sorted by the bot:
106
+ `provisioning` → `failed` → `live`, newest `lifecycleAt` first
107
+ within each bucket. The `Lifecycle` column is the load-bearing
108
+ field. Common values:
109
+
110
+ - **`live`** — app is operational. Use `launchpad status <slug>` for
111
+ drift + the `deployment_verified` outcome of the most recent
112
+ deploy.
113
+ - **`provisioning`** — first-deploy is still in flight. Wait, then
114
+ re-check. If it stays in `provisioning` for more than ~20 minutes,
115
+ the apply may have failed silently — surface to platform-team.
116
+ - **`failed`** — provisioning failed. See `launchpad status <slug>
117
+ --json` for the most recent error, and the bot's open PR on
118
+ `launchpad-platform` for the apply trace.
119
+ - **`destroying` / `destroyed` / `destroy_failed`** — teardown
120
+ states. Route to `/launchpad-destroy`.
121
+
122
+ Restrict to a single slug with `grep` (or `--json` parsing) if the
123
+ list is long. `launchpad apps` is read-only and cheap.
124
+
125
+ ## Render
126
+
127
+ Pretty-print the result as a single block:
128
+
129
+ ```
130
+ App: demo-9
131
+ Lifecycle: live
132
+ State: in sync
133
+ Live at: https://demo-9.launchpad.m-kopa.us
134
+ ```
135
+
136
+ Drift case:
137
+
138
+ ```
139
+ App: demo-9
140
+ Lifecycle: live
141
+ State: drift (metadata.owner, production_env.API_BASE)
142
+
143
+ Next steps:
144
+ Edit ./launchpad.yaml to resolve the differences, then run
145
+ `launchpad deploy` to roll it out.
146
+
147
+ Or run `launchpad pull demo-9 --out deployed.yaml` to bring your
148
+ local manifest into line with what's deployed.
149
+ ```
150
+
151
+ In-flight first deploy:
152
+
153
+ ```
154
+ App: demo-9
155
+ Lifecycle: provisioning
156
+ State: no deployed manifest yet
157
+
158
+ Next steps:
159
+ Wait for the first deploy to complete. Re-run this skill in a few
160
+ minutes, or watch `launchpad apps` for the lifecycle to flip to
161
+ `live`.
162
+ ```
163
+
164
+ Failed deploy:
165
+
166
+ ```
167
+ App: demo-7
168
+ Lifecycle: failed
169
+ State: no deployed manifest yet — bundle_rejected (3 oversized files)
170
+
171
+ Next steps:
172
+ /launchpad-deploy "Recover a legacy in-flight deploy" → resume,
173
+ OR fix the bundle (per the reasons above) and run `launchpad deploy`
174
+ again. The bot is idempotent on retries against a failed slug.
175
+ ```
176
+
177
+ ## Legacy — M-892 stage taxonomy
178
+
179
+ For apps that are stuck in the pre-Model-A zero-touch provisioning
180
+ flow (`launchpad deploy --new` / `--resume` / `--abandon`), the bot
181
+ still emits the original stage trace. The taxonomy is:
182
+
183
+ ```
184
+ pending → repo_created → bootstrap_pr_opened → bootstrap_pr_merged →
185
+ tf_pr_opened → tf_pr_merged → tf_applied → cert_active →
186
+ policy_attached → ready_for_content → deployment_verified → done
187
+ ```
188
+
189
+ Terminal failure stages: `failed`, `bot_pr_ci_failed`, `abandoned`,
190
+ `cf-pages-poll-unrecoverable`.
191
+
192
+ The CLI surfaces this through `launchpad apps` (lifecycle bucket)
193
+ and `launchpad status <slug> --json` (the most recent transition).
194
+ You no longer parse the bot's `/apps/<sub>/status` shape by hand
195
+ from the playbook — every status surface goes through the CLI.
196
+
197
+ If a legacy app is genuinely stuck mid-pipeline (e.g. a bot PR
198
+ failed CI), the recovery paths are:
199
+
200
+ - **Resume:** `launchpad deploy --resume <slug>` — re-fires the bot
201
+ endpoint and walks the stages again from where they failed.
202
+ - **Abandon:** `launchpad deploy --abandon <slug>` — frees the slug
203
+ but does NOT clean up Cloudflare resources or the GitHub repo.
204
+ Follow up with `launchpad destroy <slug>` for a full teardown.
205
+
206
+ Both flows live in `/launchpad-deploy` § Legacy.
207
+
208
+ ## Linking open PRs
209
+
210
+ If the user wants to see open bot PRs for the app, route through
211
+ `launchpad status <slug> --json` — the `hasOpenPr` / `openPrNumber`
212
+ pair points at the platform-repo TF PR for in-flight changes. For
213
+ the app repo, `launchpad apps` includes the deploy PR URL on the
214
+ most-recent transition row when relevant. The playbook does not
215
+ shell out to `gh pr list` — the bot owns GH credentials, not the
216
+ CLI, and external users without M-KOPA GH access will not be able
217
+ to follow such a link anyway.
218
+
219
+ ## Don'ts
220
+
221
+ - Do **not** mutate anything from this skill. Status is read-only.
222
+ - Do **not** shell out to `gh`, `jq`, `curl`, or `git`. The CLI
223
+ verbs cover every surface this skill needs.
224
+ - Do **not** invent stage names or skip stages from the legacy
225
+ taxonomy block — consistency with `/launchpad-deploy` is what
226
+ makes the renames-ripple property work.
227
+ - Do **not** retry indefinitely on transient 5xx. The CLI verbs do
228
+ their own bounded retries; one playbook call, one error message,
229
+ suggest the user re-run.
230
+ - Do **not** parse the prose output of `launchpad status` /
231
+ `launchpad apps`. Use `--json` for any downstream automation.