@m-kopa/launchpad-cli 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +854 -0
- package/README.md +109 -0
- package/dist/auth/browser.d.ts +18 -0
- package/dist/auth/browser.d.ts.map +1 -0
- package/dist/auth/callback-server.d.ts +24 -0
- package/dist/auth/callback-server.d.ts.map +1 -0
- package/dist/auth/discovery.d.ts +25 -0
- package/dist/auth/discovery.d.ts.map +1 -0
- package/dist/auth/flow.d.ts +39 -0
- package/dist/auth/flow.d.ts.map +1 -0
- package/dist/auth/jwt.d.ts +27 -0
- package/dist/auth/jwt.d.ts.map +1 -0
- package/dist/auth/pkce.d.ts +26 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/registration.d.ts +8 -0
- package/dist/auth/registration.d.ts.map +1 -0
- package/dist/auth/session.d.ts +54 -0
- package/dist/auth/session.d.ts.map +1 -0
- package/dist/auth/token.d.ts +37 -0
- package/dist/auth/token.d.ts.map +1 -0
- package/dist/bundle/cron-bundle.d.ts +77 -0
- package/dist/bundle/cron-bundle.d.ts.map +1 -0
- package/dist/bundle/cwd-walker.d.ts +43 -0
- package/dist/bundle/cwd-walker.d.ts.map +1 -0
- package/dist/bundle/orchestrate.d.ts +51 -0
- package/dist/bundle/orchestrate.d.ts.map +1 -0
- package/dist/bundle/upload.d.ts +66 -0
- package/dist/bundle/upload.d.ts.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +9757 -0
- package/dist/clone/git-init.d.ts +18 -0
- package/dist/clone/git-init.d.ts.map +1 -0
- package/dist/clone/tar-extract.d.ts +59 -0
- package/dist/clone/tar-extract.d.ts.map +1 -0
- package/dist/commands/apps.d.ts +14 -0
- package/dist/commands/apps.d.ts.map +1 -0
- package/dist/commands/channel-auth.d.ts +31 -0
- package/dist/commands/channel-auth.d.ts.map +1 -0
- package/dist/commands/clone.d.ts +3 -0
- package/dist/commands/clone.d.ts.map +1 -0
- package/dist/commands/create.d.ts +27 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/deploy-flags.d.ts +75 -0
- package/dist/commands/deploy-flags.d.ts.map +1 -0
- package/dist/commands/deploy-modes.d.ts +59 -0
- package/dist/commands/deploy-modes.d.ts.map +1 -0
- package/dist/commands/deploy.d.ts +29 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/destroy.d.ts +14 -0
- package/dist/commands/destroy.d.ts.map +1 -0
- package/dist/commands/envvars.d.ts +28 -0
- package/dist/commands/envvars.d.ts.map +1 -0
- package/dist/commands/generate.d.ts +3 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/groups-whoami.d.ts +3 -0
- package/dist/commands/groups-whoami.d.ts.map +1 -0
- package/dist/commands/groups.d.ts +3 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/init.d.ts +44 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logs.d.ts +16 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/merge.d.ts +29 -0
- package/dist/commands/merge.d.ts.map +1 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.d.ts.map +1 -0
- package/dist/commands/pull.d.ts +12 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/review.d.ts +22 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/rollback.d.ts +3 -0
- package/dist/commands/rollback.d.ts.map +1 -0
- package/dist/commands/secrets-template.d.ts +3 -0
- package/dist/commands/secrets-template.d.ts.map +1 -0
- package/dist/commands/secrets.d.ts +3 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/skills.d.ts +13 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/status.d.ts +54 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/update.d.ts +114 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/validate.d.ts +3 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/deploy/apply.d.ts +29 -0
- package/dist/deploy/apply.d.ts.map +1 -0
- package/dist/deploy/dry-run.d.ts +13 -0
- package/dist/deploy/dry-run.d.ts.map +1 -0
- package/dist/deploy/git-files.d.ts +33 -0
- package/dist/deploy/git-files.d.ts.map +1 -0
- package/dist/deploy/group-pin.d.ts +66 -0
- package/dist/deploy/group-pin.d.ts.map +1 -0
- package/dist/deploy/manifest-state.d.ts +20 -0
- package/dist/deploy/manifest-state.d.ts.map +1 -0
- package/dist/deploy/manifest-status.d.ts +11 -0
- package/dist/deploy/manifest-status.d.ts.map +1 -0
- package/dist/deploy/resolve.d.ts +53 -0
- package/dist/deploy/resolve.d.ts.map +1 -0
- package/dist/deploy/rollback.d.ts +23 -0
- package/dist/deploy/rollback.d.ts.map +1 -0
- package/dist/deploy/runner.d.ts +29 -0
- package/dist/deploy/runner.d.ts.map +1 -0
- package/dist/deploy/stage-exit-codes.d.ts +41 -0
- package/dist/deploy/stage-exit-codes.d.ts.map +1 -0
- package/dist/deploy/status-polling.d.ts +37 -0
- package/dist/deploy/status-polling.d.ts.map +1 -0
- package/dist/deploy/tar-pack.d.ts +22 -0
- package/dist/deploy/tar-pack.d.ts.map +1 -0
- package/dist/detect/index.d.ts +53 -0
- package/dist/detect/index.d.ts.map +1 -0
- package/dist/dispatcher.d.ts +30 -0
- package/dist/dispatcher.d.ts.map +1 -0
- package/dist/groups/client.d.ts +62 -0
- package/dist/groups/client.d.ts.map +1 -0
- package/dist/http/api-client.d.ts +33 -0
- package/dist/http/api-client.d.ts.map +1 -0
- package/dist/http/errors.d.ts +31 -0
- package/dist/http/errors.d.ts.map +1 -0
- package/dist/manifest/load.d.ts +38 -0
- package/dist/manifest/load.d.ts.map +1 -0
- package/dist/manifest/schema.d.ts +3 -0
- package/dist/manifest/schema.d.ts.map +1 -0
- package/dist/postinstall.d.ts +3 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +37 -0
- package/dist/secrets/env-parse.d.ts +19 -0
- package/dist/secrets/env-parse.d.ts.map +1 -0
- package/dist/secrets/push.d.ts +13 -0
- package/dist/secrets/push.d.ts.map +1 -0
- package/dist/secrets/set.d.ts +19 -0
- package/dist/secrets/set.d.ts.map +1 -0
- package/dist/secrets/status.d.ts +19 -0
- package/dist/secrets/status.d.ts.map +1 -0
- package/dist/types/api.d.ts +112 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/update-notifier.d.ts +69 -0
- package/dist/update-notifier.d.ts.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +62 -0
- package/skills/README.md +100 -0
- package/skills/_partials/shell-contract.md +42 -0
- package/skills/launchpad-content-pr/SKILL.md +255 -0
- package/skills/launchpad-deploy/SKILL.md +415 -0
- package/skills/launchpad-deploy-status/SKILL.md +231 -0
- package/skills/launchpad-destroy/SKILL.md +317 -0
- package/skills/launchpad-onboard/SKILL.md +179 -0
- package/skills/launchpad-status/SKILL.md +263 -0
- package/skills/marquee-share/README.md +155 -0
- package/skills/marquee-share/SKILL.md +94 -0
- package/skills/marquee-share/SYNC.md +27 -0
- package/skills/marquee-share/dist/cli.js +896 -0
- package/skills/marquee-share/eslint.config.mjs +71 -0
- package/skills/marquee-share/install.sh +103 -0
- package/skills/marquee-share/package-lock.json +3946 -0
- package/skills/marquee-share/package.json +30 -0
- package/skills/marquee-share/src/auth/PROVENANCE.md +103 -0
- package/skills/marquee-share/src/auth/browser.ts +75 -0
- package/skills/marquee-share/src/auth/callback-server.ts +171 -0
- package/skills/marquee-share/src/auth/discovery.ts +171 -0
- package/skills/marquee-share/src/auth/flow.ts +262 -0
- package/skills/marquee-share/src/auth/index.ts +171 -0
- package/skills/marquee-share/src/auth/jwt.ts +77 -0
- package/skills/marquee-share/src/auth/pkce.ts +79 -0
- package/skills/marquee-share/src/auth/registration.ts +87 -0
- package/skills/marquee-share/src/auth/session.ts +205 -0
- package/skills/marquee-share/src/auth/token.ts +162 -0
- package/skills/marquee-share/src/cli.ts +246 -0
- package/skills/marquee-share/src/config.ts +101 -0
- package/skills/marquee-share/src/render/template.ts +171 -0
- package/skills/marquee-share/src/upload/index.ts +11 -0
- package/skills/marquee-share/src/upload/upload.ts +191 -0
- package/skills/marquee-share/tests/cli.test.ts +281 -0
- package/skills/marquee-share/tests/config.test.ts +119 -0
- package/skills/marquee-share/tests/flow.test.ts +356 -0
- package/skills/marquee-share/tests/no-token-leak.test.ts +240 -0
- package/skills/marquee-share/tests/pkce.test.ts +121 -0
- package/skills/marquee-share/tests/session.test.ts +173 -0
- package/skills/marquee-share/tests/template.test.ts +170 -0
- package/skills/marquee-share/tests/upload.test.ts +311 -0
- package/skills/marquee-share/tsconfig.json +23 -0
- package/skills/marquee-share/vitest.config.ts +15 -0
|
@@ -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.
|