@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,317 @@
1
+ ---
2
+ name: launchpad-destroy
3
+ description: Tear down a Launchpad app end-to-end via `launchpad destroy` — Cloudflare Pages project, Access app, custom hostname, platform-repo TF entries, and the app repo (archive-renamed). Owner-only verb with a two-step destructive confirmation. Use when someone says "destroy this app", "/launchpad-destroy", "tear down `<slug>`", "delete the app", or asks to clean up a smoke-test / orphan / retired app.
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-destroy
32
+
33
+ Destructive, owner-only verb. Tears down a Launchpad app end-to-end:
34
+ the Cloudflare Pages project, Access app, custom hostname,
35
+ launchpad-platform TF entries, and the app repo (archive-renamed,
36
+ not deleted). Zero local platform-repo / terraform / Cloudflare /
37
+ GitHub credentials needed — the bot owns all of it. CLI is a thin
38
+ client.
39
+
40
+ **Read this before running.** A clean destroy is irreversible after
41
+ the ~30-day archive-recovery window (see "Recovery"). Smoke tests and
42
+ orphan cleanup are the intended use case; if you are touching a live
43
+ production app, get a second pair of eyes first.
44
+
45
+ ## Pre-flight
46
+
47
+ You need a current Cf Access session and OWNER role on the app.
48
+ Editors cannot destroy.
49
+
50
+ ```bash
51
+ launchpad whoami
52
+ # If this fails: launchpad login
53
+ launchpad apps | grep <slug>
54
+ # Confirm you are the owner (not an editor).
55
+ ```
56
+
57
+ If `launchpad apps` doesn't show the slug, it doesn't exist — destroy
58
+ exits 1 with `no app with slug <slug>`.
59
+
60
+ ## When NOT to use this skill
61
+
62
+ - **Modifying an app's manifest.** Use `launchpad deploy` after
63
+ editing `launchpad.yaml`. Destroy doesn't reset config — it
64
+ removes the app entirely.
65
+ - **Re-creating an app under the same name.** Run `launchpad destroy
66
+ <slug>`, wait for the lifecycle to reach `destroyed`, then
67
+ `launchpad create`. The slug is freed by the archive-rename step
68
+ in AC8.
69
+ - **Cleaning up the app's GitHub history.** The destroy verb
70
+ archive-renames the repo (preserving history); it does not
71
+ hard-delete. Use `gh repo delete` if you genuinely need the repo
72
+ gone forever (irreversible after 90 days of GitHub's retention).
73
+
74
+ ## Two-step destructive confirmation
75
+
76
+ ```bash
77
+ # Interactive (TTY): prompts for slug re-type + Y/N
78
+ launchpad destroy <slug>
79
+
80
+ # CI / non-TTY: BOTH flags are MANDATORY
81
+ launchpad destroy <slug> --confirm-slug <slug> --yes
82
+
83
+ # In a launchpad-app-<slug>/ clone — slug inferred from cwd
84
+ cd launchpad-app-<slug>
85
+ launchpad destroy --confirm-slug <slug> --yes
86
+ ```
87
+
88
+ **Non-TTY behaviour:** missing either `--confirm-slug` or `--yes` in a
89
+ non-TTY context exits 64 (`EX_USAGE`) with `"--confirm-slug and --yes
90
+ required in non-interactive mode"`. CI invocations always set both.
91
+
92
+ **TTY behaviour:** the verb prints a banner naming what's about to be
93
+ destroyed, then prompts twice: (a) `Type the slug to confirm:` —
94
+ operator must re-type the slug exactly (case-sensitive), and (b)
95
+ `Are you sure? [y/N]`. Anything other than `y`/`yes` aborts (exit 1).
96
+ Either flag can short-circuit its respective prompt.
97
+
98
+ ## What the verb does (server-side flow)
99
+
100
+ 1. **Server-side slug re-validation** (`checkSlug()` + `SLUG_REGEX`).
101
+ 2. **Owner-only authz** — `owner | break_glass` only; editor → 403.
102
+ 3. **Lifecycle precondition check** — see "Lifecycle states" below.
103
+ 4. **main.tf carve-out check** — pre-M-1182 apps that live in
104
+ `main.tf` cannot be destroyed by this verb (see "Carve-outs").
105
+ 5. **Per-app TF file pair existence probe** on launchpad-platform main.
106
+ 6. **Lifecycle → `destroying`**.
107
+ 7. **Enumerate open PRs on `launchpad-app-<slug>`.** Bot-authored PRs
108
+ are closed with a comment; human-authored PRs get a heads-up
109
+ comment (NOT closed — that's the human author's decision).
110
+ 8. **Atomic destroy PR** opens on `launchpad-platform`, deleting
111
+ `terraform/envs/prod/<slug>.tf` + `<slug>.allow.tf` in one commit.
112
+ 9. **`destroy:confirmed` label gate** must be applied by a non-author
113
+ for auto-merge.
114
+ 10. **Existing `tf-apply` workflow** destroys the Cf resources on
115
+ merge. There is NO new `tf-destroy` workflow — the standard
116
+ apply with the removed module is the teardown.
117
+ 11. **Archive-rename happens AFTER tf-apply terminal-success** — never
118
+ before (AC8). If tf-apply fails, the repo is untouched and
119
+ lifecycle moves to `destroy_failed` (recoverable).
120
+
121
+ ## Lifecycle states
122
+
123
+ | State | Meaning | What you can do |
124
+ |---|---|---|
125
+ | `live` (or absent) | App is operational. | Destroy works. |
126
+ | `provisioning` | Initial create still running. | Wait, then destroy. |
127
+ | `failed` | Provisioning failed. | Destroy works (cleans up partial state). |
128
+ | `destroying` | Destroy PR is open, tf-apply pending. | Re-running `launchpad destroy` is idempotent (returns the existing PR). |
129
+ | `destroyed` | tf-apply succeeded + repo archive-renamed. | Re-running exits 0 with `already destroyed at <ts>`. Slug is free for re-use. |
130
+ | `destroy_failed` | tf-apply on destroy PR failed or hung. | Re-running re-checks state; if not retriable, surfaces `platform-team intervention required`. App repo is **not** archive-renamed (recovery path stays clean). |
131
+
132
+ ## Carve-outs
133
+
134
+ Legacy apps whose Terraform lives inside `terraform/envs/prod/main.tf`
135
+ (rather than as a per-app `<slug>.tf` + `<slug>.allow.tf` pair) cannot
136
+ be destroyed by this verb. The carve-out list is canonical in
137
+ `portal-bot/src/reserved.ts` and currently covers `marquee`, `portal`,
138
+ `canary`, `ai-cost-002`, `ai-cost-container`.
139
+
140
+ Attempting destroy on a carve-out exits 1 with:
141
+
142
+ ```text
143
+ launchpad destroy: <slug> is pre-M-1182 main.tf-resident; destroy is not supported. Contact platform-team.
144
+ ```
145
+
146
+ If you genuinely need to tear down a main.tf-resident app, ping
147
+ platform-team for a manual TF + repo cleanup.
148
+
149
+ ## Open PR handling
150
+
151
+ When the destroy starts, the bot enumerates every open PR on
152
+ `launchpad-app-<slug>` and splits them:
153
+
154
+ - **Bot-authored PRs** (e.g. auto-deploy PRs the bot opened earlier):
155
+ closed with comment `"closed by launchpad destroy; the app is being
156
+ torn down"`.
157
+ - **Human-authored PRs**: NOT closed. The bot comments on each with a
158
+ heads-up:
159
+
160
+ > NOTE: this app is being destroyed via launchpad destroy. This
161
+ > PR will become unmergeable when the platform-repo TF removal
162
+ > lands.
163
+ >
164
+ > Destroy PR: <url>
165
+
166
+ The human author decides what to do (close, salvage, etc.).
167
+
168
+ In TTY mode the verb prints the human-PR list at confirmation time so
169
+ you can pause if you didn't realise there were in-flight PRs.
170
+
171
+ ## Recovery — the archive-rename window
172
+
173
+ After a clean destroy (`lifecycle: destroyed`), the app repo lives at
174
+ `launchpad-app-<slug>-archived-<YYYYMMDD>` in the M-KOPA org, with
175
+ GitHub's `archived: true` flag set. **There is no `launchpad
176
+ undestroy` verb.** Recovery is operator self-service via `gh`:
177
+
178
+ ```bash
179
+ # Rename back + unarchive (within ~30 days; check org policy).
180
+ # `gh repo rename` takes a single positional <new-name>; the OLD
181
+ # repo is specified via --repo (or -R).
182
+ gh repo rename launchpad-app-<slug> \
183
+ --repo M-KOPA/launchpad-app-<slug>-archived-<YYYYMMDD>
184
+ gh repo unarchive M-KOPA/launchpad-app-<slug>
185
+ ```
186
+
187
+ This restores the **repo**, not the Cloudflare infrastructure — you
188
+ would still need a fresh `launchpad create <slug>` (or a hand-rolled
189
+ platform-repo PR re-adding the per-app TF pair) to bring the app
190
+ back online.
191
+
192
+ **The 30-day window is informational, not enforced by Launchpad** —
193
+ the actual horizon is whatever GitHub's org policy is. If you
194
+ suspect a destroy was a mistake, recover within hours, not weeks.
195
+
196
+ ## Exit codes
197
+
198
+ - **0** — destroy initiated successfully, OR idempotent re-run on a
199
+ `destroyed` slug (no-op).
200
+ - **1** — runtime error (auth, network, 403/404/409 from the bot).
201
+ - **64** — usage error (invalid args, missing required flags in
202
+ non-TTY mode, slug mismatch on `--confirm-slug`).
203
+
204
+ ## Common error patterns + fixes
205
+
206
+ ### `launchpad destroy: session expired, run \`launchpad login\``
207
+
208
+ Standard auth refresh. `launchpad login` once; the destroy command
209
+ works again for the duration of the Cf Access session (~24h).
210
+
211
+ ### `not authorised to destroy app X (you must be an owner — editor role is not sufficient)`
212
+
213
+ You're an editor, not the owner. Either:
214
+
215
+ - Ask the current owner to transfer ownership via the portal admin UI:
216
+ `https://launchpad.m-kopa.us/admin/apps/<slug>` (then transfer back
217
+ after destroy, if you want).
218
+ - Get break-glass access from platform-team for this specific
219
+ destroy (audited; one-shot).
220
+
221
+ ### `<slug> is pre-M-1182 main.tf-resident`
222
+
223
+ See "Carve-outs". Platform-team manual cleanup is required.
224
+
225
+ ### `no per-app TF for <slug> in launchpad-platform/main`
226
+
227
+ The app exists in the registry but has no per-app TF pair on
228
+ platform main. This usually means: (a) the app was never fully
229
+ provisioned (look at `lifecycle` — likely `failed`), or (b) the
230
+ file pair was hand-removed outside of this verb. Either way:
231
+ platform-team intervention.
232
+
233
+ ### `slug ${urlSlug} is already being destroyed`
234
+
235
+ (No — this exits 200 idempotently now, not 409. If you see this
236
+ message it's from an old bot. Update the bot.)
237
+
238
+ ### `slug <slug> is in destroy_failed state; platform-team intervention required`
239
+
240
+ tf-apply on the destroy PR failed (commonly: a stuck Cf
241
+ custom-hostname deletion). The app repo is NOT archive-renamed in
242
+ this state — the recovery path is unblocked. Options:
243
+
244
+ 1. Re-run `launchpad destroy <slug>` to retry once the underlying
245
+ issue is fixed (e.g. tf-apply hang resolved).
246
+ 2. Contact platform-team for manual TF state surgery if re-running
247
+ doesn't move past the failure.
248
+
249
+ ## --json output
250
+
251
+ For downstream scripts (CI cleanup jobs, automation):
252
+
253
+ ```bash
254
+ launchpad destroy <slug> --confirm-slug <slug> --yes --json
255
+ ```
256
+
257
+ ```json
258
+ {
259
+ "slug": "<slug>",
260
+ "lifecycle": "destroying",
261
+ "destroyPr": {
262
+ "number": 4242,
263
+ "url": "https://github.com/M-KOPA/launchpad-platform/pull/4242",
264
+ "branch": "bot/destroy/<slug>/<YYYYMMDD>",
265
+ "idempotent": false
266
+ },
267
+ "openBotPrs": [],
268
+ "openHumanPrs": [
269
+ { "number": 22, "title": "...", "htmlUrl": "...", "authorLogin": "alice" }
270
+ ]
271
+ }
272
+ ```
273
+
274
+ On an idempotent re-run against a `destroyed` slug:
275
+
276
+ ```json
277
+ {
278
+ "slug": "<slug>",
279
+ "lifecycle": "destroyed",
280
+ "alreadyDestroyed": true,
281
+ "lifecycleAt": "2026-05-26T08:00:00Z"
282
+ }
283
+ ```
284
+
285
+ ## Related skills
286
+
287
+ - **`/launchpad-status`** — read the deployed manifest, compare to
288
+ local. Pair this with `launchpad pull <slug> --out backup.yaml`
289
+ before destroy if you want to keep the manifest for reference.
290
+ - **`/launchpad-deploy`** — provision a new app (e.g. after a destroy
291
+ that frees the slug). Note the slug-lock during the destroy window:
292
+ `launchpad create <slug>` rejects if the slug is `destroying` or
293
+ `destroy_failed`.
294
+ - **`/launchpad-deploy-status`** — diagnose `provisioning` /
295
+ `failed` lifecycle stages. Less relevant after destroy lands.
296
+
297
+ ## Anti-patterns
298
+
299
+ - **Don't** run `gh repo delete` to clean up the app repo before
300
+ running `launchpad destroy` — the destroy needs to enumerate open
301
+ PRs on the repo and the platform PR opens fine without it, but
302
+ you'll lose the audit trail in the app repo's history.
303
+ - **Don't** hand-edit `terraform/envs/prod/<slug>.tf` to "blank out"
304
+ the app — the bot still considers it live and `launchpad destroy`
305
+ is the only path that handles every dependent (Access app,
306
+ hostname, registry record, app repo). Manual TF edits leave
307
+ orphan Cloudflare state.
308
+ - **Don't** parse the prose output of `launchpad destroy` in CI
309
+ scripts. Use `--json`.
310
+ - **Don't** rely on a destroy PR auto-merging without the
311
+ `destroy:confirmed` label — a non-author must apply it. The label
312
+ is the audit gate; treating it as ceremony defeats AC6's two-pair-of-
313
+ eyes property.
314
+
315
+ ## Version
316
+
317
+ This skill ships in launchpad-cli v0.14.0+ (M-1187 release).
@@ -0,0 +1,179 @@
1
+ ---
2
+ name: launchpad-onboard
3
+ description: One-time setup for the Launchpad CLI + Claude Code skill bundle. Verifies the `launchpad` CLI is installed and current, runs `launchpad whoami` to confirm the Cf Access session is fresh, and checks the bundled skills are installed and in lock-step with the CLI. Idempotent — safe to re-run any time. Use when someone says "set me up for Launchpad", "I just got a new machine and want to use Launchpad", "/launchpad-onboard", or any of the other launchpad-* skills fails on a prereq check.
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-onboard
32
+
33
+ Run a prereq + auth health check for the Launchpad CLI and the Claude
34
+ Code skill bundle. Surface a single pass/fail summary at the end with
35
+ a copy-pasteable fix for any red row.
36
+
37
+ ## What this skill is responsible for
38
+
39
+ This is the **only** skill that mutates the user's machine (it can
40
+ trigger `launchpad login` and suggest installs). The other skills
41
+ (`/launchpad-deploy`, `/launchpad-deploy-status`,
42
+ `/launchpad-content-pr`, `/launchpad-status`, `/launchpad-destroy`)
43
+ assume the machine is already onboarded and fail loudly if a prereq is
44
+ missing — pointing back here.
45
+
46
+ ## Zero non-Launchpad dependencies
47
+
48
+ Under Model A (M-1216), the CLI is fully self-contained: it talks
49
+ straight to the bot via Cf Access OAuth and never shells out to `gh`,
50
+ `jq`, `curl`, `git`, or any other system tool. **External users with
51
+ no M-KOPA GitHub access are first-class.** This skill therefore
52
+ checks only two things: the `launchpad` binary is on PATH at the
53
+ expected version, and the bundled skills are installed and synced.
54
+
55
+ There is no `gh auth` check, no `jq` probe, no `curl` smoke-test.
56
+ A regression that re-introduces such a dependency surfaces in the
57
+ `launchpad-cli-e2e` zero-deps CI matrix, not here.
58
+
59
+ ## Run sequence
60
+
61
+ Execute the checks below in order. Collect a `{ ok | fix }` row per
62
+ check and print them all at the end as a table. Do NOT stop early on
63
+ the first red row — finish the full sweep so the user sees every
64
+ issue in one pass.
65
+
66
+ ### 1. `launchpad` CLI installed
67
+
68
+ ```bash
69
+ launchpad --version
70
+ ```
71
+
72
+ - **Missing** (`command not found`) — install it:
73
+ - **Public npm (primary, all platforms):**
74
+ `npm i -g @m-kopa/launchpad-cli` — no `~/.npmrc` setup, no auth
75
+ token, no antivirus block. Works on macOS, Linux, and Windows.
76
+ - **Platform channel (legacy / air-gapped):** download and run the
77
+ installer from https://get.launchpad.m-kopa.us. On Windows the
78
+ self-contained `.ps1` may trip Defender — prefer the npm install
79
+ above.
80
+ - **Below the version bundled with this skill** (see step 4 below):
81
+ `npm update -g @m-kopa/launchpad-cli` (or re-run the platform
82
+ installer). Then `launchpad skills update` to re-sync the skill bundle.
83
+
84
+ ### 2. Session is present and fresh
85
+
86
+ `launchpad whoami` is the canonical health check. It reads the
87
+ on-disk session, decodes the JWT (without validating its signature —
88
+ that's the bot's job), and prints identity + expiry + role per app.
89
+ A session that's expired or missing surfaces as a clear "session
90
+ expired, run `launchpad login`" error from the verb itself; no manual
91
+ file inspection required.
92
+
93
+ ```bash
94
+ launchpad whoami
95
+ ```
96
+
97
+ - **Exit 0** → session is valid; identity and expiry are printed.
98
+ - **"No session — run `launchpad login`"** → no session file on
99
+ disk. Run `launchpad login` (opens a browser; sign in via
100
+ Cloudflare Access SSO).
101
+ - **"session expired, run `launchpad login`"** → refresh tokens
102
+ rotate; re-authenticate.
103
+ - **Any other error** → surface the message verbatim. Do not fall
104
+ back to inspecting `~/.launchpad/session.json` by hand — the file
105
+ is internal to the CLI and its shape is not a stable contract.
106
+
107
+ ### 3. Skill bundle installed at `$HOME/.claude/skills/launchpad-*/`
108
+
109
+ ```bash
110
+ launchpad skills list
111
+ ```
112
+
113
+ Expected: the launchpad-* skills are all present with a `version:`
114
+ matching the CLI.
115
+
116
+ - **Any skill missing** (or `$HOME/.claude/skills/` doesn't exist):
117
+ ```bash
118
+ launchpad skills install
119
+ ```
120
+
121
+ ### 4. Skill bundle version matches the installed CLI
122
+
123
+ `launchpad skills list` annotates each skill with its `version:`
124
+ frontmatter. The CLI release pipeline stamps both the CLI and the
125
+ bundled skills from `package.json` (see
126
+ `scripts/sync-skill-contract.sh`); CI rejects any PR that lets them
127
+ drift. So a mismatch here means the user has an old skill bundle on
128
+ disk, not a release-engineering bug.
129
+
130
+ If `launchpad skills list` reports any skill at a different version
131
+ than `launchpad --version`:
132
+
133
+ ```bash
134
+ launchpad skills update
135
+ ```
136
+
137
+ This re-copies the bundled skills out of the installed CLI package
138
+ and re-syncs the version on disk.
139
+
140
+ ## Summary table
141
+
142
+ At the end, print a single table like:
143
+
144
+ ```
145
+ Check Status Fix
146
+ ─────────────────────────────────────────────────────────────────────
147
+ launchpad CLI installed ✓ —
148
+ launchpad whoami succeeds (session) ✗ launchpad login
149
+ Skill bundle installed ✓ —
150
+ Skill bundle ↔ CLI version match ✓ —
151
+ ```
152
+
153
+ If every row is `✓`, finish with a single line:
154
+
155
+ ```
156
+ ✓ All checks passed — you can run /launchpad-deploy.
157
+ ```
158
+
159
+ Otherwise finish with:
160
+
161
+ ```
162
+ ✗ N check(s) failed — apply the fixes above and re-run /launchpad-onboard.
163
+ ```
164
+
165
+ ## Don'ts
166
+
167
+ - Do **not** run `launchpad login` without telling the user first — it
168
+ opens a browser. State that it's about to happen and what they need
169
+ to do (sign in via Cloudflare Access SSO).
170
+ - Do **not** run destructive ops (e.g. `npm uninstall`, `rm`) on the
171
+ user's machine even if a check fails — only suggest the fix; the
172
+ user runs it themselves.
173
+ - Do **not** probe for `gh`, `jq`, `curl`, or `git` and treat their
174
+ absence as a failure. The CLI does not depend on any of them under
175
+ Model A — flagging them red is a misleading prereq from the
176
+ pre-M-1216 era.
177
+ - Do **not** read `~/.launchpad/session.json` directly. Use
178
+ `launchpad whoami`; the file is an internal artefact.
179
+ - Do **not** retry indefinitely. One pass, one summary, done.