@m-kopa/launchpad-cli 0.27.3 → 0.27.4
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 +16 -0
- package/dist/cli.js +195 -2
- package/dist/commands/editors.d.ts +16 -0
- package/dist/commands/editors.d.ts.map +1 -0
- package/dist/dispatcher.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/skills/launchpad-content-pr/SKILL.md +1 -1
- package/skills/launchpad-deploy/SKILL.md +1 -1
- package/skills/launchpad-deploy-status/SKILL.md +1 -1
- package/skills/launchpad-destroy/SKILL.md +1 -1
- package/skills/launchpad-onboard/SKILL.md +1 -1
- package/skills/launchpad-status/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html);
|
|
7
7
|
pre-1.0 minor bumps may carry breaking changes per ADR 0005.
|
|
8
8
|
|
|
9
|
+
## 0.27.4 — 2026-06-16
|
|
10
|
+
|
|
11
|
+
Self-serve developer-access grants (sp-grnted, Bundle 1).
|
|
12
|
+
|
|
13
|
+
- New `launchpad grant-editor <slug> <id>` / `revoke-editor <slug> <id>` —
|
|
14
|
+
owner-only verbs to manage who can develop/deploy an app. The CLI hides the
|
|
15
|
+
optimistic-concurrency dance (reads the version, writes with `If-Match`,
|
|
16
|
+
re-reads + retries once on a 409, then exits `75` on a persistent conflict)
|
|
17
|
+
and shows a confirm-preview before mutating live authz (`--yes` to skip,
|
|
18
|
+
required in non-interactive mode).
|
|
19
|
+
- New `launchpad whoami --share` (alias `--id`) prints **only** your verified
|
|
20
|
+
id, so you can hand it to an app owner granting you access. Fails closed if
|
|
21
|
+
the id is unreadable rather than printing a sentinel.
|
|
22
|
+
- Grant by **email** is intentionally deferred to a later release (Bundle 2);
|
|
23
|
+
email-shaped values on the by-id path are rejected with a pointer.
|
|
24
|
+
|
|
9
25
|
## 0.27.3 — 2026-06-15
|
|
10
26
|
|
|
11
27
|
DX: keep the Claude Code skill bundle current automatically (sp-uwugsm).
|
package/dist/cli.js
CHANGED
|
@@ -19,7 +19,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
19
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
20
|
|
|
21
21
|
// src/version.ts
|
|
22
|
-
var CLI_VERSION = "0.27.
|
|
22
|
+
var CLI_VERSION = "0.27.4";
|
|
23
23
|
|
|
24
24
|
// src/config.ts
|
|
25
25
|
import * as os from "node:os";
|
|
@@ -10993,7 +10993,11 @@ var whoamiCommand = {
|
|
|
10993
10993
|
summary: "show current session identity + role per app",
|
|
10994
10994
|
run: runWhoami
|
|
10995
10995
|
};
|
|
10996
|
-
async function runWhoami(
|
|
10996
|
+
async function runWhoami(args, io) {
|
|
10997
|
+
const wantShare = args.includes("--share") || args.includes("--id");
|
|
10998
|
+
if (wantShare) {
|
|
10999
|
+
return runShare(args, io);
|
|
11000
|
+
}
|
|
10997
11001
|
try {
|
|
10998
11002
|
const cfg = loadConfig();
|
|
10999
11003
|
const session = await readSession(cfg.sessionPath);
|
|
@@ -11036,10 +11040,197 @@ async function runWhoami(_args, io) {
|
|
|
11036
11040
|
return 1;
|
|
11037
11041
|
}
|
|
11038
11042
|
}
|
|
11043
|
+
async function runShare(args, io) {
|
|
11044
|
+
const json = args.includes("--json");
|
|
11045
|
+
try {
|
|
11046
|
+
const cfg = loadConfig();
|
|
11047
|
+
const session = await readSession(cfg.sessionPath);
|
|
11048
|
+
if (session === null) {
|
|
11049
|
+
io.err("No session — run `launchpad login`.");
|
|
11050
|
+
return 1;
|
|
11051
|
+
}
|
|
11052
|
+
let payload = null;
|
|
11053
|
+
try {
|
|
11054
|
+
payload = decodeJwtPayload(session.accessToken);
|
|
11055
|
+
} catch {}
|
|
11056
|
+
const rawSub = typeof payload?.sub === "string" ? payload.sub : null;
|
|
11057
|
+
const sub = rawSub === null ? null : rawSub.trim();
|
|
11058
|
+
if (sub === null || sub.length === 0) {
|
|
11059
|
+
io.err("Couldn't read your id from the session — run `launchpad login` again.");
|
|
11060
|
+
return 1;
|
|
11061
|
+
}
|
|
11062
|
+
io.out(json ? JSON.stringify({ sub }) : sub);
|
|
11063
|
+
return 0;
|
|
11064
|
+
} catch (e) {
|
|
11065
|
+
if (e instanceof UnauthenticatedError) {
|
|
11066
|
+
io.err(e.message);
|
|
11067
|
+
return 1;
|
|
11068
|
+
}
|
|
11069
|
+
io.err(`launchpad whoami --share failed: ${describe31(e)}`);
|
|
11070
|
+
return 1;
|
|
11071
|
+
}
|
|
11072
|
+
}
|
|
11039
11073
|
function describe31(e) {
|
|
11040
11074
|
return e instanceof Error ? e.message : String(e);
|
|
11041
11075
|
}
|
|
11042
11076
|
|
|
11077
|
+
// src/commands/editors.ts
|
|
11078
|
+
import { createInterface as createInterface4 } from "node:readline/promises";
|
|
11079
|
+
var CONFLICT_EXIT = 75;
|
|
11080
|
+
function realDeps() {
|
|
11081
|
+
return {
|
|
11082
|
+
loadConfig,
|
|
11083
|
+
apiJson,
|
|
11084
|
+
isTty: () => process.stdout.isTTY === true,
|
|
11085
|
+
prompt: async (question) => {
|
|
11086
|
+
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
11087
|
+
try {
|
|
11088
|
+
return (await rl.question(question)).trim();
|
|
11089
|
+
} finally {
|
|
11090
|
+
rl.close();
|
|
11091
|
+
}
|
|
11092
|
+
}
|
|
11093
|
+
};
|
|
11094
|
+
}
|
|
11095
|
+
function parseArgs13(args) {
|
|
11096
|
+
let slug;
|
|
11097
|
+
let target;
|
|
11098
|
+
let yes = false;
|
|
11099
|
+
let json = false;
|
|
11100
|
+
for (const a of args) {
|
|
11101
|
+
if (a === "--yes" || a === "-y")
|
|
11102
|
+
yes = true;
|
|
11103
|
+
else if (a === "--json")
|
|
11104
|
+
json = true;
|
|
11105
|
+
else if (slug === undefined)
|
|
11106
|
+
slug = a;
|
|
11107
|
+
else if (target === undefined)
|
|
11108
|
+
target = a;
|
|
11109
|
+
}
|
|
11110
|
+
if (slug === undefined || target === undefined)
|
|
11111
|
+
return null;
|
|
11112
|
+
return { slug, target, yes, json };
|
|
11113
|
+
}
|
|
11114
|
+
function isVersionMismatch(b) {
|
|
11115
|
+
return typeof b === "object" && b !== null && b.error === "version_mismatch" && typeof b.currentVersion === "number";
|
|
11116
|
+
}
|
|
11117
|
+
function makeEditorCommand(action, deps = realDeps()) {
|
|
11118
|
+
const name = action === "grant" ? "grant-editor" : "revoke-editor";
|
|
11119
|
+
return {
|
|
11120
|
+
name,
|
|
11121
|
+
summary: action === "grant" ? "grant a developer editor access to an app (by id; email in Phase 2)" : "revoke a developer's editor access to an app",
|
|
11122
|
+
run: (args, io) => runEditor(action, args, io, deps)
|
|
11123
|
+
};
|
|
11124
|
+
}
|
|
11125
|
+
async function runEditor(action, args, io, deps) {
|
|
11126
|
+
const verb = action === "grant" ? "grant-editor" : "revoke-editor";
|
|
11127
|
+
const parsed = parseArgs13(args);
|
|
11128
|
+
if (parsed === null) {
|
|
11129
|
+
io.err(`usage: launchpad ${verb} <slug> <id> [--yes] [--json]`);
|
|
11130
|
+
io.err(" <id> is the recipient's value from `launchpad whoami --share`.");
|
|
11131
|
+
return 64;
|
|
11132
|
+
}
|
|
11133
|
+
const { slug, target, yes, json } = parsed;
|
|
11134
|
+
if (!yes && !deps.isTty()) {
|
|
11135
|
+
io.err(`launchpad ${verb}: refusing to mutate "${slug}" without confirmation — pass --yes in non-interactive mode.`);
|
|
11136
|
+
return 64;
|
|
11137
|
+
}
|
|
11138
|
+
try {
|
|
11139
|
+
const cfg = deps.loadConfig();
|
|
11140
|
+
const current = await deps.apiJson(cfg, {
|
|
11141
|
+
path: `/apps/${slug}`
|
|
11142
|
+
});
|
|
11143
|
+
const alreadyEditor = current.record.editors.includes(target);
|
|
11144
|
+
if (!yes) {
|
|
11145
|
+
const noop = action === "grant" && alreadyEditor || action === "revoke" && !alreadyEditor;
|
|
11146
|
+
io.out(action === "grant" ? `About to grant editor access on "${slug}" to:
|
|
11147
|
+
${target}` : `About to revoke editor access on "${slug}" from:
|
|
11148
|
+
${target}`);
|
|
11149
|
+
if (noop) {
|
|
11150
|
+
io.out(action === "grant" ? " (already an editor — this will be a no-op)" : " (not currently an editor — this will be a no-op)");
|
|
11151
|
+
}
|
|
11152
|
+
const answer = (await deps.prompt("Proceed? [y/N] ")).toLowerCase();
|
|
11153
|
+
if (answer !== "y" && answer !== "yes") {
|
|
11154
|
+
io.out("Aborted — no change made.");
|
|
11155
|
+
return 0;
|
|
11156
|
+
}
|
|
11157
|
+
}
|
|
11158
|
+
const result = await mutateWithRetry(action, slug, target, current.record.version, cfg, deps);
|
|
11159
|
+
if (result.kind === "conflict") {
|
|
11160
|
+
io.err(`launchpad ${verb}: the app record for "${slug}" changed during the ${action} — re-run \`launchpad ${verb} ${slug} ${target}\`.`);
|
|
11161
|
+
return CONFLICT_EXIT;
|
|
11162
|
+
}
|
|
11163
|
+
const changed = result.record.version !== result.attemptedVersion;
|
|
11164
|
+
if (json) {
|
|
11165
|
+
io.out(JSON.stringify({
|
|
11166
|
+
ok: true,
|
|
11167
|
+
slug,
|
|
11168
|
+
action,
|
|
11169
|
+
editor: target,
|
|
11170
|
+
changed,
|
|
11171
|
+
editors: result.record.editors
|
|
11172
|
+
}));
|
|
11173
|
+
return 0;
|
|
11174
|
+
}
|
|
11175
|
+
if (!changed) {
|
|
11176
|
+
io.out(action === "grant" ? `${target} was already an editor on "${slug}" — no change.` : `${target} was not an editor on "${slug}" — no change.`);
|
|
11177
|
+
} else {
|
|
11178
|
+
io.out(action === "grant" ? `✓ ${target} is now an editor on "${slug}".` : `✓ ${target} is no longer an editor on "${slug}".`);
|
|
11179
|
+
}
|
|
11180
|
+
return 0;
|
|
11181
|
+
} catch (e) {
|
|
11182
|
+
if (e instanceof UnauthenticatedError) {
|
|
11183
|
+
io.err(e.message);
|
|
11184
|
+
return 1;
|
|
11185
|
+
}
|
|
11186
|
+
if (e instanceof ForbiddenError) {
|
|
11187
|
+
io.err(`launchpad ${verb}: not authorised — you must be the owner of "${slug}" to manage its editors.`);
|
|
11188
|
+
return 1;
|
|
11189
|
+
}
|
|
11190
|
+
if (e instanceof NotFoundError) {
|
|
11191
|
+
io.err(`launchpad ${verb}: no app "${slug}" — check the slug with \`launchpad apps\`.`);
|
|
11192
|
+
return 1;
|
|
11193
|
+
}
|
|
11194
|
+
io.err(`launchpad ${verb} failed: ${describe32(e)}`);
|
|
11195
|
+
return 1;
|
|
11196
|
+
}
|
|
11197
|
+
}
|
|
11198
|
+
async function mutateWithRetry(action, slug, target, startVersion, cfg, deps) {
|
|
11199
|
+
let version = startVersion;
|
|
11200
|
+
for (let attempt = 0;attempt < 2; attempt++) {
|
|
11201
|
+
const body = await sendMutation(action, slug, target, version, cfg, deps);
|
|
11202
|
+
if (isVersionMismatch(body)) {
|
|
11203
|
+
version = body.currentVersion;
|
|
11204
|
+
continue;
|
|
11205
|
+
}
|
|
11206
|
+
return { kind: "ok", record: body.record, attemptedVersion: version };
|
|
11207
|
+
}
|
|
11208
|
+
return { kind: "conflict" };
|
|
11209
|
+
}
|
|
11210
|
+
async function sendMutation(action, slug, target, version, cfg, deps) {
|
|
11211
|
+
const headers = { "if-match": String(version) };
|
|
11212
|
+
if (action === "grant") {
|
|
11213
|
+
return deps.apiJson(cfg, {
|
|
11214
|
+
method: "POST",
|
|
11215
|
+
path: `/apps/${slug}/editors`,
|
|
11216
|
+
jsonBody: { editor: target },
|
|
11217
|
+
headers,
|
|
11218
|
+
nonThrowingStatuses: [409]
|
|
11219
|
+
});
|
|
11220
|
+
}
|
|
11221
|
+
return deps.apiJson(cfg, {
|
|
11222
|
+
method: "DELETE",
|
|
11223
|
+
path: `/apps/${slug}/editors/${encodeURIComponent(target)}`,
|
|
11224
|
+
headers,
|
|
11225
|
+
nonThrowingStatuses: [409]
|
|
11226
|
+
});
|
|
11227
|
+
}
|
|
11228
|
+
function describe32(e) {
|
|
11229
|
+
return e instanceof Error ? e.message : String(e);
|
|
11230
|
+
}
|
|
11231
|
+
var grantEditorCommand = makeEditorCommand("grant");
|
|
11232
|
+
var revokeEditorCommand = makeEditorCommand("revoke");
|
|
11233
|
+
|
|
11043
11234
|
// src/update-notifier.ts
|
|
11044
11235
|
import { spawn as spawn6 } from "node:child_process";
|
|
11045
11236
|
import { homedir as homedir4 } from "node:os";
|
|
@@ -11238,6 +11429,8 @@ var COMMANDS = [
|
|
|
11238
11429
|
rollbackCommand,
|
|
11239
11430
|
groupsCommand,
|
|
11240
11431
|
secretsCommand,
|
|
11432
|
+
grantEditorCommand,
|
|
11433
|
+
revokeEditorCommand,
|
|
11241
11434
|
refreshUpdateCacheCommand
|
|
11242
11435
|
];
|
|
11243
11436
|
async function dispatch(argv, io, commands = COMMANDS) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CliConfig } from "../config.js";
|
|
2
|
+
import type { ApiRequestOptions } from "../http/api-client.js";
|
|
3
|
+
import type { Command } from "../dispatcher.js";
|
|
4
|
+
/** EX_TEMPFAIL — a concurrent edit won the race; the action is safe to retry. */
|
|
5
|
+
export declare const CONFLICT_EXIT = 75;
|
|
6
|
+
export type EditorAction = "grant" | "revoke";
|
|
7
|
+
export interface EditorDeps {
|
|
8
|
+
readonly loadConfig: () => CliConfig;
|
|
9
|
+
readonly apiJson: <T>(cfg: CliConfig, opts: ApiRequestOptions) => Promise<T>;
|
|
10
|
+
readonly isTty: () => boolean;
|
|
11
|
+
readonly prompt: (question: string) => Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
export declare function makeEditorCommand(action: EditorAction, deps?: EditorDeps): Command;
|
|
14
|
+
export declare const grantEditorCommand: Command;
|
|
15
|
+
export declare const revokeEditorCommand: Command;
|
|
16
|
+
//# sourceMappingURL=editors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editors.d.ts","sourceRoot":"","sources":["../../src/commands/editors.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAEjE,iFAAiF;AACjF,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE9C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,SAAS,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7E,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACxD;AA8DD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,GAAE,UAAuB,GAAG,OAAO,CAU9F;AAgLD,eAAO,MAAM,kBAAkB,EAAE,OAAoC,CAAC;AACtE,eAAO,MAAM,mBAAmB,EAAE,OAAqC,CAAC"}
|
package/dist/dispatcher.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../src/dispatcher.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../src/dispatcher.ts"],"names":[],"mappings":"AAkDA,MAAM,WAAW,KAAK;IACpB,sDAAsD;IACtD,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,wCAAwC;IACxC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxE;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,EAAE,SAAS,OAAO,EAgCtC,CAAC;AAEF;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,KAAK,EACT,QAAQ,GAAE,SAAS,OAAO,EAAa,GACtC,OAAO,CAAC,QAAQ,CAAC,CAmBnB;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,CAyBvE"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.27.
|
|
1
|
+
export declare const CLI_VERSION = "0.27.4";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-content-pr
|
|
3
3
|
description: Push a content change to a Launchpad app via `launchpad deploy` and verify it shipped via `launchpad status`. Covers the post-first-deploy iteration loop (edit → deploy → verify) — subsequent deploys commit directly to the app repo's main and the Pages build runs asynchronously, so verification is its own step. Use when someone says "push a content change", "ship an update", "/launchpad-content-pr", "verify my deploy", or after `/launchpad-deploy` reports `done` and they want to follow up with an edit.
|
|
4
|
-
version: 0.27.
|
|
4
|
+
version: 0.27.4
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-deploy
|
|
3
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 watches the rollout via `launchpad status`. 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.27.
|
|
4
|
+
version: 0.27.4
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-deploy-status
|
|
3
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 in-flight provisioning, and is the canonical home for `launchpad recover` (repair a terminal-failed app record that is actually live). Use when someone says "what's the status of demo-X", "/launchpad-deploy-status", "is my deploy stuck", "my app says failed but it's serving", or after `/launchpad-deploy` reports a non-`done` terminal stage.
|
|
4
|
-
version: 0.27.
|
|
4
|
+
version: 0.27.4
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-destroy
|
|
3
3
|
description: Tear down a Launchpad app end-to-end via `launchpad destroy` — Cloudflare Pages project, edge-auth wiring (gateway KV/audience entries, or the Access app for `auth: access` apps), custom hostname, platform-repo TF, 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.27.
|
|
4
|
+
version: 0.27.4
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-onboard
|
|
3
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 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.27.
|
|
4
|
+
version: 0.27.4
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-status
|
|
3
3
|
description: Show whether a Launchpad app's local launchpad.yaml matches what's deployed, and read the deployed manifest. Wraps `launchpad pull` (fetch deployed YAML) and `launchpad status` (drift report). Use when someone says "is my app in sync", "what's deployed", "show drift", "/launchpad-status", "/launchpad-pull", or after `launchpad deploy` to verify the change landed.
|
|
4
|
-
version: 0.27.
|
|
4
|
+
version: 0.27.4
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|