@m-kopa/launchpad-cli 0.61.0 → 0.62.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 CHANGED
@@ -8,6 +8,27 @@ pre-1.0 minor bumps may carry breaking changes per ADR 0005.
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 0.62.0 - 2026-09-17
12
+
13
+ ### Added
14
+
15
+ - `launchpad migrate --origin dispatch` converts compatible, unprovisioned static and React apps to Workers without hand-editing v1 manifests.
16
+ It preserves access and application settings, keeps the original YAML, and refuses unsupported runtimes or an existing app record.
17
+ Failed writes clean up only the current attempt's files so migration can be retried.
18
+
19
+ ### Fixed
20
+
21
+ - Pages quota errors explain why the existing manifest still selects Pages and give the supported migration command.
22
+ Pages Functions apps get build-specific guidance instead of an unsafe origin-only edit.
23
+ - Deploy prints when the manifest selects Pages, so a CLI update is not mistaken for a runtime migration.
24
+
25
+ ## 0.61.1 - 2026-09-17
26
+
27
+ ### Fixed
28
+
29
+ - Post-deploy checks stop at redirects instead of comparing an SSO sign-in page with the app.
30
+ Private apps report that content verification needs an authenticated browser, while direct content mismatches still fail.
31
+
11
32
  ## 0.61.0 - 2026-09-17
12
33
 
13
34
  ### Changed
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.61.0";
22
+ var CLI_VERSION = "0.62.0";
23
23
 
24
24
  // src/config.ts
25
25
  import { readFileSync } from "node:fs";
@@ -226,7 +226,12 @@ class ForbiddenError extends Error {
226
226
  }
227
227
 
228
228
  class NotFoundError extends Error {
229
+ serverCode;
229
230
  code = "not_found";
231
+ constructor(message, serverCode) {
232
+ super(message);
233
+ this.serverCode = serverCode;
234
+ }
230
235
  }
231
236
  var EXIT_CLI_UPGRADE_REQUIRED = 42;
232
237
 
@@ -1148,8 +1153,9 @@ async function apiRaw(cfg, opts, fetcher = fetch) {
1148
1153
  throw recordedFault(new ForbiddenError(`bot returned 403 for ${opts.path}: ${detail}`));
1149
1154
  }
1150
1155
  if (res.status === 404) {
1151
- const detail = await errorDetail(res, opts.path);
1152
- throw recordedFault(new NotFoundError(`bot returned 404 for ${opts.path}: ${detail}`));
1156
+ const envelope = await readErrorEnvelope(res, opts.path);
1157
+ const detail = formatErrorEnvelope(envelope);
1158
+ throw recordedFault(new NotFoundError(`bot returned 404 for ${opts.path}: ${detail}`, envelope.code));
1153
1159
  }
1154
1160
  if (!res.ok) {
1155
1161
  if (opts.nonThrowingStatuses?.includes(res.status) === true) {
@@ -4886,7 +4892,7 @@ function toBytes(v) {
4886
4892
  async function verifyServed(args, deps = { fetch }) {
4887
4893
  let res;
4888
4894
  try {
4889
- res = await deps.fetch(args.servedUrl, { method: "GET" });
4895
+ res = await deps.fetch(args.servedUrl, { method: "GET", redirect: "manual" });
4890
4896
  } catch (e) {
4891
4897
  return {
4892
4898
  kind: "unreachable",
@@ -4897,7 +4903,7 @@ async function verifyServed(args, deps = { fetch }) {
4897
4903
  return {
4898
4904
  kind: "unreachable",
4899
4905
  status: res.status,
4900
- reason: `served URL returned HTTP ${res.status}`
4906
+ reason: [301, 302, 303, 307, 308].includes(res.status) ? `served URL redirected (HTTP ${res.status}); verify the app in an authenticated browser` : `served URL returned HTTP ${res.status}`
4901
4907
  };
4902
4908
  }
4903
4909
  if (args.appType !== "static") {
@@ -9076,6 +9082,12 @@ async function runModelADeploy(args) {
9076
9082
  return 64;
9077
9083
  }
9078
9084
  io.out(`Bundling ${cwd} for ${slug} (Model A) …`);
9085
+ if (declaredOrigin !== "dispatch" && appType !== "container") {
9086
+ io.out("Deployment target: Cloudflare Pages, selected by launchpad.yaml.");
9087
+ if (appType === "static" || appType === "react") {
9088
+ io.out("For an app that has never provisioned, `launchpad migrate --origin dispatch` checks whether it can use Workers without a Pages project.");
9089
+ }
9090
+ }
9079
9091
  let cfg;
9080
9092
  try {
9081
9093
  cfg = loadConfig();
@@ -11828,6 +11840,178 @@ function describe25(e) {
11828
11840
  return e instanceof Error ? e.message : String(e);
11829
11841
  }
11830
11842
 
11843
+ // src/commands/migrate.ts
11844
+ import { closeSync, existsSync as existsSync11, lstatSync as lstatSync4, mkdirSync as mkdirSync4, mkdtempSync, openSync, readFileSync as readFileSync16, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
11845
+ import path12 from "node:path";
11846
+ import { stringify } from "yaml";
11847
+
11848
+ // src/manifest/migrate-dispatch.ts
11849
+ function dispatchManifest(manifest) {
11850
+ const v2 = manifest.apiVersion === "launchpad.m-kopa.us/v2";
11851
+ const source = v2 ? manifest.spec : manifest;
11852
+ const appType = v2 ? manifest.spec.appType : manifest.deployment.type;
11853
+ if (appType !== "static" && appType !== "react") {
11854
+ throw new Error("Only static and React apps can use this converter. Pages Functions (react+api) need a compiled Worker build before switching to dispatch; containers use their own runtime.");
11855
+ }
11856
+ if (source.secrets !== undefined || source.containers !== undefined) {
11857
+ throw new Error("Secret and container bindings cannot be converted automatically.");
11858
+ }
11859
+ if (!v2)
11860
+ assertConvertibleV1(manifest);
11861
+ const spec = v2 ? { ...manifest.spec } : {
11862
+ appType,
11863
+ display: {
11864
+ name: manifest.metadata.name,
11865
+ team: manifest.metadata.team,
11866
+ ...manifest.metadata.description === undefined ? {} : { description: manifest.metadata.description }
11867
+ },
11868
+ owner: { email: manifest.metadata.owner },
11869
+ access: {
11870
+ audience: manifest.access.audience,
11871
+ private_owner: manifest.access.private_owner,
11872
+ allowed_entra_groups: [...allowedEntraGroups(manifest.access)],
11873
+ identity_types: manifest.access.identity_types
11874
+ },
11875
+ build: manifest.build,
11876
+ app: manifest.app,
11877
+ verify: manifest.verify,
11878
+ sensitive: manifest.sensitive,
11879
+ catalogue: manifest.catalogue,
11880
+ ...manifest.production_env === undefined ? {} : {
11881
+ env_vars: Object.fromEntries(Object.entries(manifest.production_env).map(([name, value]) => [name, { type: "plain_text", value }]))
11882
+ }
11883
+ };
11884
+ const candidate = {
11885
+ apiVersion: "launchpad.m-kopa.us/v2",
11886
+ kind: "App",
11887
+ metadata: v2 ? manifest.metadata : { slug: manifest.metadata.name, schemaVersion: 2 },
11888
+ spec: { ...spec, origin: "dispatch", auth: "gateway", confine_origin: undefined }
11889
+ };
11890
+ const parsed = parseManifestV2(candidate);
11891
+ if (parsed.kind !== "ok") {
11892
+ throw new Error(`Cannot convert manifest: ${parsed.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
11893
+ }
11894
+ return parsed.manifest;
11895
+ }
11896
+ function assertConvertibleV1(manifest) {
11897
+ const unsupported2 = [
11898
+ manifest.hostnames !== undefined && "hostnames",
11899
+ manifest.targets !== undefined && "targets",
11900
+ manifest.access.session_duration !== undefined && "access.session_duration",
11901
+ manifest.deployment.runtime !== undefined && "deployment.runtime"
11902
+ ].filter(Boolean);
11903
+ if (unsupported2.length > 0) {
11904
+ throw new Error(`Cannot preserve ${unsupported2.join(", ")} on dispatch. Review these settings with the platform team before migrating.`);
11905
+ }
11906
+ }
11907
+
11908
+ // src/commands/migrate.ts
11909
+ var USAGE = "usage: launchpad migrate --origin dispatch [--dry-run]";
11910
+ var migrateCommand = {
11911
+ name: "migrate",
11912
+ summary: "convert an unprovisioned static or React app to Workers dispatch",
11913
+ run: runMigrate
11914
+ };
11915
+ async function runMigrate(args, io) {
11916
+ if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) {
11917
+ io.out(USAGE);
11918
+ return 0;
11919
+ }
11920
+ const flags = args.filter((arg) => arg !== "--dry-run");
11921
+ if (flags.length !== 2 || flags[0] !== "--origin" || flags[1] !== "dispatch" || args.length > 3) {
11922
+ io.err(USAGE);
11923
+ return 64;
11924
+ }
11925
+ const cwd = process.cwd();
11926
+ const file = path12.join(cwd, "launchpad.yaml");
11927
+ try {
11928
+ if (!lstatSync4(file).isFile())
11929
+ throw new Error("launchpad.yaml must be a regular file, not a symlink.");
11930
+ const original = readFileSync16(file, "utf8");
11931
+ const loaded = parseManifestAnyVersion(original, file);
11932
+ if (loaded.kind !== "ok")
11933
+ throw new Error("launchpad.yaml is invalid. Run `launchpad validate` and correct it first.");
11934
+ const manifest = loaded.manifest;
11935
+ if (manifest.apiVersion === "launchpad.m-kopa.us/v2" && manifest.spec.origin === "dispatch") {
11936
+ io.out("launchpad.yaml already selects Workers dispatch. Run `launchpad deploy`.");
11937
+ return 0;
11938
+ }
11939
+ const converted = dispatchManifest(manifest);
11940
+ assertNoCustomRuntime(cwd, converted);
11941
+ const slug = converted.metadata.slug;
11942
+ await assertUnregistered(slug);
11943
+ if (args.includes("--dry-run")) {
11944
+ io.out(`${slug}: eligible for v2 Workers dispatch with gateway authentication. No files changed.`);
11945
+ return 0;
11946
+ }
11947
+ saveMigration(file, original, converted);
11948
+ io.out(`${slug}: launchpad.yaml now uses v2 Workers dispatch with gateway authentication.`);
11949
+ io.out("Audience, owner and application settings are preserved. Original: .launchpad/manifest-before-dispatch.yaml");
11950
+ io.out("Run `launchpad deploy`. No Pages project is needed.");
11951
+ return 0;
11952
+ } catch (error) {
11953
+ io.err(`launchpad migrate: ${error instanceof Error ? error.message : String(error)}`);
11954
+ return 1;
11955
+ }
11956
+ }
11957
+ async function assertUnregistered(slug) {
11958
+ try {
11959
+ await apiRaw(loadConfig(), { path: `/apps/${encodeURIComponent(slug)}/manifest/state` });
11960
+ } catch (error) {
11961
+ if (error instanceof NotFoundError && error.serverCode === "not_found")
11962
+ return;
11963
+ throw error;
11964
+ }
11965
+ throw new Error("This app is already registered. This command only converts apps that have never provisioned; ask the platform team to migrate an existing app.");
11966
+ }
11967
+ function assertNoCustomRuntime(cwd, manifest) {
11968
+ const appRoot = path12.resolve(cwd, manifest.spec.app?.root ?? ".");
11969
+ const buildRoot = path12.resolve(appRoot, manifest.spec.build?.root_dir ?? ".");
11970
+ const roots = new Set([
11971
+ cwd,
11972
+ appRoot,
11973
+ buildRoot,
11974
+ path12.resolve(cwd, manifest.spec.build?.root_dir ?? "."),
11975
+ path12.resolve(buildRoot, manifest.spec.build?.destination_dir ?? "dist")
11976
+ ]);
11977
+ for (const root of roots) {
11978
+ for (const name of ["functions", "_worker.js", "wrangler.toml", "wrangler.json", "wrangler.jsonc"]) {
11979
+ if (existsSync11(path12.join(root, name))) {
11980
+ throw new Error(`${name} needs a runtime review before conversion. Pages Functions and custom Worker bindings must not be dropped. Ask the platform team to review this app.`);
11981
+ }
11982
+ }
11983
+ }
11984
+ }
11985
+ function saveMigration(file, original, converted) {
11986
+ if (readFileSync16(file, "utf8") !== original)
11987
+ throw new Error("launchpad.yaml changed during the check. Retry migration.");
11988
+ const backupDir = path12.join(path12.dirname(file), ".launchpad");
11989
+ mkdirSync4(backupDir, { recursive: true });
11990
+ const backup = path12.join(backupDir, "manifest-before-dispatch.yaml");
11991
+ const attemptDir = mkdtempSync(path12.join(backupDir, "migration-"));
11992
+ let backupCreated = false;
11993
+ try {
11994
+ const fd = openSync(backup, "wx", 384);
11995
+ backupCreated = true;
11996
+ try {
11997
+ writeFileSync4(fd, original);
11998
+ } finally {
11999
+ closeSync(fd);
12000
+ }
12001
+ const temporary = path12.join(attemptDir, "launchpad.yaml");
12002
+ writeFileSync4(temporary, stringify(converted), { flag: "wx", mode: 384 });
12003
+ if (readFileSync16(file, "utf8") !== original)
12004
+ throw new Error("launchpad.yaml changed during migration. Retry migration.");
12005
+ renameSync(temporary, file);
12006
+ } catch (error) {
12007
+ if (backupCreated)
12008
+ rmSync2(backup);
12009
+ throw error;
12010
+ } finally {
12011
+ rmSync2(attemptDir, { recursive: true, force: true });
12012
+ }
12013
+ }
12014
+
11831
12015
  // src/commands/plan.ts
11832
12016
  import { resolve as resolve12 } from "node:path";
11833
12017
  var planCommand = {
@@ -12292,18 +12476,18 @@ function describe26(e) {
12292
12476
  }
12293
12477
 
12294
12478
  // src/commands/pull.ts
12295
- import { writeFileSync as writeFileSync4 } from "node:fs";
12479
+ import { writeFileSync as writeFileSync5 } from "node:fs";
12296
12480
  import { stringify as stringifyYaml } from "yaml";
12297
12481
 
12298
12482
  // src/deploy/manifest-status.ts
12299
12483
  async function fetchManifestStatus(cfg, slug, fetcher = fetch) {
12300
- const path12 = `/apps/${encodeURIComponent(slug)}/manifest/status`;
12301
- return apiJson(cfg, { path: path12 }, fetcher);
12484
+ const path13 = `/apps/${encodeURIComponent(slug)}/manifest/status`;
12485
+ return apiJson(cfg, { path: path13 }, fetcher);
12302
12486
  }
12303
12487
 
12304
12488
  // src/pull/source-sync.ts
12305
12489
  import * as fs6 from "node:fs/promises";
12306
- import * as path12 from "node:path";
12490
+ import * as path13 from "node:path";
12307
12491
  import { constants as fsConstants } from "node:fs";
12308
12492
  import { createHash as createHash3 } from "node:crypto";
12309
12493
  import { execFile } from "node:child_process";
@@ -12342,9 +12526,9 @@ async function runSourceSync(args) {
12342
12526
  args.io.out(`Already current at ${compare.headSha.slice(0, 12)}.`);
12343
12527
  return 0;
12344
12528
  }
12345
- const stageRoot = path12.resolve(args.cwd, MARKER_DIR, `pull-stage-${process.pid}-${crypto.randomUUID()}`);
12346
- const baseDir = path12.join(stageRoot, "base");
12347
- const headDir = path12.join(stageRoot, "head");
12529
+ const stageRoot = path13.resolve(args.cwd, MARKER_DIR, `pull-stage-${process.pid}-${crypto.randomUUID()}`);
12530
+ const baseDir = path13.join(stageRoot, "base");
12531
+ const headDir = path13.join(stageRoot, "head");
12348
12532
  await fs6.mkdir(baseDir, { recursive: true });
12349
12533
  await fs6.mkdir(headDir, { recursive: true });
12350
12534
  try {
@@ -12396,7 +12580,7 @@ async function bootstrapProvenance(args) {
12396
12580
  args.io.err(" Preserve this directory and obtain a separate current clone for comparison.");
12397
12581
  return 1;
12398
12582
  }
12399
- const stageRoot = path12.resolve(args.cwd, MARKER_DIR, `pull-bootstrap-${process.pid}-${crypto.randomUUID()}`);
12583
+ const stageRoot = path13.resolve(args.cwd, MARKER_DIR, `pull-bootstrap-${process.pid}-${crypto.randomUUID()}`);
12400
12584
  await fs6.mkdir(stageRoot, { recursive: true });
12401
12585
  try {
12402
12586
  const headSha = await fetchSnapshotRef(args.cfg, args.slug, "main", stageRoot);
@@ -12422,7 +12606,7 @@ async function treesEqual(cwd, tracked, headDir, headPaths) {
12422
12606
  for (const relative6 of tracked) {
12423
12607
  if (!expected.has(relative6))
12424
12608
  return false;
12425
- if (!equal(await readEntry(path12.resolve(cwd, relative6)), await readEntry(path12.resolve(headDir, relative6)))) {
12609
+ if (!equal(await readEntry(path13.resolve(cwd, relative6)), await readEntry(path13.resolve(headDir, relative6)))) {
12426
12610
  return false;
12427
12611
  }
12428
12612
  }
@@ -12430,7 +12614,7 @@ async function treesEqual(cwd, tracked, headDir, headPaths) {
12430
12614
  }
12431
12615
  async function listFiles(root, relative6 = "") {
12432
12616
  const files = [];
12433
- const entries = await fs6.readdir(path12.resolve(root, relative6), {
12617
+ const entries = await fs6.readdir(path13.resolve(root, relative6), {
12434
12618
  withFileTypes: true
12435
12619
  });
12436
12620
  for (const entry of entries) {
@@ -12506,7 +12690,7 @@ async function cleanAncestorRemovals(args) {
12506
12690
  const checked = await checkParents(args.cwd, relative6, removals);
12507
12691
  if (checked.kind === "blocked")
12508
12692
  continue;
12509
- const base = await readEntry(path12.resolve(args.baseDir, relative6));
12693
+ const base = await readEntry(path13.resolve(args.baseDir, relative6));
12510
12694
  const local = checked.projectedMissing ? { kind: "missing" } : await readEntry(checked.target);
12511
12695
  if (local.kind !== "missing" && cleanLocal(base, local))
12512
12696
  removals.add(relative6);
@@ -12516,9 +12700,9 @@ async function cleanAncestorRemovals(args) {
12516
12700
  async function planPath(args, relative6, removals) {
12517
12701
  assertSafePath(relative6);
12518
12702
  const checked = await checkParents(args.cwd, relative6, removals);
12519
- const base = await readEntry(path12.resolve(args.baseDir, relative6));
12703
+ const base = await readEntry(path13.resolve(args.baseDir, relative6));
12520
12704
  const upstreamDeleted = args.compare.deleted.includes(relative6);
12521
- const head = upstreamDeleted ? { kind: "missing" } : await readEntry(path12.resolve(args.headDir, relative6));
12705
+ const head = upstreamDeleted ? { kind: "missing" } : await readEntry(path13.resolve(args.headDir, relative6));
12522
12706
  const unsupported2 = (args.compare.unsupported ?? []).includes(relative6);
12523
12707
  if (checked.kind === "blocked") {
12524
12708
  return conflictPlan(relative6, checked.target, { kind: "missing" }, head, upstreamDeleted, checked.reason);
@@ -12655,7 +12839,7 @@ async function writeAtomic(cwd, relative6, bytes, mode, allowMetadata = false) {
12655
12839
  if (checked.kind === "blocked")
12656
12840
  throw new Error(checked.reason);
12657
12841
  const file = checked.target;
12658
- await fs6.mkdir(path12.dirname(file), { recursive: true });
12842
+ await fs6.mkdir(path13.dirname(file), { recursive: true });
12659
12843
  const rechecked = await checkParents(cwd, relative6, new Set, allowMetadata);
12660
12844
  if (rechecked.kind === "blocked")
12661
12845
  throw new Error(rechecked.reason);
@@ -12697,14 +12881,14 @@ function isSafeSourcePath(relative6) {
12697
12881
  return isSafeRelativePath(relative6) && !relative6.startsWith(`${MARKER_DIR}/`);
12698
12882
  }
12699
12883
  function isSafeRelativePath(relative6) {
12700
- const normalized = path12.posix.normalize(relative6);
12701
- return relative6.length > 0 && !relative6.includes("\\") && !path12.isAbsolute(relative6) && normalized === relative6 && normalized !== ".." && !normalized.startsWith("../");
12884
+ const normalized = path13.posix.normalize(relative6);
12885
+ return relative6.length > 0 && !relative6.includes("\\") && !path13.isAbsolute(relative6) && normalized === relative6 && normalized !== ".." && !normalized.startsWith("../");
12702
12886
  }
12703
12887
  function resolveInsideClone(cwd, relative6) {
12704
- const root = path12.resolve(cwd);
12705
- const target = path12.resolve(root, relative6);
12706
- const fromRoot = path12.relative(root, target);
12707
- if (fromRoot.length === 0 || path12.isAbsolute(fromRoot) || fromRoot === ".." || fromRoot.startsWith(`..${path12.sep}`)) {
12888
+ const root = path13.resolve(cwd);
12889
+ const target = path13.resolve(root, relative6);
12890
+ const fromRoot = path13.relative(root, target);
12891
+ if (fromRoot.length === 0 || path13.isAbsolute(fromRoot) || fromRoot === ".." || fromRoot.startsWith(`..${path13.sep}`)) {
12708
12892
  throw new Error(`unsafe source path returned by bot: ${relative6}`);
12709
12893
  }
12710
12894
  return target;
@@ -12719,9 +12903,9 @@ async function checkParents(cwd, relative6, projectedRemovals, allowMetadata = f
12719
12903
  }
12720
12904
  const target = resolveInsideClone(cwd, relative6);
12721
12905
  const components = relative6.split("/");
12722
- let current = path12.resolve(cwd);
12906
+ let current = path13.resolve(cwd);
12723
12907
  for (let index = 0;index < components.length - 1; index += 1) {
12724
- current = path12.join(current, components[index]);
12908
+ current = path13.join(current, components[index]);
12725
12909
  const ancestor = components.slice(0, index + 1).join("/");
12726
12910
  const stat = await lstatIfPresent(current);
12727
12911
  if (stat === null)
@@ -12747,12 +12931,12 @@ async function checkParents(cwd, relative6, projectedRemovals, allowMetadata = f
12747
12931
  return { kind: "safe", target, projectedMissing: false };
12748
12932
  }
12749
12933
  async function assertSafeCloneRoot(cwd) {
12750
- const root = await fs6.lstat(path12.resolve(cwd));
12934
+ const root = await fs6.lstat(path13.resolve(cwd));
12751
12935
  if (!root.isDirectory() || root.isSymbolicLink()) {
12752
12936
  throw new Error("launchpad pull requires a real clone directory");
12753
12937
  }
12754
12938
  try {
12755
- const metadata = await fs6.lstat(path12.resolve(cwd, MARKER_DIR));
12939
+ const metadata = await fs6.lstat(path13.resolve(cwd, MARKER_DIR));
12756
12940
  if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
12757
12941
  throw new Error("launchpad pull refuses a non-directory or symlinked .launchpad path");
12758
12942
  }
@@ -12808,7 +12992,7 @@ async function runPull(args, io) {
12808
12992
  }
12809
12993
  const yaml = stringifyYaml(res.status);
12810
12994
  if (parsed.out !== null) {
12811
- writeFileSync4(parsed.out, yaml);
12995
+ writeFileSync5(parsed.out, yaml);
12812
12996
  io.err(`launchpad pull: wrote status (${res.role} view) to ${parsed.out}`);
12813
12997
  } else {
12814
12998
  io.out(yaml.replace(/\n$/, ""));
@@ -12829,7 +13013,7 @@ async function runPull(args, io) {
12829
13013
  return 1;
12830
13014
  }
12831
13015
  if (parsed.out !== null) {
12832
- writeFileSync4(parsed.out, state.manifestYaml);
13016
+ writeFileSync5(parsed.out, state.manifestYaml);
12833
13017
  io.err(`launchpad pull: wrote ${state.manifestYaml.length} bytes to ${parsed.out}`);
12834
13018
  } else {
12835
13019
  io.out(state.manifestYaml.replace(/\n$/, ""));
@@ -13230,7 +13414,7 @@ var watchCommand = {
13230
13414
  };
13231
13415
 
13232
13416
  // src/commands/review.ts
13233
- import * as path13 from "node:path";
13417
+ import * as path14 from "node:path";
13234
13418
  var reviewCommand = {
13235
13419
  name: "review",
13236
13420
  summary: "show the review state for a PR (slug-scoped)",
@@ -13250,7 +13434,7 @@ async function runReview(args, io) {
13250
13434
  } else {
13251
13435
  const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
13252
13436
  if (inferred === null) {
13253
- io.err(`launchpad review: could not infer slug from cwd (${path13.basename(process.cwd())});
13437
+ io.err(`launchpad review: could not infer slug from cwd (${path14.basename(process.cwd())});
13254
13438
  ` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
13255
13439
  return 64;
13256
13440
  }
@@ -13383,7 +13567,7 @@ function describe28(e) {
13383
13567
  }
13384
13568
 
13385
13569
  // src/deploy/rollback.ts
13386
- import { existsSync as existsSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync5 } from "node:fs";
13570
+ import { existsSync as existsSync12, readFileSync as readFileSync17, writeFileSync as writeFileSync6 } from "node:fs";
13387
13571
  import { resolve as resolvePath3 } from "node:path";
13388
13572
  import { createInterface as createInterface3 } from "node:readline/promises";
13389
13573
 
@@ -13485,7 +13669,7 @@ async function runRollback(opts, io, deps = {}) {
13485
13669
  }
13486
13670
  }
13487
13671
  try {
13488
- writeFileSync5(manifestPath, historicalYaml, "utf8");
13672
+ writeFileSync6(manifestPath, historicalYaml, "utf8");
13489
13673
  } catch (e) {
13490
13674
  io.err(`launchpad rollback: failed to write ${manifestPath}: ${describe29(e)}`);
13491
13675
  return 2;
@@ -13507,16 +13691,16 @@ async function runRollback(opts, io, deps = {}) {
13507
13691
  yes: true
13508
13692
  }, io, applyDeps);
13509
13693
  }
13510
- function readCurrentManifest(path14) {
13511
- if (!existsSync11(path14))
13694
+ function readCurrentManifest(path15) {
13695
+ if (!existsSync12(path15))
13512
13696
  return null;
13513
13697
  let raw;
13514
13698
  try {
13515
- raw = readFileSync16(path14, "utf8");
13699
+ raw = readFileSync17(path15, "utf8");
13516
13700
  } catch {
13517
13701
  return null;
13518
13702
  }
13519
- const parsed = parseManifest2(raw, path14);
13703
+ const parsed = parseManifest2(raw, path15);
13520
13704
  if (parsed.kind !== "ok")
13521
13705
  return null;
13522
13706
  return summarise(parsed.manifest);
@@ -13668,7 +13852,7 @@ function parseArgs12(args) {
13668
13852
  }
13669
13853
 
13670
13854
  // src/secrets/push.ts
13671
- import { existsSync as existsSync12, readFileSync as readFileSync17 } from "node:fs";
13855
+ import { existsSync as existsSync13, readFileSync as readFileSync18 } from "node:fs";
13672
13856
  import { resolve as resolvePath4 } from "node:path";
13673
13857
  import { createInterface as createInterface4 } from "node:readline/promises";
13674
13858
  import { stdin as input2, stdout as output2 } from "node:process";
@@ -13729,14 +13913,14 @@ async function runSecretsPush(opts, io, deps = {}) {
13729
13913
  return 0;
13730
13914
  }
13731
13915
  const envPath = resolvePath4(process.cwd(), opts.env ?? ".env");
13732
- if (!existsSync12(envPath)) {
13916
+ if (!existsSync13(envPath)) {
13733
13917
  io.err(`launchpad secrets push: ${envPath}`);
13734
13918
  io.err(" .env file not found. Run `launchpad secrets template` to scaffold one.");
13735
13919
  return 2;
13736
13920
  }
13737
13921
  let envText;
13738
13922
  try {
13739
- envText = readFileSync17(envPath, "utf8");
13923
+ envText = readFileSync18(envPath, "utf8");
13740
13924
  } catch (e) {
13741
13925
  io.err(`launchpad secrets push: failed to read ${envPath}: ${describe30(e)}`);
13742
13926
  return 2;
@@ -14079,7 +14263,7 @@ function renderManifestError5(result, io) {
14079
14263
  }
14080
14264
 
14081
14265
  // src/secrets/set.ts
14082
- import { existsSync as existsSync13, readFileSync as readFileSync18 } from "node:fs";
14266
+ import { existsSync as existsSync14, readFileSync as readFileSync19 } from "node:fs";
14083
14267
  import { resolve as resolvePath6 } from "node:path";
14084
14268
  import { parse as parseYaml8 } from "yaml";
14085
14269
  var CELL_LABEL2 = {
@@ -14088,23 +14272,23 @@ var CELL_LABEL2 = {
14088
14272
  not_deployed: "NOT_DEPLOYED"
14089
14273
  };
14090
14274
  function loadSet(fleetFile, setName, io) {
14091
- const path14 = resolvePath6(process.cwd(), fleetFile ?? "fleet-secret-sets.yaml");
14092
- if (!existsSync13(path14)) {
14093
- io.err(`✗ ${path14}`);
14275
+ const path15 = resolvePath6(process.cwd(), fleetFile ?? "fleet-secret-sets.yaml");
14276
+ if (!existsSync14(path15)) {
14277
+ io.err(`✗ ${path15}`);
14094
14278
  io.err(" fleet-secret-sets.yaml not found. Run from the platform repo root or pass --fleet-file.");
14095
14279
  return 2;
14096
14280
  }
14097
14281
  let obj;
14098
14282
  try {
14099
- obj = parseYaml8(readFileSync18(path14, "utf8"));
14283
+ obj = parseYaml8(readFileSync19(path15, "utf8"));
14100
14284
  } catch (e) {
14101
- io.err(`✗ ${path14}`);
14285
+ io.err(`✗ ${path15}`);
14102
14286
  io.err(` YAML parse error: ${e instanceof Error ? e.message : String(e)}`);
14103
14287
  return 1;
14104
14288
  }
14105
14289
  const parsed = parseFleetSecretSets(obj);
14106
14290
  if (!parsed.ok) {
14107
- io.err(`✗ ${path14}`);
14291
+ io.err(`✗ ${path15}`);
14108
14292
  io.err(` ${parsed.issues.length} schema issue(s):`);
14109
14293
  for (const i of parsed.issues)
14110
14294
  io.err(` ${i.path}: ${i.message}`);
@@ -14113,7 +14297,7 @@ function loadSet(fleetFile, setName, io) {
14113
14297
  const set = parsed.manifest.secretSets.find((s) => s.name === setName);
14114
14298
  if (set === undefined) {
14115
14299
  const names = parsed.manifest.secretSets.map((s) => s.name).join(", ");
14116
- io.err(`✗ secret-set "${setName}" not found in ${path14}`);
14300
+ io.err(`✗ secret-set "${setName}" not found in ${path15}`);
14117
14301
  io.err(` declared sets: ${names}`);
14118
14302
  return 1;
14119
14303
  }
@@ -14181,14 +14365,14 @@ async function runSecretsPushSet(opts, io, deps = {}) {
14181
14365
  if (typeof set === "number")
14182
14366
  return set;
14183
14367
  const envPath = resolvePath6(process.cwd(), opts.env ?? ".env");
14184
- if (!existsSync13(envPath)) {
14368
+ if (!existsSync14(envPath)) {
14185
14369
  io.err(`launchpad secrets push --set: ${envPath} not found.`);
14186
14370
  io.err(` Create a .env carrying the set's secrets: ${set.secrets.join(", ")}`);
14187
14371
  return 2;
14188
14372
  }
14189
14373
  let envText;
14190
14374
  try {
14191
- envText = readFileSync18(envPath, "utf8");
14375
+ envText = readFileSync19(envPath, "utf8");
14192
14376
  } catch (e) {
14193
14377
  io.err(`launchpad secrets push --set: failed to read ${envPath}: ${e instanceof Error ? e.message : String(e)}`);
14194
14378
  return 2;
@@ -14332,7 +14516,7 @@ function setPushExit(e) {
14332
14516
  }
14333
14517
 
14334
14518
  // src/commands/secrets-template.ts
14335
- import { existsSync as existsSync14, writeFileSync as writeFileSync6 } from "node:fs";
14519
+ import { existsSync as existsSync15, writeFileSync as writeFileSync7 } from "node:fs";
14336
14520
  import { resolve as resolve14 } from "node:path";
14337
14521
  async function runSecretsTemplate(args, io) {
14338
14522
  const flags = parseFlags4(args);
@@ -14356,13 +14540,13 @@ async function runSecretsTemplate(args, io) {
14356
14540
  return 0;
14357
14541
  }
14358
14542
  const outPath = resolve14(process.cwd(), flags.out);
14359
- if (existsSync14(outPath) && !flags.force) {
14543
+ if (existsSync15(outPath) && !flags.force) {
14360
14544
  io.err(`launchpad secrets template: ${outPath} already exists`);
14361
14545
  io.err("Pass --force to overwrite, or --stdout to print without writing.");
14362
14546
  return 64;
14363
14547
  }
14364
14548
  try {
14365
- writeFileSync6(outPath, body, { encoding: "utf8" });
14549
+ writeFileSync7(outPath, body, { encoding: "utf8" });
14366
14550
  } catch (err) {
14367
14551
  const e = err;
14368
14552
  io.err(`launchpad secrets template: failed to write ${outPath}`);
@@ -14653,12 +14837,12 @@ function printHelp3(io) {
14653
14837
  // src/commands/skills.ts
14654
14838
  import { fileURLToPath } from "node:url";
14655
14839
  import { dirname as dirname10, join as join16, resolve as resolve15 } from "node:path";
14656
- import { promises as fs7, existsSync as existsSync15 } from "node:fs";
14840
+ import { promises as fs7, existsSync as existsSync16 } from "node:fs";
14657
14841
 
14658
14842
  // src/skills-bundle.ts
14659
14843
  import { homedir as homedir2 } from "node:os";
14660
14844
  import { join as join15 } from "node:path";
14661
- import { readFileSync as readFileSync19, readdirSync as readdirSync2 } from "node:fs";
14845
+ import { readFileSync as readFileSync20, readdirSync as readdirSync2 } from "node:fs";
14662
14846
  var SKILL_PREFIX = "launchpad-";
14663
14847
  function skillsTargetDir() {
14664
14848
  return process.env.LAUNCHPAD_SKILLS_TARGET_DIR ?? join15(homedir2(), ".claude", "skills");
@@ -14677,9 +14861,9 @@ function readInstalledSkills() {
14677
14861
  return { present: false, version: null };
14678
14862
  }
14679
14863
  }
14680
- function readSkillVersion(path14) {
14864
+ function readSkillVersion(path15) {
14681
14865
  try {
14682
- const text = readFileSync19(path14, "utf8");
14866
+ const text = readFileSync20(path15, "utf8");
14683
14867
  const fenceEnd = text.indexOf(`
14684
14868
  ---`, 4);
14685
14869
  const front = fenceEnd === -1 ? text.slice(0, 1024) : text.slice(0, fenceEnd);
@@ -14762,7 +14946,7 @@ function defaultBundleDir() {
14762
14946
  resolve15(here, "..", "..", "skills")
14763
14947
  ];
14764
14948
  for (const c of candidates) {
14765
- if (existsSync15(join16(c, "launchpad-onboard", "SKILL.md"))) {
14949
+ if (existsSync16(join16(c, "launchpad-onboard", "SKILL.md"))) {
14766
14950
  return c;
14767
14951
  }
14768
14952
  }
@@ -14842,10 +15026,10 @@ async function doList(io) {
14842
15026
  }
14843
15027
  return 0;
14844
15028
  }
14845
- async function readVersion(path14) {
15029
+ async function readVersion(path15) {
14846
15030
  let text;
14847
15031
  try {
14848
- text = await fs7.readFile(path14, "utf8");
15032
+ text = await fs7.readFile(path15, "utf8");
14849
15033
  } catch {
14850
15034
  return null;
14851
15035
  }
@@ -14855,9 +15039,9 @@ async function readVersion(path14) {
14855
15039
  const m = /^version:\s*(.+?)\s*$/m.exec(front);
14856
15040
  return m === null ? null : m[1] ?? null;
14857
15041
  }
14858
- async function isDir(path14) {
15042
+ async function isDir(path15) {
14859
15043
  try {
14860
- const stat = await fs7.stat(path14);
15044
+ const stat = await fs7.stat(path15);
14861
15045
  return stat.isDirectory();
14862
15046
  } catch {
14863
15047
  return false;
@@ -14873,7 +15057,7 @@ import { promisify } from "node:util";
14873
15057
  import { fileURLToPath as fileURLToPath2 } from "node:url";
14874
15058
  import { dirname as dirname11, resolve as resolve16, relative as relative6, isAbsolute as isAbsolute3, join as join17 } from "node:path";
14875
15059
  import { homedir as homedir3, tmpdir } from "node:os";
14876
- import { readFileSync as readFileSync20, mkdtempSync, writeFileSync as writeFileSync7, rmSync as rmSync2 } from "node:fs";
15060
+ import { readFileSync as readFileSync21, mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync8, rmSync as rmSync3 } from "node:fs";
14877
15061
 
14878
15062
  // src/commands/channel-auth.ts
14879
15063
  import { createServer as createServer2 } from "node:http";
@@ -15123,17 +15307,17 @@ async function openSystemBrowser(url) {
15123
15307
  await execFileAsync(opener, [url]);
15124
15308
  }
15125
15309
  async function runInstallerScript(script) {
15126
- const dir = mkdtempSync(join17(tmpdir(), "launchpad-update-"));
15310
+ const dir = mkdtempSync2(join17(tmpdir(), "launchpad-update-"));
15127
15311
  const file = join17(dir, "install.sh");
15128
15312
  try {
15129
- writeFileSync7(file, script, { mode: 448 });
15313
+ writeFileSync8(file, script, { mode: 448 });
15130
15314
  return await new Promise((resolvePromise) => {
15131
15315
  const child = spawn5("bash", [file], { stdio: "inherit" });
15132
15316
  child.on("error", () => resolvePromise(127));
15133
15317
  child.on("close", (c) => resolvePromise(c ?? 1));
15134
15318
  });
15135
15319
  } finally {
15136
- rmSync2(dir, { recursive: true, force: true });
15320
+ rmSync3(dir, { recursive: true, force: true });
15137
15321
  }
15138
15322
  }
15139
15323
  async function runChannelInstaller() {
@@ -15148,7 +15332,7 @@ function resolveLatestVersion() {
15148
15332
  }
15149
15333
  function detectInstallChannel() {
15150
15334
  try {
15151
- return readFileSync20(CHANNEL_MARKER, "utf8").trim() === "platform" ? "platform" : "github";
15335
+ return readFileSync21(CHANNEL_MARKER, "utf8").trim() === "platform" ? "platform" : "github";
15152
15336
  } catch {
15153
15337
  return "github";
15154
15338
  }
@@ -15356,7 +15540,7 @@ function printHelp5(io) {
15356
15540
  }
15357
15541
 
15358
15542
  // src/commands/validate.ts
15359
- import { existsSync as existsSync16, readFileSync as readFileSync21, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
15543
+ import { existsSync as existsSync17, readFileSync as readFileSync22, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
15360
15544
  import { dirname as dirname12, resolve as resolve17 } from "node:path";
15361
15545
  var validateCommand = {
15362
15546
  name: "validate",
@@ -15371,14 +15555,14 @@ async function runValidate(args, io) {
15371
15555
  return 64;
15372
15556
  }
15373
15557
  const { file, json, strictGroups } = parseResult;
15374
- const path14 = resolve17(process.cwd(), file ?? "launchpad.yaml");
15375
- const result = loadManifestAnyVersion(path14);
15558
+ const path15 = resolve17(process.cwd(), file ?? "launchpad.yaml");
15559
+ const result = loadManifestAnyVersion(path15);
15376
15560
  if (result.kind !== "ok") {
15377
15561
  return json ? renderJsonError(result, io) : renderHumanError(result, io);
15378
15562
  }
15379
15563
  const unsupported2 = rejectUnsupportedCliRuntime({
15380
15564
  command: "validate",
15381
- manifestPath: path14,
15565
+ manifestPath: path15,
15382
15566
  manifest: result.manifest,
15383
15567
  io,
15384
15568
  json,
@@ -15387,8 +15571,8 @@ async function runValidate(args, io) {
15387
15571
  if (unsupported2 !== null)
15388
15572
  return unsupported2;
15389
15573
  const view = manifestView(result.manifest);
15390
- const boundary = checkBoundary(path14, view.appDeclared);
15391
- const serveErrors = checkStaticServeDir(path14, result.manifest);
15574
+ const boundary = checkBoundary(path15, view.appDeclared);
15575
+ const serveErrors = checkStaticServeDir(path15, result.manifest);
15392
15576
  if (serveErrors.length > 0) {
15393
15577
  boundary.errors = [...boundary.errors, ...serveErrors];
15394
15578
  }
@@ -15405,7 +15589,7 @@ function checkBoundary(manifestPath, declared) {
15405
15589
  }
15406
15590
  let manifestYaml;
15407
15591
  try {
15408
- manifestYaml = readFileSync21(manifestPath, "utf8");
15592
+ manifestYaml = readFileSync22(manifestPath, "utf8");
15409
15593
  } catch {
15410
15594
  manifestYaml = null;
15411
15595
  }
@@ -15431,7 +15615,7 @@ function checkStaticServeDir(manifestPath, manifest) {
15431
15615
  }
15432
15616
  const dir = dirname12(manifestPath);
15433
15617
  const abs = resolve17(dir, serveDir);
15434
- if (!existsSync16(abs)) {
15618
+ if (!existsSync17(abs)) {
15435
15619
  return [
15436
15620
  `static app serves '${serveDir}/' but that directory does not exist — nothing would be served. ` + `Create ${serveDir}/index.html (or run \`launchpad init\` to scaffold it).`
15437
15621
  ];
@@ -15456,7 +15640,7 @@ function checkStaticServeDir(manifestPath, manifest) {
15456
15640
  `static app's served directory '${serveDir}/' is empty — the deploy would serve an empty site.`
15457
15641
  ];
15458
15642
  }
15459
- if (!existsSync16(resolve17(abs, "index.html"))) {
15643
+ if (!existsSync17(resolve17(abs, "index.html"))) {
15460
15644
  return [
15461
15645
  `static app's served directory '${serveDir}/' has no index.html — the deploy would serve a directory with no entrypoint.`
15462
15646
  ];
@@ -16433,10 +16617,10 @@ import { spawn as spawn6 } from "node:child_process";
16433
16617
  import { homedir as homedir4 } from "node:os";
16434
16618
  import { join as join18 } from "node:path";
16435
16619
  import {
16436
- existsSync as existsSync17,
16437
- mkdirSync as mkdirSync4,
16438
- readFileSync as readFileSync22,
16439
- writeFileSync as writeFileSync8
16620
+ existsSync as existsSync18,
16621
+ mkdirSync as mkdirSync5,
16622
+ readFileSync as readFileSync23,
16623
+ writeFileSync as writeFileSync9
16440
16624
  } from "node:fs";
16441
16625
  var INTERNAL_REFRESH_VERB = "__refresh-update-cache";
16442
16626
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
@@ -16444,7 +16628,7 @@ var OPT_OUT_ENV = "LAUNCHPAD_NO_UPDATE_NOTIFIER";
16444
16628
  var CACHE_FILE = join18(homedir4(), ".launchpad", "update-check.json");
16445
16629
  function readCache2() {
16446
16630
  try {
16447
- const raw = JSON.parse(readFileSync22(CACHE_FILE, "utf8"));
16631
+ const raw = JSON.parse(readFileSync23(CACHE_FILE, "utf8"));
16448
16632
  if (typeof raw === "object" && raw !== null && typeof raw.checkedAt === "number") {
16449
16633
  const latest = raw.latest;
16450
16634
  return {
@@ -16457,23 +16641,23 @@ function readCache2() {
16457
16641
  }
16458
16642
  function writeCache2(state) {
16459
16643
  try {
16460
- mkdirSync4(join18(homedir4(), ".launchpad"), { recursive: true });
16461
- writeFileSync8(CACHE_FILE, `${JSON.stringify(state)}
16644
+ mkdirSync5(join18(homedir4(), ".launchpad"), { recursive: true });
16645
+ writeFileSync9(CACHE_FILE, `${JSON.stringify(state)}
16462
16646
  `, { mode: 384 });
16463
16647
  } catch {}
16464
16648
  }
16465
16649
  var SKILLS_HINT_MARKER = join18(homedir4(), ".launchpad", "skills-hint-shown");
16466
16650
  function absentHintShown() {
16467
16651
  try {
16468
- return existsSync17(SKILLS_HINT_MARKER);
16652
+ return existsSync18(SKILLS_HINT_MARKER);
16469
16653
  } catch {
16470
16654
  return false;
16471
16655
  }
16472
16656
  }
16473
16657
  function markAbsentHintShown() {
16474
16658
  try {
16475
- mkdirSync4(join18(homedir4(), ".launchpad"), { recursive: true });
16476
- writeFileSync8(SKILLS_HINT_MARKER, `${Date.now()}
16659
+ mkdirSync5(join18(homedir4(), ".launchpad"), { recursive: true });
16660
+ writeFileSync9(SKILLS_HINT_MARKER, `${Date.now()}
16477
16661
  `, { mode: 384 });
16478
16662
  } catch {}
16479
16663
  }
@@ -16576,7 +16760,7 @@ var refreshUpdateCacheCommand = {
16576
16760
  import { spawn as spawn7 } from "node:child_process";
16577
16761
  import { homedir as homedir5 } from "node:os";
16578
16762
  import { join as join19 } from "node:path";
16579
- import { existsSync as existsSync18, mkdirSync as mkdirSync5, readFileSync as readFileSync23, writeFileSync as writeFileSync9 } from "node:fs";
16763
+ import { existsSync as existsSync19, mkdirSync as mkdirSync6, readFileSync as readFileSync24, writeFileSync as writeFileSync10 } from "node:fs";
16580
16764
  import { randomUUID } from "node:crypto";
16581
16765
  var INTERNAL_EMIT_VERB = "__emit-telemetry";
16582
16766
  var POSTHOG_HOST = "https://us.i.posthog.com";
@@ -16652,15 +16836,15 @@ async function resolveIdentityReal() {
16652
16836
  }
16653
16837
  function getOrCreateDeviceId() {
16654
16838
  try {
16655
- const raw = JSON.parse(readFileSync23(DEVICE_ID_FILE, "utf8"));
16839
+ const raw = JSON.parse(readFileSync24(DEVICE_ID_FILE, "utf8"));
16656
16840
  const id2 = raw?.deviceId;
16657
16841
  if (typeof id2 === "string" && id2)
16658
16842
  return id2;
16659
16843
  } catch {}
16660
16844
  const id = randomUUID();
16661
16845
  try {
16662
- mkdirSync5(join19(homedir5(), ".launchpad"), { recursive: true });
16663
- writeFileSync9(DEVICE_ID_FILE, `${JSON.stringify({ deviceId: id })}
16846
+ mkdirSync6(join19(homedir5(), ".launchpad"), { recursive: true });
16847
+ writeFileSync10(DEVICE_ID_FILE, `${JSON.stringify({ deviceId: id })}
16664
16848
  `, {
16665
16849
  mode: 384
16666
16850
  });
@@ -16730,15 +16914,15 @@ function telemetryNoticeLine() {
16730
16914
  }
16731
16915
  function firstRunNoticeShown() {
16732
16916
  try {
16733
- return existsSync18(FIRST_RUN_MARKER);
16917
+ return existsSync19(FIRST_RUN_MARKER);
16734
16918
  } catch {
16735
16919
  return false;
16736
16920
  }
16737
16921
  }
16738
16922
  function markFirstRunNotice() {
16739
16923
  try {
16740
- mkdirSync5(join19(homedir5(), ".launchpad"), { recursive: true });
16741
- writeFileSync9(FIRST_RUN_MARKER, `${Date.now()}
16924
+ mkdirSync6(join19(homedir5(), ".launchpad"), { recursive: true });
16925
+ writeFileSync10(FIRST_RUN_MARKER, `${Date.now()}
16742
16926
  `, { mode: 384 });
16743
16927
  } catch {}
16744
16928
  }
@@ -16803,7 +16987,7 @@ import { createInterface as createInterface7 } from "node:readline/promises";
16803
16987
  import { platform } from "node:os";
16804
16988
 
16805
16989
  // src/report/breadcrumb.ts
16806
- import { writeFileSync as writeFileSync10, readFileSync as readFileSync24, mkdirSync as mkdirSync6 } from "node:fs";
16990
+ import { writeFileSync as writeFileSync11, readFileSync as readFileSync25, mkdirSync as mkdirSync7 } from "node:fs";
16807
16991
  import { homedir as homedir6 } from "node:os";
16808
16992
  import { join as join20, dirname as dirname13 } from "node:path";
16809
16993
  function breadcrumbPath() {
@@ -16812,13 +16996,13 @@ function breadcrumbPath() {
16812
16996
  function writeBreadcrumb(verb, exit) {
16813
16997
  try {
16814
16998
  const p = breadcrumbPath();
16815
- mkdirSync6(dirname13(p), { recursive: true });
16816
- writeFileSync10(p, JSON.stringify({ verb, exit, ts: Date.now() }), "utf8");
16999
+ mkdirSync7(dirname13(p), { recursive: true });
17000
+ writeFileSync11(p, JSON.stringify({ verb, exit, ts: Date.now() }), "utf8");
16817
17001
  } catch {}
16818
17002
  }
16819
17003
  function readBreadcrumb() {
16820
17004
  try {
16821
- const raw = JSON.parse(readFileSync24(breadcrumbPath(), "utf8"));
17005
+ const raw = JSON.parse(readFileSync25(breadcrumbPath(), "utf8"));
16822
17006
  if (typeof raw !== "object" || raw === null)
16823
17007
  return null;
16824
17008
  const r = raw;
@@ -16987,6 +17171,7 @@ var COMMANDS = [
16987
17171
  createCommand,
16988
17172
  cloneCommand,
16989
17173
  deployCommand,
17174
+ migrateCommand,
16990
17175
  redeployCommand,
16991
17176
  reviewCommand,
16992
17177
  mergeCommand,
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AA0EA,OAAO,EAGL,4BAA4B,EAC7B,MAAM,uBAAuB,CAAC;AAY/B,OAAO,EAAa,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,KAAK,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAQjE,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;AAEjD,eAAO,MAAM,aAAa,EAAE,OAI3B,CAAC;AAEF,UAAU,UAAU;IAClB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AA6RD;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAoBpC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,GAAG,IAAI,CA4BpE;AAiMD,2BAA2B;AAC3B,OAAO,EAAE,4BAA4B,EAAE,CAAC;AAstBxC,2EAA2E;AAC3E;+EAC+E;AAC/E,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;AAE9D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,cAAc,CAG/E;AAED;;;4EAG4E;AAC5E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;CACrD,GAAG,OAAO,CAIV;AAED,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,OAAO,EACvB,UAAU,GAAE,OAAO,GAAG,SAAqB,GAC1C,MAAM,CAOR"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AA0EA,OAAO,EAGL,4BAA4B,EAC7B,MAAM,uBAAuB,CAAC;AAY/B,OAAO,EAAa,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,KAAK,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAQjE,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;AAEjD,eAAO,MAAM,aAAa,EAAE,OAI3B,CAAC;AAEF,UAAU,UAAU;IAClB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AA6RD;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAoBpC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,GAAG,IAAI,CA4BpE;AAiMD,2BAA2B;AAC3B,OAAO,EAAE,4BAA4B,EAAE,CAAC;AA4tBxC,2EAA2E;AAC3E;+EAC+E;AAC/E,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;AAE9D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,cAAc,CAG/E;AAED;;;4EAG4E;AAC5E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;CACrD,GAAG,OAAO,CAIV;AAED,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,OAAO,EACvB,UAAU,GAAE,OAAO,GAAG,SAAqB,GAC1C,MAAM,CAOR"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from "../dispatcher.js";
2
+ export declare const migrateCommand: Command;
3
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAS,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAIvD,eAAO,MAAM,cAAc,EAAE,OAI5B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"served-verify.d.ts","sourceRoot":"","sources":["../../src/deploy/served-verify.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,GAAG,WAAW,CAAC;AAE3E,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE;AAC5B;;;;;;;;GAQG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAC9B;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAExF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,iBAAiB,EAAE,UAAU,GAAG,MAAM,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;CACjC;AAUD;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,UAAU,EAChB,IAAI,GAAE,UAAsB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAgDxB"}
1
+ {"version":3,"file":"served-verify.d.ts","sourceRoot":"","sources":["../../src/deploy/served-verify.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,GAAG,WAAW,CAAC;AAE3E,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE;AAC5B;;;;;;;;GAQG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAC9B;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAExF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,iBAAiB,EAAE,UAAU,GAAG,MAAM,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;CACjC;AAUD;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,UAAU,EAChB,IAAI,GAAE,UAAsB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAmDxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../src/dispatcher.ts"],"names":[],"mappings":"AA0DA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAOzD,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,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,EAAE,SAAS,OAAO,EA0CtC,CAAC;AAEF;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,KAAK,EACT,QAAQ,GAAE,SAAS,OAAO,EAAa,EACvC,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,QAAQ,CAAC,CA4BnB;AAuDD,wBAAgB,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,CAyBvE"}
1
+ {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../src/dispatcher.ts"],"names":[],"mappings":"AA2DA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAOzD,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,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,EAAE,SAAS,OAAO,EA2CtC,CAAC;AAEF;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,KAAK,EACT,QAAQ,GAAE,SAAS,OAAO,EAAa,EACvC,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,QAAQ,CAAC,CA4BnB;AAuDD,wBAAgB,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,CAyBvE"}
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/http/api-client.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAgB9C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAC;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD;;;;;;;OAOG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClD;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAC7B,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,CAAC,CAAC,CAWZ;AAED;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAC1B,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,QAAQ,CAAC,CA6FnB"}
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/http/api-client.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAgB9C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAC;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD;;;;;;;OAOG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClD;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAC7B,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,CAAC,CAAC,CAWZ;AAED;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAC1B,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,QAAQ,CAAC,CA8FnB"}
@@ -13,7 +13,10 @@ export declare class ForbiddenError extends Error {
13
13
  /** 404 — the bot returned not-found. Distinct from UnauthenticatedError
14
14
  * so the CLI doesn't suggest re-login when the slug is just unknown. */
15
15
  export declare class NotFoundError extends Error {
16
+ readonly serverCode?: string | undefined;
16
17
  readonly code: "not_found";
18
+ /** Retain the server's code so registry misses differ from unknown routes. */
19
+ constructor(message: string, serverCode?: string | undefined);
17
20
  }
18
21
  /** Stable process exit used only for a server-enforced CLI upgrade. */
19
22
  export declare const EXIT_CLI_UPGRADE_REQUIRED = 42;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/http/errors.ts"],"names":[],"mappings":"AAIA;;2DAE2D;AAC3D,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAG,iBAAiB,CAAU;CAC5C;AAED;;uCAEuC;AACvC,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAG,WAAW,CAAU;CACtC;AAED;yEACyE;AACzE,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,IAAI,EAAG,WAAW,CAAU;CACtC;AAED,uEAAuE;AACvE,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,gFAAgF;AAChF,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAG,sBAAsB,CAAU;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAM3C;AAED,2EAA2E;AAC3E,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,qBAAqB,CAAC;AAE7B,2EAA2E;AAC3E,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,SAAS,EAAG,WAAW,CAAU;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IACnD,QAAQ,CAAC,UAAU,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;IAC3D,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAC;gBAE/B,QAAQ,EAAE,uBAAuB;CAgB9C;AAED;2BAC2B;AAC3B,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAG,iBAAiB,CAAU;CAC5C"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/http/errors.ts"],"names":[],"mappings":"AAIA;;2DAE2D;AAC3D,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAG,iBAAiB,CAAU;CAC5C;AAED;;uCAEuC;AACvC,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAG,WAAW,CAAU;CACtC;AAED;yEACyE;AACzE,qBAAa,aAAc,SAAQ,KAAK;IAIT,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM;IAHzD,QAAQ,CAAC,IAAI,EAAG,WAAW,CAAU;IAErC,8EAA8E;gBAClE,OAAO,EAAE,MAAM,EAAW,UAAU,CAAC,EAAE,MAAM,YAAA;CAG1D;AAED,uEAAuE;AACvE,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,gFAAgF;AAChF,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAG,sBAAsB,CAAU;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAM3C;AAED,2EAA2E;AAC3E,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,qBAAqB,CAAC;AAE7B,2EAA2E;AAC3E,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,SAAS,EAAG,WAAW,CAAU;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IACnD,QAAQ,CAAC,UAAU,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;IAC3D,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAC;gBAE/B,QAAQ,EAAE,uBAAuB;CAgB9C;AAED;2BAC2B;AAC3B,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAG,iBAAiB,CAAU;CAC5C"}
@@ -0,0 +1,4 @@
1
+ import { type DeploymentManifest, type ManifestV2 } from "@m-kopa/launchpad-engine";
2
+ /** Convert only the static subset whose deployment contract dispatch preserves. */
3
+ export declare function dispatchManifest(manifest: DeploymentManifest): ManifestV2;
4
+ //# sourceMappingURL=migrate-dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-dispatch.d.ts","sourceRoot":"","sources":["../../src/manifest/migrate-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,kBAAkB,EAEvB,KAAK,UAAU,EAChB,MAAM,0BAA0B,CAAC;AAElC,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,UAAU,CAkDzE"}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.61.0";
1
+ export declare const CLI_VERSION = "0.62.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m-kopa/launchpad-cli",
3
- "version": "0.61.0",
3
+ "version": "0.62.0",
4
4
  "description": "Launchpad CLI — clone / deploy / review / merge against Launchpad-managed apps. Talks to the portal-bot endpoints (SCOPE-M-760 / T4).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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.61.0
4
+ version: 0.62.0
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.61.0
4
+ version: 0.62.0
5
5
  ---
6
6
 
7
7
  <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
@@ -151,6 +151,14 @@ Gateway placement is automatic for new gateway-fronted apps, including Pages API
151
151
  The platform selects the configured gateway with the most free hostname slots, counting pending deployments as reservations.
152
152
  Do not ask the author to choose a gateway or add gateway project names to the manifest.
153
153
  Existing apps and retries retain their assigned gateway and public URL.
154
+ Updating the CLI does not rewrite an existing Pages manifest.
155
+ If an unprovisioned static or React app hits the Pages project limit, run `launchpad update`, then `launchpad migrate --origin dispatch --dry-run`.
156
+ When eligible, run `launchpad migrate --origin dispatch`, review the preserved audience and build settings, then run `launchpad deploy`.
157
+ The command checks the live registry and saves the original YAML under `.launchpad/manifest-before-dispatch.yaml`.
158
+ Do not regenerate the manifest with `init`, broaden its groups, or change private ownership to work around the quota.
159
+ If migration refuses functions, custom runtime configuration or bindings, stop and explain the named limitation.
160
+ Pages Functions apps need a compiled Worker build; changing `appType` to `react` would not convert their API.
161
+ Do not use this command to migrate a registered app or delete its Pages resources.
154
162
  If the pool is full or inventory cannot be read, report the capacity error; do not fall back to Pages or change authentication to bypass it.
155
163
  Do not replace that manifest with a v1 example or add `--origin pages` just because an older app uses Pages.
156
164
  Use `--origin pages` only when Pages is deliberately required.
@@ -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), or watch provisioning live with `launchpad watch`. 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", "watch my deploy go live", "watch provisioning", "my app says failed but it's serving", or after `/launchpad-deploy` reports a non-`done` terminal stage.
4
- version: 0.61.0
4
+ version: 0.62.0
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.61.0
4
+ version: 0.62.0
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-identity
3
3
  description: Teach an app author how to use the signed-in user's identity inside a Launchpad app — read the gateway-forwarded X-Launchpad-User-Assertion in a Pages Function, VERIFY it with @m-kopa/platform-auth (fail-closed), and show who's logged in (sub/email/name). Use when someone says "who is logged in", "show the current user", "get the user's email in my app", "auth in my launchpad app", "read the user identity", "/launchpad-identity", or is wiring up an /api/me for a gateway-fronted app.
4
- version: 0.61.0
4
+ version: 0.62.0
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.61.0
4
+ version: 0.62.0
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-report
3
3
  description: File a bug report or feature request to the Launchpad team's tracker from the CLI. Use when someone reports something broken, hits an error in a launchpad command, or wishes a feature existed — e.g. "this is broken", "report a bug", "can you file that", "I wish launchpad could…", "/launchpad-bug", "/launchpad-feature". Always confirm and show exactly what you'll send before filing; never file silently.
4
- version: 0.61.0
4
+ version: 0.62.0
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: Safely refresh a Launchpad clone, show manifest drift, or read the deployed manifest. Wraps bare `launchpad pull` for source sync, `launchpad pull --manifest` for deployed YAML, and `launchpad status` for drift. Use for "sync my clone", "what's deployed", "show drift", "/launchpad-status", "/launchpad-pull", or post-deploy verification.
4
- version: 0.61.0
4
+ version: 0.62.0
5
5
  ---
6
6
 
7
7
  <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->