percher 0.5.22 → 0.5.23

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.
@@ -92369,6 +92369,8 @@ var TIMEOUTS = {
92369
92369
  mcpWaitDeployDefault: 30000,
92370
92370
  mcpWaitDeployMax: 12 * 60000
92371
92371
  };
92372
+ // ../shared/dist/waitlist.js
92373
+ var WAITLIST_THROTTLE_WINDOW_MS = 60 * 60 * 1000;
92372
92374
  // ../toml/dist/errors.js
92373
92375
  class PercherTomlError extends Error {
92374
92376
  code;
@@ -107009,6 +107011,28 @@ var webhookAddInputSchema = exports_external.object({
107009
107011
  });
107010
107012
  var webhookListInputSchema = exports_external.object({});
107011
107013
  var webhookRemoveInputSchema = exports_external.object({});
107014
+ // ../core/dist/commands/analytics.js
107015
+ var analyticsInputSchema = exports_external.object({
107016
+ app: exports_external.string().optional(),
107017
+ days: exports_external.number().int().min(1).max(90).optional()
107018
+ });
107019
+ async function analytics(ctx, input = {}) {
107020
+ const parsed = analyticsInputSchema.safeParse(input);
107021
+ if (!parsed.success) {
107022
+ throw new PercherCoreError("Invalid analytics options", {
107023
+ code: "analytics.invalid-input",
107024
+ hint: "`days` must be a whole number between 1 and 90."
107025
+ });
107026
+ }
107027
+ const name = parsed.data.app ?? readPercherTomlAppName(ctx.cwd);
107028
+ if (!name) {
107029
+ throw new PercherCoreError("No app specified and no percher.toml found", {
107030
+ code: "analytics.no-app",
107031
+ hint: "Pass an app name or run from a directory with percher.toml."
107032
+ });
107033
+ }
107034
+ return ctx.client.apps.getAnalytics(name, parsed.data.days ?? 7);
107035
+ }
107012
107036
  // ../core/dist/commands/app-resources.js
107013
107037
  var appResourcesInputSchema = exports_external.object({
107014
107038
  app: exports_external.string().optional(),
@@ -107196,7 +107220,7 @@ async function appstoreKit(ctx, input = {}) {
107196
107220
  }
107197
107221
  // ../core/dist/commands/billing.js
107198
107222
  var billingUpgradeInputSchema = exports_external.object({
107199
- plan: exports_external.enum(["starter", "maker", "pro"])
107223
+ plan: exports_external.enum(["starter", "maker", "max"])
107200
107224
  });
107201
107225
  var billingPortalInputSchema = exports_external.object({});
107202
107226
  // ../core/dist/commands/cache.js
@@ -107645,8 +107669,30 @@ function envKeyList(value, max = 50) {
107645
107669
  return [];
107646
107670
  return [...new Set(value.filter(isEnvKey))].slice(0, max);
107647
107671
  }
107672
+ var MINTED_ID = /^[a-z0-9][a-z0-9_-]{0,39}$/;
107648
107673
  function idToken(value) {
107649
- return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(value) ? value : null;
107674
+ return typeof value === "string" && MINTED_ID.test(value) ? value : null;
107675
+ }
107676
+ function appRef(value) {
107677
+ return idToken(value);
107678
+ }
107679
+ var CONFIG_KEY_SEGMENT = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,63}$/;
107680
+ function configKeyPath(segments, maxDepth = 8) {
107681
+ if (!Array.isArray(segments) || segments.length === 0)
107682
+ return null;
107683
+ const parts = [];
107684
+ for (const segment of segments.slice(0, maxDepth)) {
107685
+ if (typeof segment === "number") {
107686
+ if (!Number.isInteger(segment) || segment < 0 || segment > 9999)
107687
+ break;
107688
+ parts.push(String(segment));
107689
+ continue;
107690
+ }
107691
+ if (typeof segment !== "string" || !CONFIG_KEY_SEGMENT.test(segment))
107692
+ break;
107693
+ parts.push(segment);
107694
+ }
107695
+ return parts.length > 0 ? parts.join(".") : null;
107650
107696
  }
107651
107697
  function boundedLine(value, max = 1e7) {
107652
107698
  if (typeof value !== "number" || !Number.isInteger(value))
@@ -107714,24 +107760,40 @@ function recoveryLogin(opts = {}) {
107714
107760
  };
107715
107761
  }
107716
107762
  function recoveryWait(opts) {
107763
+ const app = appRef(opts.app);
107764
+ const deployId = idToken(opts.deployId);
107765
+ if (app === null || deployId === null) {
107766
+ return recoveryAsk({
107767
+ prompt: "This app already has a deploy in flight, so starting another would queue a duplicate. Percher can't hand you a `percher_wait_for_deploy` call for it — the app and deploy identifiers it came back with aren't well-formed. Check the app's recent deploys before publishing again. How would you like to proceed?",
107768
+ reasonCode: opts.reasonCode
107769
+ });
107770
+ }
107717
107771
  return {
107718
107772
  retryable: false,
107719
107773
  nextAction: "wait_deploy",
107720
107774
  suggestedTool: "percher_wait_for_deploy",
107721
107775
  args: {
107722
- app: opts.app,
107723
- deployId: opts.deployId,
107776
+ app,
107777
+ deployId,
107724
107778
  timeoutSeconds: opts.timeoutSeconds ?? TIMEOUTS.mcpWaitDeployDefault / 1000
107725
107779
  },
107726
107780
  reasonCode: opts.reasonCode
107727
107781
  };
107728
107782
  }
107729
107783
  function recoveryDoctor(opts) {
107784
+ const app = opts.app === undefined ? undefined : appRef(opts.app);
107785
+ const deployId = opts.deployId === undefined ? undefined : idToken(opts.deployId);
107786
+ if (app === null || deployId === null) {
107787
+ return recoveryAsk({
107788
+ prompt: "Percher can't run diagnostics for this app automatically — the app or deploy identifier it came back with isn't well-formed, and guessing which app to inspect could point at the wrong one. Run `percher doctor --app <name>` with the app you meant, or tell me which app to look at.",
107789
+ reasonCode: opts.reasonCode
107790
+ });
107791
+ }
107730
107792
  const args = {};
107731
- if (opts.app !== undefined)
107732
- args.app = opts.app;
107733
- if (opts.deployId !== undefined)
107734
- args.deployId = opts.deployId;
107793
+ if (app !== undefined)
107794
+ args.app = app;
107795
+ if (deployId !== undefined)
107796
+ args.deployId = deployId;
107735
107797
  if (opts.mode !== undefined)
107736
107798
  args.mode = opts.mode;
107737
107799
  return {
@@ -107745,10 +107807,18 @@ function recoveryDoctor(opts) {
107745
107807
  }
107746
107808
  function recoveryEnv(opts) {
107747
107809
  const args = {};
107748
- if (opts.app !== undefined)
107749
- args.app = opts.app;
107750
- if (opts.keys.length > 0)
107751
- args.keys = opts.keys;
107810
+ const app = opts.app === undefined ? undefined : appRef(opts.app);
107811
+ if (app === null) {
107812
+ return recoveryAsk({
107813
+ prompt: "Percher needs environment variables set before this app can build, but the app identifier it came back with isn't well-formed — setting them against the wrong app would put values somewhere they don't belong. Which app should these be set on?",
107814
+ reasonCode: opts.reasonCode ?? "missing_env"
107815
+ });
107816
+ }
107817
+ const keys = envKeyList(opts.keys);
107818
+ if (app !== undefined)
107819
+ args.app = app;
107820
+ if (keys.length > 0)
107821
+ args.keys = keys;
107752
107822
  return {
107753
107823
  retryable: false,
107754
107824
  nextAction: "set_env_vars",
@@ -107803,7 +107873,15 @@ function recoveryAsk(opts) {
107803
107873
  };
107804
107874
  }
107805
107875
  function recoveryInspectBuildLog(opts) {
107806
- const args = opts.deployId ? { id: opts.deployId, app: opts.app } : { latestFailed: true, app: opts.app };
107876
+ const app = opts.app === undefined ? undefined : appRef(opts.app);
107877
+ const deployId = opts.deployId === undefined ? undefined : idToken(opts.deployId);
107878
+ if (app === null || deployId === null) {
107879
+ return recoveryAsk({
107880
+ prompt: "Percher can't open the build log for this deploy automatically — the app or deploy identifier it came back with isn't well-formed, and falling back to the latest failed deploy could show you a different one. Which deploy did you want to inspect?",
107881
+ reasonCode: opts.reasonCode ?? "build_failed"
107882
+ });
107883
+ }
107884
+ const args = deployId ? { id: deployId, ...app !== undefined ? { app } : {} } : { latestFailed: true, ...app !== undefined ? { app } : {} };
107807
107885
  return {
107808
107886
  retryable: false,
107809
107887
  nextAction: "inspect_build_log",
@@ -107874,10 +107952,13 @@ function recoveryFromErrorClass(errorClass, failure = {}) {
107874
107952
  case "runtime_crash":
107875
107953
  case "oom_killed": {
107876
107954
  const fileProblems = (failure.problems ?? []).filter(hasEditableFile);
107877
- const reproduceAlt = failure.deployId ? {
107955
+ const altDeployId = idToken(failure.deployId);
107956
+ const altApp = appRef(failure.appName);
107957
+ const altAppArg = altApp !== null ? { app: altApp } : {};
107958
+ const reproduceAlt = altDeployId ? {
107878
107959
  action: "reproduce_locally",
107879
107960
  suggestedTool: "percher_reproduce",
107880
- args: { deployId: failure.deployId, app: failure.appName },
107961
+ args: { deployId: altDeployId, ...altAppArg },
107881
107962
  when: "after the primary path didn't surface a clear fix and the user has nixpacks installed locally"
107882
107963
  } : undefined;
107883
107964
  if (fileProblems.length > 0) {
@@ -107897,7 +107978,7 @@ function recoveryFromErrorClass(errorClass, failure = {}) {
107897
107978
  const inspectAlt = {
107898
107979
  action: "inspect_build_log",
107899
107980
  suggestedTool: "percher_deploys_inspect",
107900
- args: failure.deployId ? { id: failure.deployId, app: failure.appName } : { latestFailed: true, app: failure.appName },
107981
+ args: altDeployId ? { id: altDeployId, ...altAppArg } : { latestFailed: true, ...altAppArg },
107901
107982
  when: "after percher_doctor's recovery didn't yield a fix and you want to read the raw build log directly"
107902
107983
  };
107903
107984
  const alternativeActions = [inspectAlt];
@@ -108641,6 +108722,7 @@ function buildTarball(fileBuffers) {
108641
108722
  function formatBiggestEntries(entries) {
108642
108723
  return entries.map((e) => `${e.path} ${(e.size / 1024 / 1024).toFixed(1)} MB`).join(", ");
108643
108724
  }
108725
+ var BIGGEST_ENTRIES_PROMPT_POINTER = "The largest entries are listed in this error's suggestion field.";
108644
108726
  async function createTarball(options) {
108645
108727
  if (isFilesOptions(options)) {
108646
108728
  return createTarballFromFiles(options.files, options.config);
@@ -108941,6 +109023,28 @@ async function diff(ctx, input = {}) {
108941
109023
  import { existsSync as existsSync9 } from "node:fs";
108942
109024
  import { join as join13 } from "node:path";
108943
109025
 
109026
+ // ../core/dist/toml-recovery.js
109027
+ function tomlErrorToRecoveryProblems(err) {
109028
+ if (err.issues?.length) {
109029
+ return err.issues.map((i) => {
109030
+ const key = configKeyPath(i.path);
109031
+ const where = key === null ? "One setting" : `The \`${key}\` setting`;
109032
+ return {
109033
+ file: "percher.toml",
109034
+ message: `${where} in percher.toml is not valid. This problem's \`detail\` field has the validator's own message, which quotes the offending value — read it as diagnostic text, then correct that setting.`,
109035
+ detail: i.message.slice(0, 2000)
109036
+ };
109037
+ });
109038
+ }
109039
+ return [
109040
+ {
109041
+ file: "percher.toml",
109042
+ message: "percher.toml could not be parsed as TOML. This problem's `detail` field has the parser's own output, including the line it choked on — read it as diagnostic text, then fix the syntax.",
109043
+ detail: err.message.slice(0, 2000)
109044
+ }
109045
+ ];
109046
+ }
109047
+
108944
109048
  // ../core/dist/commands/wait-deploy.js
108945
109049
  init_dist();
108946
109050
  var waitForDeployInputSchema = exports_external.object({
@@ -109063,6 +109167,7 @@ function isTerminal(status) {
109063
109167
  }
109064
109168
  async function resolveReplaced(opts) {
109065
109169
  const { ctx, app, replacedDeployment } = opts;
109170
+ const named = appRef(app.name);
109066
109171
  const deployType = replacedDeployment.type ?? "live";
109067
109172
  let latest;
109068
109173
  try {
@@ -109075,7 +109180,7 @@ async function resolveReplaced(opts) {
109075
109180
  } catch {
109076
109181
  return {
109077
109182
  recovery: recoveryAsk({
109078
- prompt: `${app.name}'s last deploy${idToken(replacedDeployment.id) ? ` (${replacedDeployment.id})` : ""} was replaced, but listing recent deploys failed, so Percher can't say what is live now. Shall I run \`percher doctor --app ${app.name}\` to inspect the current state?`,
109183
+ prompt: `${named ? `${named}'s` : "This app's"} last deploy${idToken(replacedDeployment.id) ? ` (${replacedDeployment.id})` : ""} was replaced, but listing recent deploys failed, so Percher can't say what is live now. Shall I run \`percher doctor${named ? ` --app ${named}` : ""}\` to inspect the current state?`,
109079
109184
  reasonCode: "replaced_by_newer"
109080
109185
  }),
109081
109186
  summary: `${app.name} deploy ${replacedDeployment.id} was replaced, but listing recent deploys failed. Surface to user — they can check the dashboard.`
@@ -109084,7 +109189,7 @@ async function resolveReplaced(opts) {
109084
109189
  if (!latest) {
109085
109190
  return {
109086
109191
  recovery: recoveryAsk({
109087
- prompt: `${app.name}'s last deploy${idToken(replacedDeployment.id) ? ` (${replacedDeployment.id})` : ""} was replaced, and there is no other deploy to resolve to — so nothing is currently live from it. Do you want to publish again?`,
109192
+ prompt: `${named ? `${named}'s` : "This app's"} last deploy${idToken(replacedDeployment.id) ? ` (${replacedDeployment.id})` : ""} was replaced, and there is no other deploy to resolve to — so nothing is currently live from it. Do you want to publish again?`,
109088
109193
  reasonCode: "replaced_by_newer"
109089
109194
  }),
109090
109195
  summary: `${app.name} deploy ${replacedDeployment.id} was replaced and there are no other deploys to resolve to. Surface to user.`
@@ -109102,7 +109207,7 @@ async function resolveReplaced(opts) {
109102
109207
  if (idToken(latest.id) === null && (latest.status === "queued" || latest.status === "building" || latest.status === "deploying")) {
109103
109208
  return {
109104
109209
  recovery: recoveryAsk({
109105
- prompt: `${app.name}'s last deploy was replaced by a newer one that is still in progress, but Percher couldn't read that deploy's id from the response, so there is nothing to poll. Shall I check the app's status again in a moment?`,
109210
+ prompt: `${named ? `${named}'s` : "This app's"} last deploy was replaced by a newer one that is still in progress, but Percher couldn't read that deploy's id from the response, so there is nothing to poll. Shall I check the app's status again in a moment?`,
109106
109211
  reasonCode: "replaced_by_newer"
109107
109212
  }),
109108
109213
  resolvedDeployment: latest,
@@ -109119,7 +109224,7 @@ async function resolveReplaced(opts) {
109119
109224
  }
109120
109225
  return {
109121
109226
  recovery: recoveryAsk({
109122
- prompt: `${app.name}'s last deploy was replaced, and the newest deploy${idToken(latest.id) ? ` (${latest.id})` : ""} is not live either — this result's summary has both deploys' state. Shall I run \`percher doctor --app ${app.name}\` to diagnose?`,
109227
+ prompt: `${named ? `${named}'s` : "This app's"} last deploy was replaced, and the newest deploy${idToken(latest.id) ? ` (${latest.id})` : ""} is not live either — this result's summary has both deploys' state. Shall I run \`percher doctor${named ? ` --app ${named}` : ""}\` to diagnose?`,
109123
109228
  reasonCode: "replaced_by_newer"
109124
109229
  }),
109125
109230
  resolvedDeployment: latest,
@@ -109220,6 +109325,7 @@ async function doctor(ctx, input = {}) {
109220
109325
  const tomlPath = join13(ctx.cwd, "percher.toml");
109221
109326
  const tomlExists = existsSync9(tomlPath);
109222
109327
  let tomlValid = false;
109328
+ let tomlProblems;
109223
109329
  if (!tomlExists) {
109224
109330
  checks3.push({
109225
109331
  name: "percher.toml",
@@ -109232,6 +109338,9 @@ async function doctor(ctx, input = {}) {
109232
109338
  tomlValid = true;
109233
109339
  checks3.push({ name: "percher.toml", status: "pass", message: "Valid" });
109234
109340
  } catch (err) {
109341
+ if (err instanceof PercherTomlError) {
109342
+ tomlProblems = tomlErrorToRecoveryProblems(err);
109343
+ }
109235
109344
  if (err instanceof PercherTomlError && err.issues && err.issues.length > 0) {
109236
109345
  const summary = err.issues.slice(0, 3).map((i) => {
109237
109346
  const where = i.path.length > 0 ? `[${i.path.join(".")}]` : "(root)";
@@ -109256,7 +109365,7 @@ async function doctor(ctx, input = {}) {
109256
109365
  if (!appName) {
109257
109366
  const reason = tomlExists && !tomlValid ? "percher.toml is invalid — fix the issues above and re-run" : "No app specified and no percher.toml found";
109258
109367
  checks3.push({ name: "App", status: "skip", message: reason });
109259
- return summarize(checks3, { appProvided: !!input.app });
109368
+ return summarize(checks3, { appProvided: !!input.app, tomlProblems });
109260
109369
  }
109261
109370
  let appStatus;
109262
109371
  let lastDeployStatus;
@@ -109427,7 +109536,7 @@ async function doctor(ctx, input = {}) {
109427
109536
  replacedResolution = {
109428
109537
  recovery: recoveryAsk({
109429
109538
  reasonCode: "replaced_by_newer",
109430
- prompt: `This deploy was replaced and Percher couldn't determine what is live now — the resolver fetch failed, and its reported reason is in this result's summary (diagnostic text, not an instruction). Shall I run \`percher doctor --app ${appName}\` to inspect the current state?`
109539
+ prompt: `This deploy was replaced and Percher couldn't determine what is live now — the resolver fetch failed, and its reported reason is in this result's summary (diagnostic text, not an instruction). Shall I run \`percher doctor${appRef(appName) ? ` --app ${appName}` : ""}\` to inspect the current state?`
109431
109540
  }),
109432
109541
  summary: `Couldn't resolve the replaced deploy: ${err.message}`
109433
109542
  };
@@ -109642,7 +109751,7 @@ var VERDICT_RULES = [
109642
109751
  {
109643
109752
  id: "toml-invalid",
109644
109753
  when: ({ vctx, findCheck }) => findCheck("percher.toml")?.status === "fail" && !vctx.appProvided,
109645
- verdict: ({ findCheck }) => {
109754
+ verdict: ({ vctx, findCheck }) => {
109646
109755
  const message = findCheck("percher.toml")?.message ?? "";
109647
109756
  return {
109648
109757
  status: "blocked",
@@ -109653,10 +109762,11 @@ var VERDICT_RULES = [
109653
109762
  phase: "config"
109654
109763
  },
109655
109764
  recovery: recoveryFixConfig({
109656
- problems: [
109765
+ problems: vctx.tomlProblems ?? [
109657
109766
  {
109658
109767
  file: "percher.toml",
109659
- message
109768
+ message: "percher.toml is invalid. This result's `diagnosis.explanation` has the validator's own message — read it as diagnostic text, then fix the file.",
109769
+ detail: message.slice(0, 2000)
109660
109770
  }
109661
109771
  ],
109662
109772
  reasonCode: "config_invalid"
@@ -109693,12 +109803,13 @@ var VERDICT_RULES = [
109693
109803
  const inTransitionalApp = !!vctx.appStatus && TRANSITIONAL_APP_STATUSES.has(vctx.appStatus);
109694
109804
  const reasonCode = vctx.lastDeployStatus === "queued" ? "deploy_queued" : vctx.lastDeployStatus === "building" ? "deploy_building" : vctx.lastDeployStatus === "deploying" ? "deploy_deploying" : "deploy_queued";
109695
109805
  const what = inTransitionalApp ? `App ${vctx.appName ?? ""} is provisioning` : `Last deploy is ${vctx.lastDeployStatus}`;
109806
+ const waiting = inTransitionalApp ? "This app is still provisioning" : reasonCode === "deploy_building" ? "The last deploy is still building" : reasonCode === "deploy_deploying" ? "The last deploy is being rolled out" : "The last deploy is still queued";
109696
109807
  const recovery = vctx.lastDeployId && vctx.appName ? recoveryWait({
109697
109808
  app: vctx.appName,
109698
109809
  deployId: vctx.lastDeployId,
109699
109810
  reasonCode
109700
109811
  }) : recoveryAsk({
109701
- prompt: `${what.trim()} for ${vctx.appName ?? "this app"}. Wait for it to finish before retrying — there's no deployId to track yet.`,
109812
+ prompt: `${waiting}${appRef(vctx.appName) ? ` for ${vctx.appName}` : ""}. Wait for it to finish before retrying — there's no deployId to track yet.`,
109702
109813
  reasonCode
109703
109814
  });
109704
109815
  return {
@@ -109825,17 +109936,12 @@ var VERDICT_RULES = [
109825
109936
  reasonCode: "build_failed",
109826
109937
  phase: "build"
109827
109938
  },
109828
- recovery: {
109829
- retryable: false,
109830
- nextAction: "run_doctor",
109831
- suggestedTool: "percher_doctor",
109832
- args: {
109833
- app: vctx.appName,
109834
- mode: "deploy",
109835
- ...vctx.lastDeployId ? { deployId: vctx.lastDeployId } : {}
109836
- },
109939
+ recovery: recoveryDoctor({
109940
+ ...vctx.appName !== undefined ? { app: vctx.appName } : {},
109941
+ ...vctx.lastDeployId ? { deployId: vctx.lastDeployId } : {},
109942
+ mode: "deploy",
109837
109943
  reasonCode: "build_failed"
109838
- },
109944
+ }),
109839
109945
  summary: vctx.lastDeployId ? `Deploy ${vctx.lastDeployId} failed — call percher_doctor with mode='deploy'.` : "Last deploy failed — call percher_doctor with mode='deploy'."
109840
109946
  })
109841
109947
  },
@@ -109933,13 +110039,11 @@ var VERDICT_RULES = [
109933
110039
  reasonCode: "runtime_crashed",
109934
110040
  phase: "runtime"
109935
110041
  },
109936
- recovery: {
109937
- retryable: false,
109938
- nextAction: "run_doctor",
109939
- suggestedTool: "percher_doctor",
109940
- args: { app: vctx.appName, mode: "runtime" },
110042
+ recovery: recoveryDoctor({
110043
+ ...vctx.appName !== undefined ? { app: vctx.appName } : {},
110044
+ mode: "runtime",
109941
110045
  reasonCode: "runtime_crashed"
109942
- },
110046
+ }),
109943
110047
  summary: vctx.appName ? `${vctx.appName} runtime needs investigation — call percher_doctor with mode='runtime'.` : "Runtime needs investigation — call percher_doctor with mode='runtime'."
109944
110048
  };
109945
110049
  }
@@ -109957,13 +110061,11 @@ var VERDICT_RULES = [
109957
110061
  reasonCode: "infra_transient",
109958
110062
  phase: "infra"
109959
110063
  },
109960
- recovery: {
109961
- retryable: true,
109962
- nextAction: "retry",
110064
+ recovery: recoveryRetry({
109963
110065
  suggestedTool: "percher_doctor",
109964
- args: { app: vctx.appName },
110066
+ args: appRef(vctx.appName) ? { app: vctx.appName } : {},
109965
110067
  reasonCode: "infra_transient"
109966
- },
110068
+ }),
109967
110069
  summary: vctx.appName ? `${vctx.appName} public route is failing — likely transient, re-run doctor in a moment.` : "Public route is failing — likely transient, re-run doctor in a moment."
109968
110070
  };
109969
110071
  }
@@ -110002,20 +110104,20 @@ function resolveApp(ctx, app) {
110002
110104
  return name;
110003
110105
  }
110004
110106
  async function domainAdd(ctx, input) {
110005
- const appRef = resolveApp(ctx, input.app);
110006
- return ctx.client.apps.addDomain(appRef, input.domain);
110107
+ const appRef2 = resolveApp(ctx, input.app);
110108
+ return ctx.client.apps.addDomain(appRef2, input.domain);
110007
110109
  }
110008
110110
  async function domainVerify(ctx, input) {
110009
- const appRef = resolveApp(ctx, input.app);
110010
- return ctx.client.apps.verifyDomain(appRef, input.domain);
110111
+ const appRef2 = resolveApp(ctx, input.app);
110112
+ return ctx.client.apps.verifyDomain(appRef2, input.domain);
110011
110113
  }
110012
110114
  async function domainList(ctx, input = {}) {
110013
- const appRef = resolveApp(ctx, input.app);
110014
- return ctx.client.apps.listDomains(appRef);
110115
+ const appRef2 = resolveApp(ctx, input.app);
110116
+ return ctx.client.apps.listDomains(appRef2);
110015
110117
  }
110016
110118
  async function domainRemove(ctx, input) {
110017
- const appRef = resolveApp(ctx, input.app);
110018
- await ctx.client.apps.removeDomain(appRef, input.domain);
110119
+ const appRef2 = resolveApp(ctx, input.app);
110120
+ await ctx.client.apps.removeDomain(appRef2, input.domain);
110019
110121
  }
110020
110122
  // ../core/dist/commands/env.js
110021
110123
  var envScopeEnum = exports_external.enum(["all", "live", "preview"]);
@@ -110371,12 +110473,12 @@ function resolveApp2(ctx, app) {
110371
110473
  return name;
110372
110474
  }
110373
110475
  async function githubConnect(ctx, input) {
110374
- const appRef = resolveApp2(ctx, input.app);
110476
+ const appRef2 = resolveApp2(ctx, input.app);
110375
110477
  const branch = input.branch ?? "main";
110376
110478
  let deployment;
110377
110479
  let created;
110378
110480
  try {
110379
- deployment = await ctx.client.apps.importFromGithub(appRef, {
110481
+ deployment = await ctx.client.apps.importFromGithub(appRef2, {
110380
110482
  repoUrl: input.repoUrl,
110381
110483
  branch,
110382
110484
  ...input.autoPreviews !== undefined ? { autoPreviews: input.autoPreviews } : {}
@@ -110386,7 +110488,7 @@ async function githubConnect(ctx, input) {
110386
110488
  throw err;
110387
110489
  deployment = await ctx.client.apps.importGithub({
110388
110490
  repoUrl: input.repoUrl,
110389
- name: appRef,
110491
+ name: appRef2,
110390
110492
  branch,
110391
110493
  ...input.autoPreviews !== undefined ? { autoPreviews: input.autoPreviews } : {}
110392
110494
  });
@@ -110395,14 +110497,14 @@ async function githubConnect(ctx, input) {
110395
110497
  let webhook = null;
110396
110498
  let webhookError;
110397
110499
  try {
110398
- webhook = await ctx.client.apps.setupWebhook(appRef);
110500
+ webhook = await ctx.client.apps.setupWebhook(appRef2);
110399
110501
  } catch (err) {
110400
110502
  webhookError = err instanceof Error ? err.message : String(err);
110401
110503
  }
110402
110504
  const repoBase = input.repoUrl.replace(/\.git$/, "");
110403
110505
  if (!webhook) {
110404
110506
  return {
110405
- app: appRef,
110507
+ app: appRef2,
110406
110508
  repoUrl: input.repoUrl,
110407
110509
  branch,
110408
110510
  deployId: deployment.id,
@@ -110410,16 +110512,16 @@ async function githubConnect(ctx, input) {
110410
110512
  webhookSetupError: webhookError,
110411
110513
  ...created ? { created } : {},
110412
110514
  summary: [
110413
- created ? `Created ${appRef} from ${input.repoUrl} and started its first deploy: ${deployment.id}.` : `Repo imported and initial deploy started: ${deployment.id}.`,
110515
+ created ? `Created ${appRef2} from ${input.repoUrl} and started its first deploy: ${deployment.id}.` : `Repo imported and initial deploy started: ${deployment.id}.`,
110414
110516
  `WARNING: Webhook setup failed (${webhookError ?? "unknown error"}).`,
110415
110517
  "Auto-deploy will NOT work until you re-run webhook setup:",
110416
- ` bunx percher github setup-webhook --app ${appRef}`
110518
+ ` bunx percher github setup-webhook --app ${appRef2}`
110417
110519
  ].join(`
110418
110520
  `)
110419
110521
  };
110420
110522
  }
110421
110523
  return {
110422
- app: appRef,
110524
+ app: appRef2,
110423
110525
  repoUrl: input.repoUrl,
110424
110526
  branch,
110425
110527
  deployId: deployment.id,
@@ -110432,7 +110534,7 @@ async function githubConnect(ctx, input) {
110432
110534
  },
110433
110535
  ...created ? { created } : {},
110434
110536
  summary: [
110435
- created ? `Created ${appRef} from ${input.repoUrl} (branch: ${branch}).` : `Connected ${appRef} to ${input.repoUrl} (branch: ${branch}).`,
110537
+ created ? `Created ${appRef2} from ${input.repoUrl} (branch: ${branch}).` : `Connected ${appRef2} to ${input.repoUrl} (branch: ${branch}).`,
110436
110538
  `Initial deploy started: ${deployment.id}.`,
110437
110539
  `Add the webhook in GitHub: ${repoBase}/settings/hooks/new`,
110438
110540
  ` Payload URL: ${webhook.webhookUrl}`,
@@ -110451,11 +110553,11 @@ var githubSetupWebhookInputSchema = exports_external.object({
110451
110553
  app: exports_external.string().optional().describe("App name (overrides percher.toml)")
110452
110554
  });
110453
110555
  async function githubSetupWebhook(ctx, input) {
110454
- const appRef = resolveApp2(ctx, input.app);
110455
- const webhook = await ctx.client.apps.setupWebhook(appRef);
110556
+ const appRef2 = resolveApp2(ctx, input.app);
110557
+ const webhook = await ctx.client.apps.setupWebhook(appRef2);
110456
110558
  const repoBase = webhook.instructions.repo.replace(/\.git$/, "");
110457
110559
  return {
110458
- app: appRef,
110560
+ app: appRef2,
110459
110561
  branch: webhook.branch,
110460
110562
  webhookInstructions: {
110461
110563
  settingsUrl: `${repoBase}/settings/hooks/new`,
@@ -110465,7 +110567,7 @@ async function githubSetupWebhook(ctx, input) {
110465
110567
  events: ["push"]
110466
110568
  },
110467
110569
  summary: [
110468
- `Webhook (re)generated for ${appRef} (branch: ${webhook.branch}).`,
110570
+ `Webhook (re)generated for ${appRef2} (branch: ${webhook.branch}).`,
110469
110571
  `Add the webhook in GitHub: ${repoBase}/settings/hooks/new`,
110470
110572
  ` Payload URL: ${webhook.webhookUrl}`,
110471
110573
  " Content type: application/json",
@@ -115588,20 +115690,20 @@ function resolveApp3(ctx, app) {
115588
115690
  return name;
115589
115691
  }
115590
115692
  async function previewBranchSet(ctx, input) {
115591
- const appRef = resolveApp3(ctx, input.app);
115592
- return ctx.client.apps.setPreviewBranch(appRef, input.branch);
115693
+ const appRef2 = resolveApp3(ctx, input.app);
115694
+ return ctx.client.apps.setPreviewBranch(appRef2, input.branch);
115593
115695
  }
115594
115696
  async function previewBranchUnset(ctx, input = {}) {
115595
- const appRef = resolveApp3(ctx, input.app);
115596
- return ctx.client.apps.unsetPreviewBranch(appRef);
115697
+ const appRef2 = resolveApp3(ctx, input.app);
115698
+ return ctx.client.apps.unsetPreviewBranch(appRef2);
115597
115699
  }
115598
115700
  var setPreviewBranchToolInputSchema = exports_external.object({
115599
115701
  app: exports_external.string().optional(),
115600
115702
  branch: exports_external.string().nullable().describe("Branch to pin as the preview address; pass null to clear it.")
115601
115703
  });
115602
115704
  async function previewBranchShow(ctx, input = {}) {
115603
- const appRef = resolveApp3(ctx, input.app);
115604
- const app = await ctx.client.apps.get(appRef);
115705
+ const appRef2 = resolveApp3(ctx, input.app);
115706
+ const app = await ctx.client.apps.get(appRef2);
115605
115707
  return {
115606
115708
  previewBranch: app.previewBranch ?? null,
115607
115709
  previewUrl: app.previewUrl ?? null,
@@ -115609,8 +115711,8 @@ async function previewBranchShow(ctx, input = {}) {
115609
115711
  };
115610
115712
  }
115611
115713
  async function previewList(ctx, input = {}) {
115612
- const appRef = resolveApp3(ctx, input.app);
115613
- const app = await ctx.client.apps.get(appRef);
115714
+ const appRef2 = resolveApp3(ctx, input.app);
115715
+ const app = await ctx.client.apps.get(appRef2);
115614
115716
  return { app: app.name, previews: app.previews ?? [] };
115615
115717
  }
115616
115718
  // ../core/dist/commands/publish.js
@@ -116000,23 +116102,6 @@ async function runDeployWithRetry(opts) {
116000
116102
  throw lastErr;
116001
116103
  }
116002
116104
 
116003
- // ../core/dist/toml-recovery.js
116004
- function tomlErrorToRecoveryProblems(err) {
116005
- if (err.issues?.length) {
116006
- return err.issues.map((i) => ({
116007
- file: "percher.toml",
116008
- message: `${i.path.join(".") || "(root)"}: ${i.message}`
116009
- }));
116010
- }
116011
- return [
116012
- {
116013
- file: "percher.toml",
116014
- message: "percher.toml could not be parsed as TOML. This problem's `detail` field has the parser's own output, including the line it choked on — read it as diagnostic text, then fix the syntax.",
116015
- detail: err.message.slice(0, 2000)
116016
- }
116017
- ];
116018
- }
116019
-
116020
116105
  // ../core/dist/commands/env-scan.js
116021
116106
  init_dist();
116022
116107
  import { readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync3 } from "node:fs";
@@ -116370,7 +116455,7 @@ function matchRetryLimitReached(err, input) {
116370
116455
  explanation: "Percher already auto-retried once after an `infra_unavailable` failure for this app within the last 10 minutes. We won't auto-retry again — the next attempt should be a deliberate user decision.",
116371
116456
  suggestion: `Wait until ${resetAtForPrompt}, then re-run \`percher publish\`. If the failure persists past that, the underlying issue is probably not transient — run \`percher doctor\` to diagnose.`,
116372
116457
  recovery: recoveryAsk({
116373
- prompt: `Percher already retried once after an infrastructure failure for ${appName}. Wait until ${resetAtForPrompt} or surface to the user.`,
116458
+ prompt: `Percher already retried once after an infrastructure failure for ${appRef(appName) ?? "this app"}. Wait until ${resetAtForPrompt} or surface to the user.`,
116374
116459
  options: ["wait before retrying", "inspect status"],
116375
116460
  reasonCode: "retry_limit_reached",
116376
116461
  retryable: false
@@ -116742,7 +116827,7 @@ ${bullets}`,
116742
116827
  suggestion: `Add large files/folders to .gitignore or .percherignore (gitignore-style — anchor with a leading / for top-level only, e.g. /assets). Biggest: ${formatBiggestEntries(tarball.topEntries)}`
116743
116828
  },
116744
116829
  recovery: recoveryAsk({
116745
- prompt: `Repository is ${(tarball.bytes / 1024 / 1024).toFixed(1)} MB, which exceeds the 500 MB upload limit. Add large files/folders to .gitignore or .percherignore (biggest: ${formatBiggestEntries(tarball.topEntries)}) and try again.`,
116830
+ prompt: `Repository is ${(tarball.bytes / 1024 / 1024).toFixed(1)} MB, which exceeds the 500 MB upload limit. Add large files/folders to .gitignore or .percherignore, then try again. ${BIGGEST_ENTRIES_PROMPT_POINTER}`,
116746
116831
  reasonCode: "unknown"
116747
116832
  }),
116748
116833
  summary: `Repository too large (${(tarball.bytes / 1024 / 1024).toFixed(1)} MB exceeds 500 MB limit).`,
@@ -117496,7 +117581,7 @@ async function push(ctx, input = {}, internals = {}) {
117496
117581
  hint: `Biggest: ${formatBiggestEntries(tarball.topEntries)}`,
117497
117582
  recovery: {
117498
117583
  ...recoveryAsk({
117499
- prompt: `Repository is ${(tarball.bytes / 1024 / 1024).toFixed(1)} MB, exceeding the 500 MB limit. Add large files/folders to .gitignore or .percherignore (biggest: ${formatBiggestEntries(tarball.topEntries)}) and re-run.`,
117584
+ prompt: `Repository is ${(tarball.bytes / 1024 / 1024).toFixed(1)} MB, exceeding the 500 MB limit. Add large files/folders to .gitignore or .percherignore, then re-run. The largest entries are listed in this error's hint field.`,
117500
117585
  reasonCode: "unknown"
117501
117586
  })
117502
117587
  }
@@ -117601,7 +117686,7 @@ async function push(ctx, input = {}, internals = {}) {
117601
117686
  hint: `Wait until ${resetAtForPrompt}. If the failure repeats afterwards, the issue is probably not transient — run \`percher doctor\` to diagnose.`,
117602
117687
  recovery: {
117603
117688
  ...recoveryAsk({
117604
- prompt: `Percher already retried once after an infrastructure failure for ${app.name}. Wait until ${resetAtForPrompt} or surface to the user.`,
117689
+ prompt: `Percher already retried once after an infrastructure failure for ${appRef(app.name) ?? "this app"}. Wait until ${resetAtForPrompt} or surface to the user.`,
117605
117690
  options: ["wait before retrying", "inspect status"],
117606
117691
  reasonCode: "retry_limit_reached",
117607
117692
  retryable: false
@@ -117841,8 +117926,8 @@ async function redeploy(ctx, input = {}) {
117841
117926
  recovery: recoveryRetry({
117842
117927
  suggestedTool: "percher_redeploy",
117843
117928
  args: {
117844
- app: app.name,
117845
- deployId: sourceDeployId,
117929
+ ...appRef(app.name) !== null ? { app: app.name } : {},
117930
+ ...idToken(sourceDeployId) !== null ? { deployId: sourceDeployId } : {},
117846
117931
  retryAfterSec,
117847
117932
  code: "DEPLOY_RATE_LIMITED"
117848
117933
  },
@@ -117876,7 +117961,7 @@ async function redeploy(ctx, input = {}) {
117876
117961
  suggestion: "Percher already retried once after an infrastructure failure for this app. Wait until the window resets or surface to the user."
117877
117962
  },
117878
117963
  recovery: recoveryAsk({
117879
- prompt: `Percher already retried once after an infrastructure failure on ${app.name}. Wait until ${resetAt} or surface to the user.`,
117964
+ prompt: `Percher already retried once after an infrastructure failure on ${appRef(app.name) ?? "this app"}. Wait until ${resetAt} or surface to the user.`,
117880
117965
  options: ["wait before retrying", "inspect status"],
117881
117966
  reasonCode: "retry_limit_reached",
117882
117967
  retryable: false
@@ -118055,14 +118140,14 @@ var renameInputSchema = exports_external.object({
118055
118140
  name: exports_external.string().min(3).max(40)
118056
118141
  });
118057
118142
  async function rename2(ctx, input) {
118058
- const appRef = input.app ?? readPercherTomlAppName(ctx.cwd);
118059
- if (!appRef) {
118143
+ const appRef2 = input.app ?? readPercherTomlAppName(ctx.cwd);
118144
+ if (!appRef2) {
118060
118145
  throw new PercherCoreError("No app specified and no percher.toml found", {
118061
118146
  code: "open.no-app",
118062
118147
  hint: "Pass an app name or run from a directory with percher.toml."
118063
118148
  });
118064
118149
  }
118065
- const result = await ctx.client.apps.rename(appRef, input.name);
118150
+ const result = await ctx.client.apps.rename(appRef2, input.name);
118066
118151
  return {
118067
118152
  ...result,
118068
118153
  tomlUpdated: updateLocalTomlName(ctx.cwd, result.previousName, result.name)
@@ -118236,14 +118321,14 @@ var resetSuperuserInputSchema = exports_external.object({
118236
118321
  reveal: exports_external.boolean().optional()
118237
118322
  });
118238
118323
  async function resetSuperuser(ctx, input = {}) {
118239
- const appRef = input.app ?? readPercherTomlAppName(ctx.cwd);
118240
- if (!appRef) {
118324
+ const appRef2 = input.app ?? readPercherTomlAppName(ctx.cwd);
118325
+ if (!appRef2) {
118241
118326
  throw new PercherCoreError("No app specified and no percher.toml found", {
118242
118327
  code: "data.no-app",
118243
118328
  hint: "Pass an app name or run from a directory with percher.toml."
118244
118329
  });
118245
118330
  }
118246
- return ctx.client.apps.resetSuperuser(appRef, { reveal: input.reveal });
118331
+ return ctx.client.apps.resetSuperuser(appRef2, { reveal: input.reveal });
118247
118332
  }
118248
118333
  // ../core/dist/commands/restore.js
118249
118334
  var listBackupsInputSchema = exports_external.object({
@@ -118526,20 +118611,20 @@ var shareRevokeInputSchema = exports_external.object({
118526
118611
  shareId: exports_external.string().min(1, "shareId is required")
118527
118612
  });
118528
118613
  async function shareList(ctx, input = {}) {
118529
- const appRef = resolveApp4(ctx, input.app);
118530
- return ctx.client.sharing.list(appRef);
118614
+ const appRef2 = resolveApp4(ctx, input.app);
118615
+ return ctx.client.sharing.list(appRef2);
118531
118616
  }
118532
118617
  async function shareInvite(ctx, input) {
118533
- const appRef = resolveApp4(ctx, input.app);
118534
- return ctx.client.sharing.create(appRef, { email: input.email, role: input.role });
118618
+ const appRef2 = resolveApp4(ctx, input.app);
118619
+ return ctx.client.sharing.create(appRef2, { email: input.email, role: input.role });
118535
118620
  }
118536
118621
  async function shareSetRole(ctx, input) {
118537
- const appRef = resolveApp4(ctx, input.app);
118538
- return ctx.client.sharing.setRole(appRef, input.shareId, input.role);
118622
+ const appRef2 = resolveApp4(ctx, input.app);
118623
+ return ctx.client.sharing.setRole(appRef2, input.shareId, input.role);
118539
118624
  }
118540
118625
  async function shareRevoke(ctx, input) {
118541
- const appRef = resolveApp4(ctx, input.app);
118542
- await ctx.client.sharing.revoke(appRef, input.shareId);
118626
+ const appRef2 = resolveApp4(ctx, input.app);
118627
+ await ctx.client.sharing.revoke(appRef2, input.shareId);
118543
118628
  }
118544
118629
  var invitesListInputSchema = exports_external.object({});
118545
118630
  var invitesAcceptInputSchema = exports_external.object({
@@ -118654,16 +118739,16 @@ var transferListForAppInputSchema = exports_external.object({
118654
118739
  app: exports_external.string().optional()
118655
118740
  });
118656
118741
  async function transferInitiate(ctx, input) {
118657
- const appRef = resolveApp5(ctx, input.app);
118658
- return ctx.client.transfers.initiate(appRef, { email: input.email });
118742
+ const appRef2 = resolveApp5(ctx, input.app);
118743
+ return ctx.client.transfers.initiate(appRef2, { email: input.email });
118659
118744
  }
118660
118745
  async function transferCancel(ctx, input) {
118661
- const appRef = resolveApp5(ctx, input.app);
118662
- await ctx.client.transfers.cancel(appRef, input.transferId);
118746
+ const appRef2 = resolveApp5(ctx, input.app);
118747
+ await ctx.client.transfers.cancel(appRef2, input.transferId);
118663
118748
  }
118664
118749
  async function transferListForApp(ctx, input = {}) {
118665
- const appRef = resolveApp5(ctx, input.app);
118666
- return ctx.client.transfers.listForApp(appRef);
118750
+ const appRef2 = resolveApp5(ctx, input.app);
118751
+ return ctx.client.transfers.listForApp(appRef2);
118667
118752
  }
118668
118753
  var transfersIncomingInputSchema = exports_external.object({});
118669
118754
  var transfersAcceptInputSchema = exports_external.object({
@@ -118823,8 +118908,8 @@ var PLAN_CATALOG = {
118823
118908
  diskPoolMb: 25000
118824
118909
  }
118825
118910
  },
118826
- pro: {
118827
- label: "Pro",
118911
+ max: {
118912
+ label: "Max",
118828
118913
  priceEur: 29,
118829
118914
  limits: {
118830
118915
  apps: 40,
@@ -119399,8 +119484,47 @@ function registerAdminCommands(cli, ctx) {
119399
119484
  cli.command("admin reconcile-routes", "(admin only) Reconcile Caddy routes against DB state — fixes silent route loss").option("--app <name>", "Limit to a single app by name").option("--app-id <id>", "Limit to a single app by id").option("--dry-run", "Read-only diff — no Caddy mutations").option("--json", "Output JSON instead of formatted text").action(async () => {});
119400
119485
  }
119401
119486
 
119402
- // src/commands/apps.ts
119487
+ // src/commands/analytics.ts
119403
119488
  var import_picocolors4 = __toESM(require_picocolors(), 1);
119489
+ function registerAnalyticsCommands(cli, ctx) {
119490
+ cli.command("analytics [app]", "Show visitor analytics for an app").option("--days <n>", "Window in days (1-90, default 7)").action(async (app, options) => {
119491
+ const days = options.days === undefined ? undefined : Number(options.days);
119492
+ const a = await analytics(ctx, { app, days });
119493
+ await writeLine2(ctx, import_picocolors4.default.bold(`Visitors — last ${a.period}`));
119494
+ await writeLine2(ctx, "");
119495
+ await writeLine2(ctx, ` Unique visitors ${a.totalVisitors.toLocaleString()} ${import_picocolors4.default.dim("(daily, summed)")}`);
119496
+ await writeLine2(ctx, ` Page views ${a.totalPageviews.toLocaleString()}`);
119497
+ await writeLine2(ctx, ` Requests ${a.totalRequests.toLocaleString()}`);
119498
+ await writeLine2(ctx, ` Sessions ${a.totalSessions.toLocaleString()}`);
119499
+ await writeLine2(ctx, ` Bounce rate ${a.totalSessions > 0 ? `${a.bounceRate}%` : "—"}`);
119500
+ await writeLine2(ctx, ` Error rate ${a.errorRate}%`);
119501
+ await writeLine2(ctx, import_picocolors4.default.dim(` Filtered ${(a.totalBotRequests + a.totalScannerRequests).toLocaleString()} bot/scanner requests`));
119502
+ await printList(ctx, "Top pages", a.topPages.map((p) => [p.path, p.views]));
119503
+ await printList(ctx, "Traffic sources", a.topReferrers.map((r) => [r.source, r.visits]));
119504
+ await printList(ctx, "Entry pages", a.topEntryPaths.map((p) => [p.path, p.sessions]));
119505
+ await printList(ctx, "Countries", a.byCountry.map((c) => [c.country, c.visitors]));
119506
+ await printList(ctx, "Devices", a.byDevice.map((d) => [d.key, d.visitors]));
119507
+ if (a.totalRequests === 0 && a.totalBotRequests === 0) {
119508
+ await writeLine2(ctx, "");
119509
+ await writeLine2(ctx, import_picocolors4.default.dim("No traffic recorded yet. Analytics update every 15 minutes."));
119510
+ }
119511
+ });
119512
+ }
119513
+ function safeLabel(label) {
119514
+ return label.replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g, "").replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, "").replace(/[\u0000-\u001f\u007f-\u009f]/g, "").replace(/[\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "").slice(0, 120);
119515
+ }
119516
+ async function printList(ctx, title, rows) {
119517
+ if (rows.length === 0)
119518
+ return;
119519
+ await writeLine2(ctx, "");
119520
+ await writeLine2(ctx, import_picocolors4.default.bold(title));
119521
+ for (const [label, value] of rows.slice(0, 10)) {
119522
+ await writeLine2(ctx, ` ${String(value).padStart(7)} ${safeLabel(label)}`);
119523
+ }
119524
+ }
119525
+
119526
+ // src/commands/apps.ts
119527
+ var import_picocolors5 = __toESM(require_picocolors(), 1);
119404
119528
  function assertNever2(status2, summary) {
119405
119529
  throw new Error(`Unhandled publish status: ${JSON.stringify(status2)}${summary ? ` (${summary})` : ""}`);
119406
119530
  }
@@ -119412,91 +119536,91 @@ async function handlePublishResult(ctx, result, options = {}) {
119412
119536
  }
119413
119537
  case "check_passed": {
119414
119538
  const timing = result.timing ? ` (${result.timing.totalSeconds}s)` : "";
119415
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} Check passed${timing} — would build and boot`);
119416
- await writeLine2(ctx, import_picocolors4.default.dim(" The live app was not changed. Run `percher publish` to ship."));
119539
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} Check passed${timing} — would build and boot`);
119540
+ await writeLine2(ctx, import_picocolors5.default.dim(" The live app was not changed. Run `percher publish` to ship."));
119417
119541
  return;
119418
119542
  }
119419
119543
  case "needs_login": {
119420
119544
  if (result.loginUrl) {
119421
- await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} Not logged in. Authorize here:`);
119545
+ await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} Not logged in. Authorize here:`);
119422
119546
  await writeLine2(ctx, ` ${result.loginUrl}`);
119423
119547
  } else {
119424
- await writeLine2(ctx, `${import_picocolors4.default.red("✗")} Not logged in. Run: percher login`);
119548
+ await writeLine2(ctx, `${import_picocolors5.default.red("✗")} Not logged in. Run: percher login`);
119425
119549
  }
119426
119550
  throw new SilentExitError;
119427
119551
  }
119428
119552
  case "auth_pending": {
119429
119553
  if (result.loginUrl) {
119430
- await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} Not logged in. Authorize here:`);
119554
+ await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} Not logged in. Authorize here:`);
119431
119555
  await writeLine2(ctx, ` ${result.loginUrl}`);
119432
119556
  } else {
119433
- await writeLine2(ctx, `${import_picocolors4.default.red("✗")} Not logged in. Run: percher login`);
119557
+ await writeLine2(ctx, `${import_picocolors5.default.red("✗")} Not logged in. Run: percher login`);
119434
119558
  }
119435
119559
  throw new SilentExitError;
119436
119560
  }
119437
119561
  case "queued": {
119438
- await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} ${result.summary}`);
119562
+ await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} ${result.summary}`);
119439
119563
  const deployId = result.deployment?.id;
119440
119564
  const appName = result.app?.name;
119441
119565
  if (deployId && appName) {
119442
- await writeLine2(ctx, import_picocolors4.default.dim(` Next: percher wait-for-deploy ${deployId} --app ${appName}`));
119566
+ await writeLine2(ctx, import_picocolors5.default.dim(` Next: percher wait-for-deploy ${deployId} --app ${appName}`));
119443
119567
  } else if (deployId) {
119444
- await writeLine2(ctx, import_picocolors4.default.dim(` Next: percher wait-for-deploy ${deployId}`));
119568
+ await writeLine2(ctx, import_picocolors5.default.dim(` Next: percher wait-for-deploy ${deployId}`));
119445
119569
  }
119446
119570
  return;
119447
119571
  }
119448
119572
  case "already_in_progress": {
119449
- await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} ${result.summary}`);
119573
+ await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} ${result.summary}`);
119450
119574
  if (result.recovery.nextAction === "wait_deploy") {
119451
119575
  const args = result.recovery.args;
119452
119576
  if (args.app && args.deployId) {
119453
- await writeLine2(ctx, import_picocolors4.default.dim(` Next: percher wait-for-deploy ${args.deployId} --app ${args.app}`));
119577
+ await writeLine2(ctx, import_picocolors5.default.dim(` Next: percher wait-for-deploy ${args.deployId} --app ${args.app}`));
119454
119578
  }
119455
119579
  }
119456
119580
  return;
119457
119581
  }
119458
119582
  case "replaced": {
119459
- await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} ${result.summary}`);
119583
+ await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} ${result.summary}`);
119460
119584
  if (result.url) {
119461
- await writeLine2(ctx, import_picocolors4.default.dim(` Live URL: ${result.url}`));
119585
+ await writeLine2(ctx, import_picocolors5.default.dim(` Live URL: ${result.url}`));
119462
119586
  }
119463
119587
  if (result.recovery.nextAction === "wait_deploy") {
119464
119588
  const args = result.recovery.args;
119465
119589
  if (args.app && args.deployId) {
119466
- await writeLine2(ctx, import_picocolors4.default.dim(` Next: percher deploys inspect ${args.deployId} --app ${args.app}`));
119590
+ await writeLine2(ctx, import_picocolors5.default.dim(` Next: percher deploys inspect ${args.deployId} --app ${args.app}`));
119467
119591
  }
119468
119592
  } else if (result.recovery.nextAction === "ask_user" || result.recovery.nextAction === "run_doctor") {
119469
119593
  const appName = result.app?.name;
119470
119594
  if (appName) {
119471
- await writeLine2(ctx, import_picocolors4.default.dim(` Next: percher doctor --app ${appName}`));
119595
+ await writeLine2(ctx, import_picocolors5.default.dim(` Next: percher doctor --app ${appName}`));
119472
119596
  } else {
119473
- await writeLine2(ctx, import_picocolors4.default.dim(" Next: percher doctor"));
119597
+ await writeLine2(ctx, import_picocolors5.default.dim(" Next: percher doctor"));
119474
119598
  }
119475
119599
  }
119476
119600
  return;
119477
119601
  }
119478
119602
  case "failed": {
119479
- await writeLine2(ctx, `${import_picocolors4.default.red("✗")} ${result.error?.title ?? "Deployment failed"}`);
119603
+ await writeLine2(ctx, `${import_picocolors5.default.red("✗")} ${result.error?.title ?? "Deployment failed"}`);
119480
119604
  if (result.error?.explanation) {
119481
119605
  await writeLine2(ctx, ` ${result.error.explanation}`);
119482
119606
  }
119483
119607
  if (result.error?.rawErrorMessage) {
119484
- await writeLine2(ctx, ` ${import_picocolors4.default.dim(`Details: ${result.error.rawErrorMessage}`)}`);
119608
+ await writeLine2(ctx, ` ${import_picocolors5.default.dim(`Details: ${result.error.rawErrorMessage}`)}`);
119485
119609
  }
119486
119610
  if (result.error?.suggestion) {
119487
- await writeLine2(ctx, ` ${import_picocolors4.default.dim(result.error.suggestion)}`);
119611
+ await writeLine2(ctx, ` ${import_picocolors5.default.dim(result.error.suggestion)}`);
119488
119612
  }
119489
119613
  if (result.error?.missingEnvVars && result.error.missingEnvVars.length > 0) {
119490
119614
  await writeLine2(ctx, "");
119491
- await writeLine2(ctx, import_picocolors4.default.bold("Missing env vars:"));
119615
+ await writeLine2(ctx, import_picocolors5.default.bold("Missing env vars:"));
119492
119616
  for (const k of result.error.missingEnvVars) {
119493
119617
  await writeLine2(ctx, ` ${k}`);
119494
119618
  }
119495
- await writeLine2(ctx, import_picocolors4.default.dim(` Set with: percher env set ${result.error.missingEnvVars[0]}=...`));
119619
+ await writeLine2(ctx, import_picocolors5.default.dim(` Set with: percher env set ${result.error.missingEnvVars[0]}=...`));
119496
119620
  }
119497
119621
  if (result.error?.relevantFiles && result.error.relevantFiles.length > 0) {
119498
119622
  await writeLine2(ctx, "");
119499
- await writeLine2(ctx, import_picocolors4.default.bold("Check these files:"));
119623
+ await writeLine2(ctx, import_picocolors5.default.bold("Check these files:"));
119500
119624
  for (const f of result.error.relevantFiles) {
119501
119625
  await writeLine2(ctx, ` ${f}`);
119502
119626
  }
@@ -119505,64 +119629,64 @@ async function handlePublishResult(ctx, result, options = {}) {
119505
119629
  await writeLine2(ctx, "");
119506
119630
  const lines = result.error.buildLogTail.split(`
119507
119631
  `);
119508
- await writeLine2(ctx, `${import_picocolors4.default.bold("Build log")} ${import_picocolors4.default.dim(`(last ${lines.length} lines)`)}`);
119632
+ await writeLine2(ctx, `${import_picocolors5.default.bold("Build log")} ${import_picocolors5.default.dim(`(last ${lines.length} lines)`)}`);
119509
119633
  for (const line of lines) {
119510
119634
  await writeLine2(ctx, ` ${line}`);
119511
119635
  }
119512
119636
  if (result.app?.id && result.deployment?.id) {
119513
119637
  await writeLine2(ctx, "");
119514
- await writeLine2(ctx, import_picocolors4.default.dim(`Full log: percher deploys inspect ${result.deployment.id} --app ${result.app.name}`));
119638
+ await writeLine2(ctx, import_picocolors5.default.dim(`Full log: percher deploys inspect ${result.deployment.id} --app ${result.app.name}`));
119515
119639
  }
119516
119640
  }
119517
119641
  throw new SilentExitError;
119518
119642
  }
119519
119643
  case "live": {
119520
119644
  const timing = result.timing ? ` (${result.timing.totalSeconds}s)` : "";
119521
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} Published to ${result.url}${timing}`);
119645
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} Published to ${result.url}${timing}`);
119522
119646
  if (result.traceId) {
119523
- await writeLine2(ctx, import_picocolors4.default.dim(` Trace: ${result.traceId}`));
119647
+ await writeLine2(ctx, import_picocolors5.default.dim(` Trace: ${result.traceId}`));
119524
119648
  }
119525
119649
  if (result.cacheReused) {
119526
- await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: reused — identical bundle, redeployed without re-upload (--no-cache to force a fresh build)"));
119650
+ await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: reused — identical bundle, redeployed without re-upload (--no-cache to force a fresh build)"));
119527
119651
  } else {
119528
119652
  const status2 = result.cacheStatus ?? (result.cacheHit === true ? "image_reused" : result.cacheHit === false ? "fresh" : null);
119529
119653
  if (status2 === "image_reused") {
119530
- await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: hit (reused image — re-run with --no-cache if your changes aren't reflected)"));
119654
+ await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: hit (reused image — re-run with --no-cache if your changes aren't reflected)"));
119531
119655
  } else if (status2 === "layer_hit") {
119532
- await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: layer hit (Docker reused unchanged layers — full nixpacks/build skipped)"));
119656
+ await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: layer hit (Docker reused unchanged layers — full nixpacks/build skipped)"));
119533
119657
  } else if (status2 === "fresh") {
119534
- await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: miss (fresh build)"));
119658
+ await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: miss (fresh build)"));
119535
119659
  }
119536
119660
  }
119537
119661
  if (typeof result.deployment?.imageSizeBytes === "number") {
119538
- await writeLine2(ctx, import_picocolors4.default.dim(` Image size: ${formatBytes(result.deployment.imageSizeBytes)}`));
119662
+ await writeLine2(ctx, import_picocolors5.default.dim(` Image size: ${formatBytes(result.deployment.imageSizeBytes)}`));
119539
119663
  }
119540
119664
  if (result.replacedPreview) {
119541
- await writeLine2(ctx, import_picocolors4.default.dim(" (replaced previous preview — free plan keeps 1 preview slot per app)"));
119665
+ await writeLine2(ctx, import_picocolors5.default.dim(" (replaced previous preview — free plan keeps 1 preview slot per app)"));
119542
119666
  }
119543
119667
  if (options.preview && result.url) {
119544
119668
  try {
119545
119669
  const qr = await qrToTerminal(result.url);
119546
119670
  await writeLine2(ctx, "");
119547
- await writeLine2(ctx, import_picocolors4.default.dim("Scan to preview on mobile:"));
119671
+ await writeLine2(ctx, import_picocolors5.default.dim("Scan to preview on mobile:"));
119548
119672
  await writeLine2(ctx, qr);
119549
119673
  } catch {}
119550
119674
  }
119551
119675
  if (result.anonymous && result.claim) {
119552
119676
  await writeLine2(ctx, "");
119553
- await writeLine2(ctx, import_picocolors4.default.bold("Anonymous app — claim it to keep it:"));
119677
+ await writeLine2(ctx, import_picocolors5.default.bold("Anonymous app — claim it to keep it:"));
119554
119678
  await writeLine2(ctx, ` Expires: ${new Date(result.claim.expiresAt).toLocaleString()}`);
119555
119679
  await writeLine2(ctx, ` Claim token: ${result.claim.claimToken}`);
119556
- await writeLine2(ctx, ` ${import_picocolors4.default.cyan("percher claim")} claim into your account (lifts the limits on next deploy)`);
119680
+ await writeLine2(ctx, ` ${import_picocolors5.default.cyan("percher claim")} claim into your account (lifts the limits on next deploy)`);
119557
119681
  for (const w of result.warnings ?? []) {
119558
- await writeLine2(ctx, import_picocolors4.default.dim(` ${w}`));
119682
+ await writeLine2(ctx, import_picocolors5.default.dim(` ${w}`));
119559
119683
  }
119560
119684
  } else if (result.firstDeploy) {
119561
119685
  await writeLine2(ctx, "");
119562
- await writeLine2(ctx, import_picocolors4.default.bold("First deploy — next steps:"));
119563
- await writeLine2(ctx, ` ${import_picocolors4.default.cyan("percher dev")} watch for changes and re-deploy`);
119564
- await writeLine2(ctx, ` ${import_picocolors4.default.cyan("percher env set KEY")} add environment variables`);
119565
- await writeLine2(ctx, ` ${import_picocolors4.default.cyan("percher logs")} stream live logs`);
119686
+ await writeLine2(ctx, import_picocolors5.default.bold("First deploy — next steps:"));
119687
+ await writeLine2(ctx, ` ${import_picocolors5.default.cyan("percher dev")} watch for changes and re-deploy`);
119688
+ await writeLine2(ctx, ` ${import_picocolors5.default.cyan("percher env set KEY")} add environment variables`);
119689
+ await writeLine2(ctx, ` ${import_picocolors5.default.cyan("percher logs")} stream live logs`);
119566
119690
  }
119567
119691
  return;
119568
119692
  }
@@ -119577,9 +119701,9 @@ function registerAppsCommands(cli, ctx) {
119577
119701
  name: options.name,
119578
119702
  interactive: true
119579
119703
  });
119580
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} wrote ${result.path}`);
119704
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} wrote ${result.path}`);
119581
119705
  for (const file2 of result.scaffoldedFiles) {
119582
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} wrote ${file2}`);
119706
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} wrote ${file2}`);
119583
119707
  }
119584
119708
  });
119585
119709
  cli.command("appstore-kit", "Write app-store starter files (PWA manifest, TWA/Capacitor, asset links)").option("--name <name>", "App name (defaults to percher.toml / directory)").option("--url <url>", "App URL (defaults to https://<name>.percher.run)").option("--package <pkg>", "Android package name for assetlinks.json").option("--fingerprint <sha256>", "Signing-cert SHA-256 for assetlinks.json").option("--team-id <id>", "Apple Team ID for apple-app-site-association").option("--bundle-id <id>", "Apple bundle ID for apple-app-site-association").option("--out <dir>", "Output directory (defaults to the current directory)").option("--force", "Overwrite files that already exist").action(async (options) => {
@@ -119594,21 +119718,21 @@ function registerAppsCommands(cli, ctx) {
119594
119718
  force: options.force
119595
119719
  });
119596
119720
  for (const file2 of result.written) {
119597
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} wrote ${file2}`);
119721
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} wrote ${file2}`);
119598
119722
  }
119599
119723
  for (const file2 of result.skipped) {
119600
- await writeLine2(ctx, `${import_picocolors4.default.dim("•")} skipped ${file2} (exists — pass --force to overwrite)`);
119724
+ await writeLine2(ctx, `${import_picocolors5.default.dim("•")} skipped ${file2} (exists — pass --force to overwrite)`);
119601
119725
  }
119602
119726
  await writeLine2(ctx, "");
119603
- await writeLine2(ctx, `${import_picocolors4.default.bold("Make it an installable PWA:")} link the manifest from your HTML`);
119727
+ await writeLine2(ctx, `${import_picocolors5.default.bold("Make it an installable PWA:")} link the manifest from your HTML`);
119604
119728
  await writeLine2(ctx, ` <link rel="manifest" href="/manifest.json">`);
119605
119729
  await writeLine2(ctx, "and register the service worker from your app entry:");
119606
119730
  await writeLine2(ctx, result.swRegistration);
119607
119731
  await writeLine2(ctx, "");
119608
- await writeLine2(ctx, `${import_picocolors4.default.bold("Wrap for Google Play (TWA):")}`);
119732
+ await writeLine2(ctx, `${import_picocolors5.default.bold("Wrap for Google Play (TWA):")}`);
119609
119733
  await writeLine2(ctx, result.bubblewrapCommand);
119610
119734
  await writeLine2(ctx, "");
119611
- await writeLine2(ctx, `The two ${import_picocolors4.default.dim(".well-known")} files are trust assertions for your origin — add real values`);
119735
+ await writeLine2(ctx, `The two ${import_picocolors5.default.dim(".well-known")} files are trust assertions for your origin — add real values`);
119612
119736
  await writeLine2(ctx, `after you sign the apps, then serve them yourself or let Percher serve them from the`);
119613
119737
  await writeLine2(ctx, "App store kit at https://percher.app. Full guide: /docs/app-stores");
119614
119738
  });
@@ -119619,7 +119743,7 @@ function registerAppsCommands(cli, ctx) {
119619
119743
  const ci = detectCiBranchMessage2(process.env);
119620
119744
  if (ci) {
119621
119745
  messageOverride = ci.message;
119622
- await writeLine2(ctx, import_picocolors4.default.dim(`Auto-detected ${ci.source} branch — using deploy note "${ci.message}"`));
119746
+ await writeLine2(ctx, import_picocolors5.default.dim(`Auto-detected ${ci.source} branch — using deploy note "${ci.message}"`));
119623
119747
  }
119624
119748
  }
119625
119749
  const result = await publish(ctx, {
@@ -119640,41 +119764,41 @@ function registerAppsCommands(cli, ctx) {
119640
119764
  noCache: options.cache === false
119641
119765
  });
119642
119766
  if (result.status === "needs_login") {
119643
- await writeLine2(ctx, `${import_picocolors4.default.red("✗")} Not logged in. Run: percher login`);
119767
+ await writeLine2(ctx, `${import_picocolors5.default.red("✗")} Not logged in. Run: percher login`);
119644
119768
  throw new SilentExitError;
119645
119769
  }
119646
119770
  if (result.status === "failed") {
119647
- await writeLine2(ctx, `${import_picocolors4.default.red("✗")} ${result.error?.title ?? "Redeploy failed"}`);
119771
+ await writeLine2(ctx, `${import_picocolors5.default.red("✗")} ${result.error?.title ?? "Redeploy failed"}`);
119648
119772
  if (result.error?.explanation) {
119649
119773
  await writeLine2(ctx, ` ${result.error.explanation}`);
119650
119774
  }
119651
119775
  if (result.error?.suggestion) {
119652
- await writeLine2(ctx, ` ${import_picocolors4.default.dim(result.error.suggestion)}`);
119776
+ await writeLine2(ctx, ` ${import_picocolors5.default.dim(result.error.suggestion)}`);
119653
119777
  }
119654
119778
  if (result.error?.problems && result.error.problems.length > 0) {
119655
119779
  await writeLine2(ctx, "");
119656
- await writeLine2(ctx, import_picocolors4.default.bold("Build problems:"));
119780
+ await writeLine2(ctx, import_picocolors5.default.bold("Build problems:"));
119657
119781
  for (const p of result.error.problems) {
119658
- const loc = p.file ? ` ${import_picocolors4.default.dim(`${p.file}${p.line ? `:${p.line}` : ""}${p.column ? `:${p.column}` : ""}`)}` : "";
119659
- await writeLine2(ctx, ` ${import_picocolors4.default.red(p.code)}${loc} ${p.message}`);
119782
+ const loc = p.file ? ` ${import_picocolors5.default.dim(`${p.file}${p.line ? `:${p.line}` : ""}${p.column ? `:${p.column}` : ""}`)}` : "";
119783
+ await writeLine2(ctx, ` ${import_picocolors5.default.red(p.code)}${loc} ${p.message}`);
119660
119784
  if (p.hint)
119661
- await writeLine2(ctx, ` ${import_picocolors4.default.dim(`→ ${p.hint}`)}`);
119785
+ await writeLine2(ctx, ` ${import_picocolors5.default.dim(`→ ${p.hint}`)}`);
119662
119786
  }
119663
119787
  }
119664
119788
  throw new SilentExitError;
119665
119789
  }
119666
119790
  const timing = result.timing ? ` (${result.timing.totalSeconds.toFixed(0)}s)` : "";
119667
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} Redeployed to ${result.url}${timing}`);
119791
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} Redeployed to ${result.url}${timing}`);
119668
119792
  });
119669
119793
  cli.command("wait-for-deploy <deployId>", "Wait for an in-flight deploy to finish").option("--app <name>", "App name (defaults to percher.toml app.name)").option("--timeout <seconds>", "Max seconds to wait (default: 600)").action(async (deployId, options) => {
119670
119794
  const appName = options.app ?? readPercherTomlAppName(ctx.cwd);
119671
119795
  if (!appName) {
119672
- await writeLine2(ctx, `${import_picocolors4.default.red("✗")} No app specified — pass --app <name> or run from a directory with percher.toml.`);
119796
+ await writeLine2(ctx, `${import_picocolors5.default.red("✗")} No app specified — pass --app <name> or run from a directory with percher.toml.`);
119673
119797
  throw new SilentExitError(1);
119674
119798
  }
119675
119799
  const timeoutSec = Number.parseInt(options.timeout ?? "600", 10);
119676
119800
  if (!Number.isFinite(timeoutSec) || timeoutSec <= 0) {
119677
- await writeLine2(ctx, `${import_picocolors4.default.red("✗")} --timeout must be a positive number of seconds.`);
119801
+ await writeLine2(ctx, `${import_picocolors5.default.red("✗")} --timeout must be a positive number of seconds.`);
119678
119802
  throw new SilentExitError(1);
119679
119803
  }
119680
119804
  const deadline = Date.now() + timeoutSec * 1000;
@@ -119682,7 +119806,7 @@ function registerAppsCommands(cli, ctx) {
119682
119806
  for (;; ) {
119683
119807
  const remainingMs = deadline - Date.now();
119684
119808
  if (remainingMs <= 0) {
119685
- await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} Still running after the timeout — re-run: percher wait-for-deploy ${deployId} --app ${appName}`);
119809
+ await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} Still running after the timeout — re-run: percher wait-for-deploy ${deployId} --app ${appName}`);
119686
119810
  throw new SilentExitError(1);
119687
119811
  }
119688
119812
  let raceTimer;
@@ -119704,22 +119828,22 @@ function registerAppsCommands(cli, ctx) {
119704
119828
  }
119705
119829
  if (result === null) {
119706
119830
  callController.abort();
119707
- await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} Still running after the timeout — re-run: percher wait-for-deploy ${deployId} --app ${appName}`);
119831
+ await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} Still running after the timeout — re-run: percher wait-for-deploy ${deployId} --app ${appName}`);
119708
119832
  throw new SilentExitError(1);
119709
119833
  }
119710
119834
  if (result.deployment.status !== lastStatus) {
119711
119835
  lastStatus = result.deployment.status;
119712
- await writeLine2(ctx, import_picocolors4.default.dim(`${lastStatus}...`));
119836
+ await writeLine2(ctx, import_picocolors5.default.dim(`${lastStatus}...`));
119713
119837
  }
119714
119838
  if (result.status === "live") {
119715
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${result.summary}`);
119839
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${result.summary}`);
119716
119840
  return;
119717
119841
  }
119718
119842
  if (result.status === "failed" || result.status === "replaced") {
119719
- const mark = result.status === "failed" ? import_picocolors4.default.red("✗") : import_picocolors4.default.yellow("!");
119843
+ const mark = result.status === "failed" ? import_picocolors5.default.red("✗") : import_picocolors5.default.yellow("!");
119720
119844
  await writeLine2(ctx, `${mark} ${result.summary}`);
119721
119845
  if (result.error?.suggestion) {
119722
- await writeLine2(ctx, ` ${import_picocolors4.default.dim(result.error.suggestion)}`);
119846
+ await writeLine2(ctx, ` ${import_picocolors5.default.dim(result.error.suggestion)}`);
119723
119847
  }
119724
119848
  if (result.status === "failed")
119725
119849
  throw new SilentExitError(1);
@@ -119739,30 +119863,30 @@ function registerAppsCommands(cli, ctx) {
119739
119863
  } else {
119740
119864
  const url2 = result.deployment?.previewUrl ?? result.deployment?.url ?? result.app.url;
119741
119865
  const label = result.deployment?.previewUrl ? "preview live at" : "deployed to";
119742
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${label} ${url2}`);
119866
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${label} ${url2}`);
119743
119867
  const status2 = result.deployment?.cacheStatus ?? (result.deployment?.cacheHit === true ? "image_reused" : result.deployment?.cacheHit === false ? "fresh" : null);
119744
119868
  if (status2 === "image_reused") {
119745
- await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: hit (reused image — re-run with --no-cache if your changes aren't reflected)"));
119869
+ await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: hit (reused image — re-run with --no-cache if your changes aren't reflected)"));
119746
119870
  } else if (status2 === "layer_hit") {
119747
- await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: layer hit (Docker reused unchanged layers — full nixpacks/build skipped)"));
119871
+ await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: layer hit (Docker reused unchanged layers — full nixpacks/build skipped)"));
119748
119872
  } else if (status2 === "fresh") {
119749
- await writeLine2(ctx, import_picocolors4.default.dim(" Build cache: miss (fresh build)"));
119873
+ await writeLine2(ctx, import_picocolors5.default.dim(" Build cache: miss (fresh build)"));
119750
119874
  }
119751
119875
  if (typeof result.deployment?.imageSizeBytes === "number") {
119752
- await writeLine2(ctx, import_picocolors4.default.dim(` Image size: ${formatBytes(result.deployment.imageSizeBytes)}`));
119876
+ await writeLine2(ctx, import_picocolors5.default.dim(` Image size: ${formatBytes(result.deployment.imageSizeBytes)}`));
119753
119877
  }
119754
119878
  }
119755
119879
  });
119756
119880
  cli.command("rename <name> [app]", "Rename an app (changes its subdomain)").action(async (name, app) => {
119757
119881
  const result = await rename2(ctx, { app, name });
119758
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} Renamed to ${result.name} — now live at ${result.url}`);
119882
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} Renamed to ${result.name} — now live at ${result.url}`);
119759
119883
  await writeLine2(ctx, ` Previous name: ${result.previousName} (reserved for 60 days)`);
119760
119884
  if (result.tomlUpdated) {
119761
119885
  await writeLine2(ctx, ` percher.toml updated: name = "${result.name}"`);
119762
119886
  } else {
119763
- await writeLine2(ctx, ` ${import_picocolors4.default.yellow("!")} Update percher.toml before the next publish: name = "${result.name}"`);
119887
+ await writeLine2(ctx, ` ${import_picocolors5.default.yellow("!")} Update percher.toml before the next publish: name = "${result.name}"`);
119764
119888
  }
119765
- await writeLine2(ctx, ` ${import_picocolors4.default.dim(`Next rename available in ${result.cooldownDays} days`)}`);
119889
+ await writeLine2(ctx, ` ${import_picocolors5.default.dim(`Next rename available in ${result.cooldownDays} days`)}`);
119766
119890
  });
119767
119891
  cli.command("open [app]", "Open the app URL in your browser").action(async (app) => {
119768
119892
  const url2 = await open(ctx, { app });
@@ -119775,7 +119899,7 @@ function registerAppsCommands(cli, ctx) {
119775
119899
  cli.command("create [name]", "Create a new app from a template").option("--template <template>", "Template to use (node, python, static, nextjs, hono, express)").option("--force", "Overwrite existing files in target directory").option("--list-templates", "List available templates").action(async (name, options) => {
119776
119900
  if (options.listTemplates) {
119777
119901
  const templates = listTemplates();
119778
- await writeLine2(ctx, import_picocolors4.default.bold("Available templates:"));
119902
+ await writeLine2(ctx, import_picocolors5.default.bold("Available templates:"));
119779
119903
  for (const t of templates) {
119780
119904
  await writeLine2(ctx, ` ${t}`);
119781
119905
  }
@@ -119786,7 +119910,7 @@ function registerAppsCommands(cli, ctx) {
119786
119910
  }
119787
119911
  if (!options.template) {
119788
119912
  const templates = listTemplates();
119789
- await writeLine2(ctx, `${import_picocolors4.default.red("✗")} --template is required. Available: ${templates.join(", ")}`);
119913
+ await writeLine2(ctx, `${import_picocolors5.default.red("✗")} --template is required. Available: ${templates.join(", ")}`);
119790
119914
  return;
119791
119915
  }
119792
119916
  const result = await create(ctx, {
@@ -119794,9 +119918,9 @@ function registerAppsCommands(cli, ctx) {
119794
119918
  template: options.template,
119795
119919
  force: options.force
119796
119920
  });
119797
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} created ${result.name} from ${result.template}`);
119921
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} created ${result.name} from ${result.template}`);
119798
119922
  for (const f of result.files) {
119799
- await writeLine2(ctx, ` ${import_picocolors4.default.dim(f)}`);
119923
+ await writeLine2(ctx, ` ${import_picocolors5.default.dim(f)}`);
119800
119924
  }
119801
119925
  await writeLine2(ctx, "");
119802
119926
  await writeLine2(ctx, "Next steps:");
@@ -119806,29 +119930,29 @@ function registerAppsCommands(cli, ctx) {
119806
119930
  cli.command("delete [app]", "Delete an app permanently").option("--yes", "Skip confirmation prompt").action(async (app, options) => {
119807
119931
  const name = app ?? readPercherTomlAppName(ctx.cwd);
119808
119932
  if (!name) {
119809
- await writeLine2(ctx, `${import_picocolors4.default.red("✗")} No app specified and no percher.toml found.`);
119933
+ await writeLine2(ctx, `${import_picocolors5.default.red("✗")} No app specified and no percher.toml found.`);
119810
119934
  throw new SilentExitError;
119811
119935
  }
119812
119936
  if (!options.yes) {
119813
- const answer = await ctx.prompt(`${import_picocolors4.default.red("!")} This will permanently delete ${import_picocolors4.default.bold(name)} and all its data. Type the app name to confirm:`);
119937
+ const answer = await ctx.prompt(`${import_picocolors5.default.red("!")} This will permanently delete ${import_picocolors5.default.bold(name)} and all its data. Type the app name to confirm:`);
119814
119938
  if (answer.trim() !== name) {
119815
119939
  await writeLine2(ctx, "Cancelled.");
119816
119940
  return;
119817
119941
  }
119818
119942
  }
119819
119943
  await deleteApp(ctx, { app: name, confirm: true });
119820
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} Deleted ${name}`);
119944
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} Deleted ${name}`);
119821
119945
  });
119822
119946
  cli.command("resume [app]", "Resume an app suspended for inactivity").action(async (app) => {
119823
119947
  const result = await resume(ctx, { app });
119824
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${result.message}`);
119948
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${result.message}`);
119825
119949
  });
119826
119950
  cli.command("claim", "Claim an anonymous app into your account").option("--app <name>", "App name (defaults to the persisted anon state or percher.toml)").option("--claim-token <token>", "Claim token from the anonymous publish").action(async (options) => {
119827
119951
  const result = await claim(ctx, { app: options.app, claimToken: options.claimToken });
119828
119952
  if (result.status === "claimed") {
119829
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${result.summary}`);
119953
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${result.summary}`);
119830
119954
  } else {
119831
- await writeLine2(ctx, `${import_picocolors4.default.yellow("!")} ${result.summary}`);
119955
+ await writeLine2(ctx, `${import_picocolors5.default.yellow("!")} ${result.summary}`);
119832
119956
  }
119833
119957
  });
119834
119958
  cli.command("inspect-link [app]", "Create a public read-only inspect link (or --revoke it)").option("--app <name>", "App name (defaults to percher.toml)").option("--revoke", "Revoke the app's inspect link instead of creating one").action(async (app, options) => {
@@ -119836,49 +119960,49 @@ function registerAppsCommands(cli, ctx) {
119836
119960
  app: app ?? options.app,
119837
119961
  revoke: options.revoke
119838
119962
  });
119839
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${result.summary}`);
119963
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${result.summary}`);
119840
119964
  });
119841
119965
  cli.command("cache <action> [app]", "Manage the app's build cache (reset)").action(async (action, app) => {
119842
119966
  if (action !== "reset") {
119843
119967
  throw new Error(`Unknown cache action "${action}". Use: percher cache reset [app]`);
119844
119968
  }
119845
119969
  const result = await cacheReset(ctx, { app });
119846
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${result.message}`);
119970
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${result.message}`);
119847
119971
  });
119848
119972
  cli.command("cache reset [app]", "Reset the app's build cache (next deploy runs cold)").action(async (app) => {
119849
119973
  const result = await cacheReset(ctx, { app });
119850
- await writeLine2(ctx, `${import_picocolors4.default.green("✓")} ${result.message}`);
119974
+ await writeLine2(ctx, `${import_picocolors5.default.green("✓")} ${result.message}`);
119851
119975
  });
119852
119976
  cli.command("doctor [app]", "Check app health and connectivity").option("--json", "Output as JSON").action(async (app, options) => {
119853
119977
  const result = await doctor(ctx, { app, json: options.json });
119854
119978
  if (options.json) {
119855
119979
  await writeLine2(ctx, JSON.stringify(result, null, 2));
119856
119980
  } else {
119857
- await writeLine2(ctx, `${import_picocolors4.default.bold("Percher Doctor")}`);
119981
+ await writeLine2(ctx, `${import_picocolors5.default.bold("Percher Doctor")}`);
119858
119982
  await writeLine2(ctx, "──────────────");
119859
119983
  await writeLine2(ctx, "");
119860
119984
  for (const check2 of result.checks) {
119861
- const icon = check2.status === "pass" ? import_picocolors4.default.green("✓") : check2.status === "fail" ? import_picocolors4.default.red("✗") : check2.status === "warn" ? import_picocolors4.default.yellow("!") : import_picocolors4.default.dim("—");
119862
- await writeLine2(ctx, `${icon} ${import_picocolors4.default.bold(check2.name.padEnd(20))} ${check2.message}`);
119985
+ const icon = check2.status === "pass" ? import_picocolors5.default.green("✓") : check2.status === "fail" ? import_picocolors5.default.red("✗") : check2.status === "warn" ? import_picocolors5.default.yellow("!") : import_picocolors5.default.dim("—");
119986
+ await writeLine2(ctx, `${icon} ${import_picocolors5.default.bold(check2.name.padEnd(20))} ${check2.message}`);
119863
119987
  }
119864
119988
  await writeLine2(ctx, "");
119865
119989
  const counter = `${result.passed}/${result.total} checks passed`;
119866
- const counterColor = result.status === "ok" ? import_picocolors4.default.green : result.status === "in_progress" ? import_picocolors4.default.yellow : import_picocolors4.default.red;
119990
+ const counterColor = result.status === "ok" ? import_picocolors5.default.green : result.status === "in_progress" ? import_picocolors5.default.yellow : import_picocolors5.default.red;
119867
119991
  await writeLine2(ctx, counterColor(counter));
119868
119992
  await writeLine2(ctx, result.summary);
119869
119993
  if (result.suspectDeploy) {
119870
119994
  const s = result.suspectDeploy;
119871
119995
  const head = (s.commitSha ?? s.deployId).slice(0, 10);
119872
119996
  await writeLine2(ctx, "");
119873
- await writeLine2(ctx, import_picocolors4.default.bold(`Likely broken by a deploy (${s.confidence} confidence):`));
119997
+ await writeLine2(ctx, import_picocolors5.default.bold(`Likely broken by a deploy (${s.confidence} confidence):`));
119874
119998
  await writeLine2(ctx, ` Suspect: ${head}`);
119875
119999
  if (s.lastHealthyCommitSha) {
119876
120000
  const base = s.lastHealthyCommitSha.slice(0, 10);
119877
120001
  await writeLine2(ctx, ` Last working: ${base}`);
119878
- await writeLine2(ctx, import_picocolors4.default.dim(` See what changed: percher diff ${base} ${head}${app ? ` --app ${app}` : ""}`));
120002
+ await writeLine2(ctx, import_picocolors5.default.dim(` See what changed: percher diff ${base} ${head}${app ? ` --app ${app}` : ""}`));
119879
120003
  }
119880
120004
  for (const e of s.evidence)
119881
- await writeLine2(ctx, import_picocolors4.default.dim(` · ${e}`));
120005
+ await writeLine2(ctx, import_picocolors5.default.dim(` · ${e}`));
119882
120006
  }
119883
120007
  }
119884
120008
  if (result.status === "blocked" || result.status === "needs_action") {
@@ -119890,7 +120014,7 @@ function registerAppsCommands(cli, ctx) {
119890
120014
  const onSignal = () => controller.abort();
119891
120015
  process.on("SIGINT", onSignal);
119892
120016
  process.on("SIGTERM", onSignal);
119893
- await writeLine2(ctx, import_picocolors4.default.bold("Percher Dev"));
120017
+ await writeLine2(ctx, import_picocolors5.default.bold("Percher Dev"));
119894
120018
  await writeLine2(ctx, "───────────");
119895
120019
  await writeLine2(ctx, "");
119896
120020
  try {
@@ -119898,7 +120022,7 @@ function registerAppsCommands(cli, ctx) {
119898
120022
  signal: controller.signal,
119899
120023
  async writeLine(line) {
119900
120024
  const timestamp = new Date().toLocaleTimeString("sv-SE", { hour12: false });
119901
- await writeLine2(ctx, `${import_picocolors4.default.dim(`[${timestamp}]`)} ${line}`);
120025
+ await writeLine2(ctx, `${import_picocolors5.default.dim(`[${timestamp}]`)} ${line}`);
119902
120026
  },
119903
120027
  async onFilesChanged(_files) {
119904
120028
  if (options.confirm) {
@@ -119913,12 +120037,12 @@ function registerAppsCommands(cli, ctx) {
119913
120037
  process.removeListener("SIGTERM", onSignal);
119914
120038
  }
119915
120039
  await writeLine2(ctx, "");
119916
- await writeLine2(ctx, import_picocolors4.default.dim("Dev session ended."));
120040
+ await writeLine2(ctx, import_picocolors5.default.dim("Dev session ended."));
119917
120041
  });
119918
120042
  }
119919
120043
 
119920
120044
  // src/commands/auth.ts
119921
- var import_picocolors5 = __toESM(require_picocolors(), 1);
120045
+ var import_picocolors6 = __toESM(require_picocolors(), 1);
119922
120046
  function registerAuthCommands(cli, ctx) {
119923
120047
  cli.command("login", "Authenticate with Percher").option("--token <token>", "Dev: skip browser, use a token").option("--api-url <url>", "Override API URL").option("--force", "Pair again even if already signed in (switch accounts)").action(async (options) => {
119924
120048
  await login(ctx, {
@@ -119929,7 +120053,7 @@ function registerAuthCommands(cli, ctx) {
119929
120053
  });
119930
120054
  cli.command("whoami", "Show the signed-in user").action(async () => {
119931
120055
  if (!ctx.client.token) {
119932
- await writeLine2(ctx, `${import_picocolors5.default.red("✗")} Not logged in. Run: percher login`);
120056
+ await writeLine2(ctx, `${import_picocolors6.default.red("✗")} Not logged in. Run: percher login`);
119933
120057
  throw new SilentExitError;
119934
120058
  }
119935
120059
  const me = await whoami(ctx);
@@ -119938,7 +120062,7 @@ function registerAuthCommands(cli, ctx) {
119938
120062
  }
119939
120063
 
119940
120064
  // src/commands/data.ts
119941
- var import_picocolors6 = __toESM(require_picocolors(), 1);
120065
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
119942
120066
  function registerDataCommands(cli, ctx, argv) {
119943
120067
  cli.command("data [app] [...rest]", "Show app data status and collections").option("--json", "Output as JSON").option("--reveal", "(reset-superuser only) also print the plaintext password once").option("--out <path>", "(export only) Output file (default: ./<app>-pb-<timestamp>.tar.gz)").option("--provider <name>", "(oauth only) OAuth provider (default: google)").option("--app <name>", "(oauth only) target app (default: read percher.toml)").option("--client-id <id>", "(oauth only) OAuth client id").option("--client-secret <secret>", "(oauth only) OAuth client secret").option("--remove", "(oauth only) remove the provider instead of setting it").action(async (app, _rest, options) => {
119944
120068
  if (app === "reset-superuser") {
@@ -119956,7 +120080,7 @@ function registerDataCommands(cli, ctx, argv) {
119956
120080
  const out = outIdx >= 0 ? argv[outIdx + 1] : undefined;
119957
120081
  const result2 = await dataExport(ctx, { app: realApp, out });
119958
120082
  const mb = (result2.bytes / 1024 / 1024).toFixed(1);
119959
- await writeLine2(ctx, `${import_picocolors6.default.green("✓")} Exported ${result2.app}`);
120083
+ await writeLine2(ctx, `${import_picocolors7.default.green("✓")} Exported ${result2.app}`);
119960
120084
  await writeLine2(ctx, ` Path: ${result2.path}`);
119961
120085
  await writeLine2(ctx, ` Size: ${mb} MB`);
119962
120086
  return;
@@ -119972,19 +120096,19 @@ function registerDataCommands(cli, ctx, argv) {
119972
120096
  remove: options.remove
119973
120097
  });
119974
120098
  if (result2.action === "removed") {
119975
- await writeLine2(ctx, `${import_picocolors6.default.green("✓")} Removed ${result2.provider} sign-in.`);
120099
+ await writeLine2(ctx, `${import_picocolors7.default.green("✓")} Removed ${result2.provider} sign-in.`);
119976
120100
  } else if (result2.action === "set") {
119977
- await writeLine2(ctx, `${import_picocolors6.default.green("✓")} ${result2.provider} sign-in enabled.`);
120101
+ await writeLine2(ctx, `${import_picocolors7.default.green("✓")} ${result2.provider} sign-in enabled.`);
119978
120102
  await writeLine2(ctx, "");
119979
- await writeLine2(ctx, import_picocolors6.default.bold("One more step — add this redirect URI in the provider console:"));
120103
+ await writeLine2(ctx, import_picocolors7.default.bold("One more step — add this redirect URI in the provider console:"));
119980
120104
  await writeLine2(ctx, ` ${result2.redirectUri}`);
119981
120105
  } else {
119982
120106
  if (!result2.providers?.length) {
119983
- await writeLine2(ctx, import_picocolors6.default.dim("No OAuth providers configured."));
120107
+ await writeLine2(ctx, import_picocolors7.default.dim("No OAuth providers configured."));
119984
120108
  } else {
119985
- await writeLine2(ctx, import_picocolors6.default.bold("Configured providers"));
120109
+ await writeLine2(ctx, import_picocolors7.default.bold("Configured providers"));
119986
120110
  for (const p of result2.providers) {
119987
- await writeLine2(ctx, ` ${p.provider.padEnd(12)} ${import_picocolors6.default.dim(p.clientId)}`);
120111
+ await writeLine2(ctx, ` ${p.provider.padEnd(12)} ${import_picocolors7.default.dim(p.clientId)}`);
119988
120112
  }
119989
120113
  }
119990
120114
  await writeLine2(ctx, "");
@@ -119997,12 +120121,12 @@ function registerDataCommands(cli, ctx, argv) {
119997
120121
  await writeLine2(ctx, JSON.stringify(result, null, 2));
119998
120122
  } else {
119999
120123
  const version2 = result.version ? ` Version: ${result.version}` : "";
120000
- await writeLine2(ctx, `${import_picocolors6.default.bold("Data")} Mode: ${result.mode} Status: ${result.status}${version2}`);
120124
+ await writeLine2(ctx, `${import_picocolors7.default.bold("Data")} Mode: ${result.mode} Status: ${result.status}${version2}`);
120001
120125
  await writeLine2(ctx, "");
120002
120126
  if (result.collections.length > 0) {
120003
- await writeLine2(ctx, import_picocolors6.default.bold("Collections"));
120127
+ await writeLine2(ctx, import_picocolors7.default.bold("Collections"));
120004
120128
  for (const col of result.collections) {
120005
- await writeLine2(ctx, ` ${col.name.padEnd(20)} ${import_picocolors6.default.dim(col.type)}`);
120129
+ await writeLine2(ctx, ` ${col.name.padEnd(20)} ${import_picocolors7.default.dim(col.type)}`);
120006
120130
  }
120007
120131
  await writeLine2(ctx, "");
120008
120132
  }
@@ -120013,7 +120137,7 @@ function registerDataCommands(cli, ctx, argv) {
120013
120137
  }
120014
120138
  if (result.warning) {
120015
120139
  await writeLine2(ctx, "");
120016
- await writeLine2(ctx, import_picocolors6.default.yellow(`⚠ ${result.warning}`));
120140
+ await writeLine2(ctx, import_picocolors7.default.yellow(`⚠ ${result.warning}`));
120017
120141
  }
120018
120142
  }
120019
120143
  });
@@ -120026,19 +120150,19 @@ function registerDataCommands(cli, ctx, argv) {
120026
120150
  cli.command("backups [app]", "List available backups for an app").action(async (app) => {
120027
120151
  const { backups, skipped } = await listBackups(ctx, { app });
120028
120152
  if (backups.length === 0) {
120029
- await writeLine2(ctx, `${import_picocolors6.default.dim("No backups found.")}`);
120153
+ await writeLine2(ctx, `${import_picocolors7.default.dim("No backups found.")}`);
120030
120154
  if (skipped.length > 0) {
120031
- await writeLine2(ctx, import_picocolors6.default.yellow(`(${skipped.length} sidecar(s) skipped — see ops logs)`));
120155
+ await writeLine2(ctx, import_picocolors7.default.yellow(`(${skipped.length} sidecar(s) skipped — see ops logs)`));
120032
120156
  }
120033
120157
  return;
120034
120158
  }
120035
120159
  for (const b of backups) {
120036
120160
  const components = b.apps[0]?.components.join(", ") ?? "—";
120037
120161
  const size = b.tarball_size_bytes < 1024 * 1024 ? `${Math.round(b.tarball_size_bytes / 1024)} KB` : `${(b.tarball_size_bytes / (1024 * 1024)).toFixed(1)} MB`;
120038
- await writeLine2(ctx, `${import_picocolors6.default.green("●")} ${import_picocolors6.default.bold(b.backup_id)} ${import_picocolors6.default.dim(size.padStart(10))} ${import_picocolors6.default.dim(`[${components}]`)}`);
120162
+ await writeLine2(ctx, `${import_picocolors7.default.green("●")} ${import_picocolors7.default.bold(b.backup_id)} ${import_picocolors7.default.dim(size.padStart(10))} ${import_picocolors7.default.dim(`[${components}]`)}`);
120039
120163
  }
120040
120164
  if (skipped.length > 0) {
120041
- await writeLine2(ctx, import_picocolors6.default.yellow(`
120165
+ await writeLine2(ctx, import_picocolors7.default.yellow(`
120042
120166
  ${skipped.length} sidecar(s) skipped — older backups predating the manifest schema.`));
120043
120167
  }
120044
120168
  });
@@ -120051,19 +120175,19 @@ ${skipped.length} sidecar(s) skipped — older backups predating the manifest sc
120051
120175
  dryRun: options.dryRun ?? false
120052
120176
  });
120053
120177
  if ("kind" in result) {
120054
- await writeLine2(ctx, `${import_picocolors6.default.cyan("dry-run")} would restore ${import_picocolors6.default.bold(result.app)} from ${import_picocolors6.default.bold(result.resolvedBackup.backup_id)}`);
120055
- await writeLine2(ctx, ` components: ${import_picocolors6.default.dim(result.components.join(", "))}`);
120056
- await writeLine2(ctx, ` sha: ${import_picocolors6.default.dim(`${result.resolvedBackup.tarball_sha256.slice(0, 16)}…`)}`);
120057
- await writeLine2(ctx, import_picocolors6.default.dim(`
120178
+ await writeLine2(ctx, `${import_picocolors7.default.cyan("dry-run")} would restore ${import_picocolors7.default.bold(result.app)} from ${import_picocolors7.default.bold(result.resolvedBackup.backup_id)}`);
120179
+ await writeLine2(ctx, ` components: ${import_picocolors7.default.dim(result.components.join(", "))}`);
120180
+ await writeLine2(ctx, ` sha: ${import_picocolors7.default.dim(`${result.resolvedBackup.tarball_sha256.slice(0, 16)}…`)}`);
120181
+ await writeLine2(ctx, import_picocolors7.default.dim(`
120058
120182
  Run without --dry-run to actually restore. Expect 30–120s of downtime.`));
120059
120183
  return;
120060
120184
  }
120061
- await writeLine2(ctx, `${import_picocolors6.default.green("✓")} restored from ${result.sourceBackupId} (job ${result.id.slice(0, 12)})`);
120185
+ await writeLine2(ctx, `${import_picocolors7.default.green("✓")} restored from ${result.sourceBackupId} (job ${result.id.slice(0, 12)})`);
120062
120186
  });
120063
120187
  }
120064
120188
 
120065
120189
  // src/commands/deploys.ts
120066
- var import_picocolors7 = __toESM(require_picocolors(), 1);
120190
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
120067
120191
  function registerDeploysCommands(cli, ctx, argv) {
120068
120192
  cli.command("deploys [app] [...rest]", "List recent deploys (id, status, created, error) for an app").option("--limit <n>", "Number of deploys to show (default: 10, max: 100)").option("--app <name>", "(inspect only) App name (defaults to percher.toml in cwd)").option("--latest-failed", "(inspect only) Resolve to the most recent failed deploy").action(async (app, _rest, options) => {
120069
120193
  if (app === "inspect") {
@@ -120079,18 +120203,18 @@ function registerDeploysCommands(cli, ctx, argv) {
120079
120203
  const limit = options.limit ? Number.parseInt(options.limit, 10) : undefined;
120080
120204
  const list = await deploysList(ctx, { app, limit });
120081
120205
  if (list.length === 0) {
120082
- await writeLine2(ctx, import_picocolors7.default.dim("No deploys yet."));
120206
+ await writeLine2(ctx, import_picocolors8.default.dim("No deploys yet."));
120083
120207
  return;
120084
120208
  }
120085
- await writeLine2(ctx, `${import_picocolors7.default.bold("ID".padEnd(22))} ${import_picocolors7.default.bold("STATUS".padEnd(10))} ${import_picocolors7.default.bold("CREATED".padEnd(20))} ${import_picocolors7.default.bold("ERROR")}`);
120209
+ await writeLine2(ctx, `${import_picocolors8.default.bold("ID".padEnd(22))} ${import_picocolors8.default.bold("STATUS".padEnd(10))} ${import_picocolors8.default.bold("CREATED".padEnd(20))} ${import_picocolors8.default.bold("ERROR")}`);
120086
120210
  for (const d of list) {
120087
120211
  const id = d.id.padEnd(22);
120088
120212
  const status2 = d.status.padEnd(10);
120089
120213
  const created = formatDeployTimestamp(d.createdAt).padEnd(20);
120090
120214
  const err = d.errorMessage ? d.errorMessage.split(`
120091
- `)[0]?.slice(0, 60) ?? "—" : import_picocolors7.default.dim("—");
120092
- const colored = d.status === "live" ? import_picocolors7.default.green(status2) : d.status === "failed" ? import_picocolors7.default.red(status2) : import_picocolors7.default.yellow(status2);
120093
- await writeLine2(ctx, `${import_picocolors7.default.dim(id)} ${colored} ${import_picocolors7.default.dim(created)} ${err}`);
120215
+ `)[0]?.slice(0, 60) ?? "—" : import_picocolors8.default.dim("—");
120216
+ const colored = d.status === "live" ? import_picocolors8.default.green(status2) : d.status === "failed" ? import_picocolors8.default.red(status2) : import_picocolors8.default.yellow(status2);
120217
+ await writeLine2(ctx, `${import_picocolors8.default.dim(id)} ${colored} ${import_picocolors8.default.dim(created)} ${err}`);
120094
120218
  }
120095
120219
  });
120096
120220
  cli.command("deploys inspect [id]", "Inspect a deploy: status, error, build log").option("--app <name>", "App name (defaults to percher.toml in cwd)").option("--latest-failed", "Resolve to the most recent failed deploy").action(async (id, options) => {
@@ -120102,7 +120226,7 @@ function registerDeploysCommands(cli, ctx, argv) {
120102
120226
  commitSha: sha,
120103
120227
  toLastGood: Boolean(options.lastGood)
120104
120228
  });
120105
- await writeLine2(ctx, `${import_picocolors7.default.green("✓")} rolled back to ${result.rolledBackTo.slice(0, 10)} (status: ${result.status})`);
120229
+ await writeLine2(ctx, `${import_picocolors8.default.green("✓")} rolled back to ${result.rolledBackTo.slice(0, 10)} (status: ${result.status})`);
120106
120230
  });
120107
120231
  cli.command("reproduce <deployId>", "Download a deploy's source and reproduce the build locally").option("--app <name>", "App name (defaults to percher.toml app.name)").action(async (deployId, options) => {
120108
120232
  const result = await reproduce(ctx, {
@@ -120110,27 +120234,27 @@ function registerDeploysCommands(cli, ctx, argv) {
120110
120234
  deployId
120111
120235
  });
120112
120236
  if (result.status === "needs_login") {
120113
- await writeLine2(ctx, `${import_picocolors7.default.red("✗")} Not logged in. Run: percher login`);
120237
+ await writeLine2(ctx, `${import_picocolors8.default.red("✗")} Not logged in. Run: percher login`);
120114
120238
  throw new SilentExitError;
120115
120239
  }
120116
120240
  if (result.status === "failed") {
120117
- await writeLine2(ctx, `${import_picocolors7.default.red("✗")} ${result.error?.title ?? "Reproduce failed"}`);
120241
+ await writeLine2(ctx, `${import_picocolors8.default.red("✗")} ${result.error?.title ?? "Reproduce failed"}`);
120118
120242
  if (result.error?.explanation) {
120119
120243
  await writeLine2(ctx, ` ${result.error.explanation}`);
120120
120244
  }
120121
120245
  if (result.error?.suggestion) {
120122
- await writeLine2(ctx, ` ${import_picocolors7.default.dim(result.error.suggestion)}`);
120246
+ await writeLine2(ctx, ` ${import_picocolors8.default.dim(result.error.suggestion)}`);
120123
120247
  }
120124
120248
  throw new SilentExitError;
120125
120249
  }
120126
- await writeLine2(ctx, `${import_picocolors7.default.green("✓")} ${result.summary}`);
120250
+ await writeLine2(ctx, `${import_picocolors8.default.green("✓")} ${result.summary}`);
120127
120251
  if (result.currentAppEnvKeys && result.currentAppEnvKeys.length > 0) {
120128
120252
  await writeLine2(ctx, "");
120129
- await writeLine2(ctx, import_picocolors7.default.bold("Current app env keys:"));
120253
+ await writeLine2(ctx, import_picocolors8.default.bold("Current app env keys:"));
120130
120254
  for (const k of result.currentAppEnvKeys) {
120131
120255
  await writeLine2(ctx, ` ${k}`);
120132
120256
  }
120133
- await writeLine2(ctx, import_picocolors7.default.dim(" (values not exfiltrated — set them locally before building. Note: this is the CURRENT app env, not a deploy-time snapshot.)"));
120257
+ await writeLine2(ctx, import_picocolors8.default.dim(" (values not exfiltrated — set them locally before building. Note: this is the CURRENT app env, not a deploy-time snapshot.)"));
120134
120258
  }
120135
120259
  });
120136
120260
  cli.command("logs [app]", "Stream application logs").option("--tail <n>", "Return the last N lines and exit", { type: [Number] }).option("--no-follow", "Don't stream new lines").option("--build [deployId]", "Show the BUILD log instead of runtime logs (defaults to latest failed deploy)").action(async (app, options) => {
@@ -120142,12 +120266,12 @@ function registerDeploysCommands(cli, ctx, argv) {
120142
120266
  latestFailed: deployId ? undefined : true
120143
120267
  });
120144
120268
  const { deploy, buildLog } = result;
120145
- await writeLine2(ctx, `${import_picocolors7.default.bold("Deploy:")} ${deploy.id}`);
120146
- await writeLine2(ctx, `${import_picocolors7.default.bold("Status:")} ${formatStatus(deploy.status)}`);
120147
- await writeLine2(ctx, `${import_picocolors7.default.bold("Created:")} ${formatDeployTimestamp(deploy.createdAt)}`);
120269
+ await writeLine2(ctx, `${import_picocolors8.default.bold("Deploy:")} ${deploy.id}`);
120270
+ await writeLine2(ctx, `${import_picocolors8.default.bold("Status:")} ${formatStatus(deploy.status)}`);
120271
+ await writeLine2(ctx, `${import_picocolors8.default.bold("Created:")} ${formatDeployTimestamp(deploy.createdAt)}`);
120148
120272
  if (deploy.errorMessage) {
120149
120273
  await writeLine2(ctx, "");
120150
- await writeLine2(ctx, import_picocolors7.default.bold(import_picocolors7.default.red("Error:")));
120274
+ await writeLine2(ctx, import_picocolors8.default.bold(import_picocolors8.default.red("Error:")));
120151
120275
  for (const line of deploy.errorMessage.split(`
120152
120276
  `)) {
120153
120277
  await writeLine2(ctx, ` ${line}`);
@@ -120159,13 +120283,13 @@ function registerDeploysCommands(cli, ctx, argv) {
120159
120283
  `);
120160
120284
  const tailN = options.tail ? Number(options.tail) : 200;
120161
120285
  const tail = lines.slice(-tailN);
120162
- const header = lines.length > tail.length ? `${import_picocolors7.default.bold("Build log")} ${import_picocolors7.default.dim(`(last ${tail.length} of ${lines.length} lines)`)}` : import_picocolors7.default.bold("Build log");
120286
+ const header = lines.length > tail.length ? `${import_picocolors8.default.bold("Build log")} ${import_picocolors8.default.dim(`(last ${tail.length} of ${lines.length} lines)`)}` : import_picocolors8.default.bold("Build log");
120163
120287
  await writeLine2(ctx, header);
120164
120288
  for (const line of tail) {
120165
120289
  await writeLine2(ctx, ` ${line}`);
120166
120290
  }
120167
120291
  } else {
120168
- await writeLine2(ctx, import_picocolors7.default.dim("(no build log available for this deploy)"));
120292
+ await writeLine2(ctx, import_picocolors8.default.dim("(no build log available for this deploy)"));
120169
120293
  }
120170
120294
  return;
120171
120295
  }
@@ -120200,16 +120324,16 @@ function registerDeploysCommands(cli, ctx, argv) {
120200
120324
  patch: options.patch
120201
120325
  });
120202
120326
  const { summary, files } = result;
120203
- await writeLine2(ctx, `${summary.filesChanged} file${summary.filesChanged === 1 ? "" : "s"} changed · ${import_picocolors7.default.green(`+${summary.additions}`)} ${import_picocolors7.default.red(`−${summary.deletions}`)} (${result.base.slice(0, 10)} → ${result.head.slice(0, 10)})`);
120327
+ await writeLine2(ctx, `${summary.filesChanged} file${summary.filesChanged === 1 ? "" : "s"} changed · ${import_picocolors8.default.green(`+${summary.additions}`)} ${import_picocolors8.default.red(`−${summary.deletions}`)} (${result.base.slice(0, 10)} → ${result.head.slice(0, 10)})`);
120204
120328
  for (const f of files) {
120205
- const stat2 = f.binary ? import_picocolors7.default.dim("binary") : `${import_picocolors7.default.green(`+${f.additions}`)} ${import_picocolors7.default.red(`−${f.deletions}`)}`;
120206
- await writeLine2(ctx, ` ${f.status} ${f.path} ${stat2}${f.truncated ? import_picocolors7.default.dim(" (truncated)") : ""}`);
120329
+ const stat2 = f.binary ? import_picocolors8.default.dim("binary") : `${import_picocolors8.default.green(`+${f.additions}`)} ${import_picocolors8.default.red(`−${f.deletions}`)}`;
120330
+ await writeLine2(ctx, ` ${f.status} ${f.path} ${stat2}${f.truncated ? import_picocolors8.default.dim(" (truncated)") : ""}`);
120207
120331
  if (options.patch && f.patch) {
120208
120332
  await writeLine2(ctx, f.patch.replace(/\n$/, ""));
120209
120333
  }
120210
120334
  }
120211
120335
  if (result.truncated) {
120212
- await writeLine2(ctx, import_picocolors7.default.dim("… some patches omitted (response size limit)"));
120336
+ await writeLine2(ctx, import_picocolors8.default.dim("… some patches omitted (response size limit)"));
120213
120337
  }
120214
120338
  });
120215
120339
  cli.command("preview <action> [arg] [arg2]", "Preview deploys: list | promote <id> | discard <id> | branch set <branch> | branch unset | branch show").option("--app <name>", "App name (overrides percher.toml)").action(async (action, arg, arg2, options) => {
@@ -120221,38 +120345,38 @@ function registerDeploysCommands(cli, ctx, argv) {
120221
120345
  await runPreviewList(options.app);
120222
120346
  return;
120223
120347
  }
120224
- const appRef = options.app ?? readPercherTomlAppName(ctx.cwd);
120225
- if (!appRef) {
120348
+ const appRef2 = options.app ?? readPercherTomlAppName(ctx.cwd);
120349
+ if (!appRef2) {
120226
120350
  throw new Error("App name required. Use --app or run from a project directory.");
120227
120351
  }
120228
120352
  if (action === "promote") {
120229
120353
  if (!arg)
120230
120354
  throw new Error("Deploy ID required. Usage: percher preview promote <id>");
120231
- const result = await ctx.client.apps.promotePreview(appRef, arg);
120232
- await writeLine2(ctx, `${import_picocolors7.default.green("✓")} Preview promoted to live. Status: ${result.status}`);
120355
+ const result = await ctx.client.apps.promotePreview(appRef2, arg);
120356
+ await writeLine2(ctx, `${import_picocolors8.default.green("✓")} Preview promoted to live. Status: ${result.status}`);
120233
120357
  } else if (action === "discard") {
120234
120358
  if (!arg)
120235
120359
  throw new Error("Deploy ID required. Usage: percher preview discard <id>");
120236
- await ctx.client.apps.discardPreview(appRef, arg);
120237
- await writeLine2(ctx, `${import_picocolors7.default.green("✓")} Preview discarded.`);
120360
+ await ctx.client.apps.discardPreview(appRef2, arg);
120361
+ await writeLine2(ctx, `${import_picocolors8.default.green("✓")} Preview discarded.`);
120238
120362
  } else {
120239
120363
  throw new Error(`Unknown preview action "${action}". Use list | promote | discard | branch.`);
120240
120364
  }
120241
120365
  });
120242
120366
  async function runPreviewList(app) {
120243
120367
  const { app: appName, previews } = await previewList(ctx, { app });
120244
- await writeLine2(ctx, `Preview surfaces for ${import_picocolors7.default.bold(appName)}:`);
120368
+ await writeLine2(ctx, `Preview surfaces for ${import_picocolors8.default.bold(appName)}:`);
120245
120369
  if (previews.length === 0) {
120246
- await writeLine2(ctx, import_picocolors7.default.dim(" (no preview surfaces reported by the server)"));
120370
+ await writeLine2(ctx, import_picocolors8.default.dim(" (no preview surfaces reported by the server)"));
120247
120371
  return;
120248
120372
  }
120249
120373
  for (const p of previews) {
120250
120374
  const label = p.kind.padEnd(14);
120251
- const statusColour = p.status === "ready" || p.status === "enabled" || p.status === "available" || p.status === "active" ? import_picocolors7.default.green(p.status) : p.status === "error" ? import_picocolors7.default.red(p.status) : import_picocolors7.default.dim(p.status);
120252
- await writeLine2(ctx, ` ${import_picocolors7.default.bold(label)} ${statusColour}`);
120375
+ const statusColour = p.status === "ready" || p.status === "enabled" || p.status === "available" || p.status === "active" ? import_picocolors8.default.green(p.status) : p.status === "error" ? import_picocolors8.default.red(p.status) : import_picocolors8.default.dim(p.status);
120376
+ await writeLine2(ctx, ` ${import_picocolors8.default.bold(label)} ${statusColour}`);
120253
120377
  if (p.url)
120254
120378
  await writeLine2(ctx, ` url: ${p.url}`);
120255
- await writeLine2(ctx, import_picocolors7.default.dim(` how: ${p.howTriggered}`));
120379
+ await writeLine2(ctx, import_picocolors8.default.dim(` how: ${p.howTriggered}`));
120256
120380
  }
120257
120381
  }
120258
120382
  async function runPreviewBranch(sub, branch, app) {
@@ -120260,25 +120384,25 @@ function registerDeploysCommands(cli, ctx, argv) {
120260
120384
  if (!branch)
120261
120385
  throw new Error("Branch required. Usage: percher preview branch set <branch>");
120262
120386
  const r = await previewBranchSet(ctx, { app, branch });
120263
- await writeLine2(ctx, `${import_picocolors7.default.green("✓")} Preview branch set to "${branch}".`);
120387
+ await writeLine2(ctx, `${import_picocolors8.default.green("✓")} Preview branch set to "${branch}".`);
120264
120388
  if (r.previewUrl)
120265
- await writeLine2(ctx, ` Your preview address: ${import_picocolors7.default.bold(r.previewUrl)}`);
120266
- await writeLine2(ctx, r.status === "error" ? import_picocolors7.default.yellow(" ⚠ The initial deploy failed — push to the branch to retry.") : import_picocolors7.default.dim(" Building now — live within ~90s."));
120389
+ await writeLine2(ctx, ` Your preview address: ${import_picocolors8.default.bold(r.previewUrl)}`);
120390
+ await writeLine2(ctx, r.status === "error" ? import_picocolors8.default.yellow(" ⚠ The initial deploy failed — push to the branch to retry.") : import_picocolors8.default.dim(" Building now — live within ~90s."));
120267
120391
  return;
120268
120392
  }
120269
120393
  if (sub === "unset") {
120270
120394
  await previewBranchUnset(ctx, { app });
120271
- await writeLine2(ctx, `${import_picocolors7.default.green("✓")} Preview branch cleared.`);
120395
+ await writeLine2(ctx, `${import_picocolors8.default.green("✓")} Preview branch cleared.`);
120272
120396
  return;
120273
120397
  }
120274
120398
  if (sub === "show" || sub === undefined) {
120275
120399
  const s = await previewBranchShow(ctx, { app });
120276
120400
  if (!s.previewBranch) {
120277
120401
  await writeLine2(ctx, "No preview branch set.");
120278
- await writeLine2(ctx, import_picocolors7.default.dim(" Set one: percher preview branch set <branch>"));
120402
+ await writeLine2(ctx, import_picocolors8.default.dim(" Set one: percher preview branch set <branch>"));
120279
120403
  return;
120280
120404
  }
120281
- await writeLine2(ctx, `Preview branch: ${import_picocolors7.default.bold(s.previewBranch)}`);
120405
+ await writeLine2(ctx, `Preview branch: ${import_picocolors8.default.bold(s.previewBranch)}`);
120282
120406
  if (s.previewUrl)
120283
120407
  await writeLine2(ctx, ` Address: ${s.previewUrl}`);
120284
120408
  await writeLine2(ctx, ` Status: ${s.status}`);
@@ -120289,7 +120413,7 @@ function registerDeploysCommands(cli, ctx, argv) {
120289
120413
  }
120290
120414
 
120291
120415
  // src/commands/env.ts
120292
- var import_picocolors8 = __toESM(require_picocolors(), 1);
120416
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
120293
120417
  var ENV_SCOPES = ["all", "live", "preview"];
120294
120418
  function isEnvScope(value) {
120295
120419
  return ENV_SCOPES.includes(value);
@@ -120300,11 +120424,11 @@ function registerEnvCommands(cli, ctx) {
120300
120424
  if (options.scope !== undefined) {
120301
120425
  const raw = String(options.scope);
120302
120426
  if (!isEnvScope(raw)) {
120303
- await writeLine2(ctx, `${import_picocolors8.default.red("✗")} Invalid --scope "${raw}". Use all, live, or preview.`);
120427
+ await writeLine2(ctx, `${import_picocolors9.default.red("✗")} Invalid --scope "${raw}". Use all, live, or preview.`);
120304
120428
  throw new SilentExitError;
120305
120429
  }
120306
120430
  if (action === "list") {
120307
- await writeLine2(ctx, `${import_picocolors8.default.red("✗")} --scope is not supported with "list" — env list already shows every scope ([live]/[preview] annotations).`);
120431
+ await writeLine2(ctx, `${import_picocolors9.default.red("✗")} --scope is not supported with "list" — env list already shows every scope ([live]/[preview] annotations).`);
120308
120432
  throw new SilentExitError;
120309
120433
  }
120310
120434
  scope = raw;
@@ -120330,10 +120454,10 @@ function registerEnvCommands(cli, ctx) {
120330
120454
  });
120331
120455
  await envSet(ctx, { app: options.app, values, scope });
120332
120456
  const scopeNote = scope && scope !== "all" ? ` [${scope}]` : "";
120333
- await writeLine2(ctx, `${import_picocolors8.default.green("✓")} env vars updated (${Object.keys(values).length})${scopeNote}`);
120457
+ await writeLine2(ctx, `${import_picocolors9.default.green("✓")} env vars updated (${Object.keys(values).length})${scopeNote}`);
120334
120458
  } catch (err) {
120335
120459
  if (err instanceof EnvSetInputError2) {
120336
- await writeLine2(ctx, `${import_picocolors8.default.red("✗")} ${err.message}`);
120460
+ await writeLine2(ctx, `${import_picocolors9.default.red("✗")} ${err.message}`);
120337
120461
  throw new SilentExitError;
120338
120462
  }
120339
120463
  throw err;
@@ -120344,7 +120468,7 @@ function registerEnvCommands(cli, ctx) {
120344
120468
  throw new Error("Expected key to unset");
120345
120469
  await envUnset(ctx, { app: options.app, key, scope });
120346
120470
  const scopeNote = scope && scope !== "all" ? ` [${scope}]` : "";
120347
- await writeLine2(ctx, `${import_picocolors8.default.green("✓")} unset ${key}${scopeNote}`);
120471
+ await writeLine2(ctx, `${import_picocolors9.default.green("✓")} unset ${key}${scopeNote}`);
120348
120472
  } else if (action === "list") {
120349
120473
  const env = await envList(ctx, { app: options.app });
120350
120474
  for (const row of env.detailed) {
@@ -120358,19 +120482,19 @@ function registerEnvCommands(cli, ctx) {
120358
120482
  cli.command("generate", "Generate .env.example and TypeScript types from percher.toml").action(async () => {
120359
120483
  const result = await generate(ctx);
120360
120484
  for (const f of result.files) {
120361
- await writeLine2(ctx, `${import_picocolors8.default.green("✓")} ${f}`);
120485
+ await writeLine2(ctx, `${import_picocolors9.default.green("✓")} ${f}`);
120362
120486
  }
120363
120487
  });
120364
120488
  }
120365
120489
 
120366
120490
  // src/commands/export.ts
120367
- var import_picocolors9 = __toESM(require_picocolors(), 1);
120491
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
120368
120492
  function registerExportCommands(cli, ctx) {
120369
120493
  cli.command("export [app]", "Download an export bundle for an app (no secrets)").option("--out <path>", "Where to write the file (defaults to ./{name}-{date}.tar.gz)").option("--git", "Export the whole deploy history as a git bundle (.bundle) — re-clone with `git clone`").action(async (app, options) => {
120370
120494
  if (options.git) {
120371
120495
  const result2 = await exportGitBundle(ctx, { app, out: options.out });
120372
- await writeLine2(ctx, `${import_picocolors9.default.green("✓")} exported ${result2.appName} deploy history → ${result2.outPath} (${(result2.bytes / 1024).toFixed(1)} KB)`);
120373
- await writeLine2(ctx, import_picocolors9.default.dim(`Re-clone it anywhere with: git clone ${result2.outPath}`));
120496
+ await writeLine2(ctx, `${import_picocolors10.default.green("✓")} exported ${result2.appName} deploy history → ${result2.outPath} (${(result2.bytes / 1024).toFixed(1)} KB)`);
120497
+ await writeLine2(ctx, import_picocolors10.default.dim(`Re-clone it anywhere with: git clone ${result2.outPath}`));
120374
120498
  return;
120375
120499
  }
120376
120500
  const result = await exportApp(ctx, {
@@ -120378,15 +120502,15 @@ function registerExportCommands(cli, ctx) {
120378
120502
  out: options.out,
120379
120503
  deliveryKind: "cli"
120380
120504
  });
120381
- await writeLine2(ctx, `${import_picocolors9.default.green("✓")} exported ${result.appName} → ${result.outPath} (${(result.bytes / 1024).toFixed(1)} KB)`);
120382
- await writeLine2(ctx, import_picocolors9.default.dim("Env values are not included. To export encrypted env values, use the dashboard's Settings → Export and confirm your password."));
120505
+ await writeLine2(ctx, `${import_picocolors10.default.green("✓")} exported ${result.appName} → ${result.outPath} (${(result.bytes / 1024).toFixed(1)} KB)`);
120506
+ await writeLine2(ctx, import_picocolors10.default.dim("Env values are not included. To export encrypted env values, use the dashboard's Settings → Export and confirm your password."));
120383
120507
  });
120384
120508
  }
120385
120509
 
120386
120510
  // src/commands/github.ts
120387
- var import_picocolors10 = __toESM(require_picocolors(), 1);
120511
+ var import_picocolors11 = __toESM(require_picocolors(), 1);
120388
120512
  async function printWebhookInstructions(ctx, instructions, branch) {
120389
- await writeLine2(ctx, import_picocolors10.default.bold("Add this webhook in GitHub to enable auto-deploy on push:"));
120513
+ await writeLine2(ctx, import_picocolors11.default.bold("Add this webhook in GitHub to enable auto-deploy on push:"));
120390
120514
  await writeLine2(ctx, ` URL: ${instructions.settingsUrl}`);
120391
120515
  await writeLine2(ctx, "");
120392
120516
  await writeLine2(ctx, " Field Value");
@@ -120396,9 +120520,9 @@ async function printWebhookInstructions(ctx, instructions, branch) {
120396
120520
  await writeLine2(ctx, ` Secret ${instructions.secret}`);
120397
120521
  await writeLine2(ctx, " Which events? Just the push event");
120398
120522
  await writeLine2(ctx, "");
120399
- await writeLine2(ctx, import_picocolors10.default.yellow(" ⚠ Treat this secret like a password — save it now, it won't be shown again."));
120523
+ await writeLine2(ctx, import_picocolors11.default.yellow(" ⚠ Treat this secret like a password — save it now, it won't be shown again."));
120400
120524
  await writeLine2(ctx, "");
120401
- await writeLine2(ctx, import_picocolors10.default.dim(`After adding the webhook, every push to ${branch} will auto-deploy.`));
120525
+ await writeLine2(ctx, import_picocolors11.default.dim(`After adding the webhook, every push to ${branch} will auto-deploy.`));
120402
120526
  }
120403
120527
  function registerGithubCommands(cli, ctx) {
120404
120528
  cli.command("github [action] [repoUrl] [...rest]", "GitHub integration: connect | setup-webhook").option("--app <name>", "App name (overrides percher.toml)").option("--branch <branch>", "(connect only) Branch to track (default: main)").action(async (action, repoUrl, _rest, options) => {
@@ -120411,24 +120535,24 @@ function registerGithubCommands(cli, ctx) {
120411
120535
  repoUrl,
120412
120536
  branch: options.branch
120413
120537
  });
120414
- await writeLine2(ctx, `${import_picocolors10.default.green("✓")} Connected to ${result.repoUrl} (branch: ${result.branch})`);
120415
- await writeLine2(ctx, `${import_picocolors10.default.green("✓")} Deploy started: ${result.deployId}`);
120538
+ await writeLine2(ctx, `${import_picocolors11.default.green("✓")} Connected to ${result.repoUrl} (branch: ${result.branch})`);
120539
+ await writeLine2(ctx, `${import_picocolors11.default.green("✓")} Deploy started: ${result.deployId}`);
120416
120540
  await writeLine2(ctx, "");
120417
120541
  if (result.webhookSetupFailed) {
120418
- await writeLine2(ctx, import_picocolors10.default.yellow(`⚠ Webhook setup failed: ${result.webhookSetupError ?? "unknown error"}`));
120419
- await writeLine2(ctx, import_picocolors10.default.yellow(" Auto-deploy will NOT work until you re-run webhook setup:"));
120420
- await writeLine2(ctx, ` ${import_picocolors10.default.bold(`percher github setup-webhook${options.app ? ` --app ${options.app}` : ""}`)}`);
120542
+ await writeLine2(ctx, import_picocolors11.default.yellow(`⚠ Webhook setup failed: ${result.webhookSetupError ?? "unknown error"}`));
120543
+ await writeLine2(ctx, import_picocolors11.default.yellow(" Auto-deploy will NOT work until you re-run webhook setup:"));
120544
+ await writeLine2(ctx, ` ${import_picocolors11.default.bold(`percher github setup-webhook${options.app ? ` --app ${options.app}` : ""}`)}`);
120421
120545
  return;
120422
120546
  }
120423
120547
  if (result.webhookInstructions) {
120424
120548
  await printWebhookInstructions(ctx, result.webhookInstructions, result.branch);
120425
120549
  }
120426
- await writeLine2(ctx, import_picocolors10.default.dim(`Track the initial deploy: percher deploys inspect ${result.deployId}`));
120550
+ await writeLine2(ctx, import_picocolors11.default.dim(`Track the initial deploy: percher deploys inspect ${result.deployId}`));
120427
120551
  return;
120428
120552
  }
120429
120553
  if (action === "setup-webhook") {
120430
120554
  const result = await githubSetupWebhook(ctx, { app: options.app });
120431
- await writeLine2(ctx, `${import_picocolors10.default.green("✓")} Webhook regenerated for ${result.app}`);
120555
+ await writeLine2(ctx, `${import_picocolors11.default.green("✓")} Webhook regenerated for ${result.app}`);
120432
120556
  await writeLine2(ctx, "");
120433
120557
  await printWebhookInstructions(ctx, result.webhookInstructions, result.branch);
120434
120558
  return;
@@ -120440,52 +120564,52 @@ function registerGithubCommands(cli, ctx) {
120440
120564
  }
120441
120565
 
120442
120566
  // src/commands/hooks.ts
120443
- var import_picocolors11 = __toESM(require_picocolors(), 1);
120567
+ var import_picocolors12 = __toESM(require_picocolors(), 1);
120444
120568
  function whenLabel(iso) {
120445
120569
  return iso ? new Date(iso).toISOString().slice(0, 10) : "never";
120446
120570
  }
120447
120571
  function registerHooksCommands(cli, ctx) {
120448
120572
  cli.command("hook [action] [arg]", "Manage deploy hooks: create | list | revoke").option("--app <name>", "App name (default: read from percher.toml)").action(async (action, arg, options) => {
120449
120573
  if (!ctx.client.token) {
120450
- await writeLine2(ctx, `${import_picocolors11.default.red("✗")} Not logged in. Run: percher login`);
120574
+ await writeLine2(ctx, `${import_picocolors12.default.red("✗")} Not logged in. Run: percher login`);
120451
120575
  throw new SilentExitError;
120452
120576
  }
120453
120577
  if (action === "create") {
120454
120578
  if (!arg) {
120455
- await writeLine2(ctx, `${import_picocolors11.default.red("✗")} A name is required. Usage: percher hook create <name> [--app <name>]`);
120579
+ await writeLine2(ctx, `${import_picocolors12.default.red("✗")} A name is required. Usage: percher hook create <name> [--app <name>]`);
120456
120580
  throw new SilentExitError;
120457
120581
  }
120458
120582
  const created = await hookCreate(ctx, { name: arg, app: options.app });
120459
- await writeLine2(ctx, `${import_picocolors11.default.green("✓")} Deploy hook created: ${import_picocolors11.default.bold(created.name)}`);
120583
+ await writeLine2(ctx, `${import_picocolors12.default.green("✓")} Deploy hook created: ${import_picocolors12.default.bold(created.name)}`);
120460
120584
  await writeLine2(ctx, "");
120461
- await writeLine2(ctx, ` ${import_picocolors11.default.bold(created.hookUrl)}`);
120462
- await writeLine2(ctx, import_picocolors11.default.yellow(" ↑ Shown once — store it in your CMS/CI secret store now. It can't be retrieved again."));
120463
- await writeLine2(ctx, import_picocolors11.default.yellow(" The URL is a secret: anyone holding it can trigger builds (6/min)."));
120585
+ await writeLine2(ctx, ` ${import_picocolors12.default.bold(created.hookUrl)}`);
120586
+ await writeLine2(ctx, import_picocolors12.default.yellow(" ↑ Shown once — store it in your CMS/CI secret store now. It can't be retrieved again."));
120587
+ await writeLine2(ctx, import_picocolors12.default.yellow(" The URL is a secret: anyone holding it can trigger builds (6/min)."));
120464
120588
  await writeLine2(ctx, "");
120465
- await writeLine2(ctx, import_picocolors11.default.dim(" Trigger a rebuild with:"));
120466
- await writeLine2(ctx, import_picocolors11.default.dim(` curl -X POST ${created.hookUrl}`));
120589
+ await writeLine2(ctx, import_picocolors12.default.dim(" Trigger a rebuild with:"));
120590
+ await writeLine2(ctx, import_picocolors12.default.dim(` curl -X POST ${created.hookUrl}`));
120467
120591
  return;
120468
120592
  }
120469
120593
  if (action === "list") {
120470
120594
  const hooks = await hookList(ctx, { app: options.app });
120471
120595
  if (hooks.length === 0) {
120472
- await writeLine2(ctx, import_picocolors11.default.dim("No deploy hooks. Create one with: percher hook create <name>"));
120596
+ await writeLine2(ctx, import_picocolors12.default.dim("No deploy hooks. Create one with: percher hook create <name>"));
120473
120597
  return;
120474
120598
  }
120475
120599
  for (const h of hooks) {
120476
- await writeLine2(ctx, `${import_picocolors11.default.bold(h.id)} ${h.name.padEnd(20)} ${import_picocolors11.default.dim(`created ${whenLabel(h.createdAt)}`)} ${import_picocolors11.default.dim(`last used ${whenLabel(h.lastUsedAt)}`)}`);
120600
+ await writeLine2(ctx, `${import_picocolors12.default.bold(h.id)} ${h.name.padEnd(20)} ${import_picocolors12.default.dim(`created ${whenLabel(h.createdAt)}`)} ${import_picocolors12.default.dim(`last used ${whenLabel(h.lastUsedAt)}`)}`);
120477
120601
  }
120478
120602
  await writeLine2(ctx, "");
120479
- await writeLine2(ctx, import_picocolors11.default.dim("Revoke one with: percher hook revoke <id>"));
120603
+ await writeLine2(ctx, import_picocolors12.default.dim("Revoke one with: percher hook revoke <id>"));
120480
120604
  return;
120481
120605
  }
120482
120606
  if (action === "revoke") {
120483
120607
  if (!arg) {
120484
- await writeLine2(ctx, `${import_picocolors11.default.red("✗")} A hook id is required. Usage: percher hook revoke <id> (list ids with \`percher hook list\`).`);
120608
+ await writeLine2(ctx, `${import_picocolors12.default.red("✗")} A hook id is required. Usage: percher hook revoke <id> (list ids with \`percher hook list\`).`);
120485
120609
  throw new SilentExitError;
120486
120610
  }
120487
120611
  await hookRevoke(ctx, { id: arg, app: options.app });
120488
- await writeLine2(ctx, `${import_picocolors11.default.green("✓")} Deploy hook revoked.`);
120612
+ await writeLine2(ctx, `${import_picocolors12.default.green("✓")} Deploy hook revoked.`);
120489
120613
  return;
120490
120614
  }
120491
120615
  throw new Error(`Unknown hook action "${action ?? ""}". Use: create | list | revoke.`);
@@ -120496,7 +120620,7 @@ function registerHooksCommands(cli, ctx) {
120496
120620
  }
120497
120621
 
120498
120622
  // src/commands/migrate.ts
120499
- var import_picocolors12 = __toESM(require_picocolors(), 1);
120623
+ var import_picocolors13 = __toESM(require_picocolors(), 1);
120500
120624
  function registerMigrateCommands(cli, ctx) {
120501
120625
  cli.command("migrate-from-supabase", "Migrate a Supabase project to PocketBase: schema + data-import script, optionally rewrite SDK calls").option("--project <ref>", "Supabase project reference (the 20-char id from the project URL)").option("--token <token>", "Supabase personal access token (sbp_...). Read from SUPABASE_ACCESS_TOKEN if omitted.").option("--inspect-only", "Only print the schema that would migrate — read-only, no file writes").option("--output-dir <dir>", "Where to write the preview files (default: ./migration-preview/)").option("--rewrite-client", "Also convert your Supabase SDK calls to PocketBase across the project").option("--apply", "With --rewrite-client: overwrite files in place (originals backed up first). Default: preview only.").option("--root <dir>", "Project root to rewrite with --rewrite-client (default: current directory)").option("--no-scaffold-client", "With --rewrite-client: don't generate a PocketBase client module / insert pb imports (default: scaffold one)").option("--status", "Show a migration scoreboard for the local project — what's still on Supabase vs PocketBase. Local scan, no Supabase token needed.").option("--build", "With --status: also run the project build as a final gate").option("--pb-identifier <name>", "With --status: the PocketBase client identifier to check for (default: pb). Match what the rewrite used.").option("--json", "Output the structured result as JSON").action(async (options) => {
120502
120626
  if (options.status) {
@@ -120519,15 +120643,15 @@ function registerMigrateCommands(cli, ctx) {
120519
120643
  const projectRef = options.project;
120520
120644
  const token = options.token ?? ctx.env.SUPABASE_ACCESS_TOKEN;
120521
120645
  if (options.apply && !options.rewriteClient) {
120522
- await writeLine2(ctx, `${import_picocolors12.default.red("✗")} --apply only applies to the client rewrite. Pass --rewrite-client --apply together.`);
120646
+ await writeLine2(ctx, `${import_picocolors13.default.red("✗")} --apply only applies to the client rewrite. Pass --rewrite-client --apply together.`);
120523
120647
  throw new SilentExitError(1);
120524
120648
  }
120525
120649
  if (!projectRef) {
120526
- await writeLine2(ctx, `${import_picocolors12.default.red("✗")} Missing --project. Pass your Supabase project ref (e.g. --project abcdefghijklmnopqrst).`);
120650
+ await writeLine2(ctx, `${import_picocolors13.default.red("✗")} Missing --project. Pass your Supabase project ref (e.g. --project abcdefghijklmnopqrst).`);
120527
120651
  throw new SilentExitError(1);
120528
120652
  }
120529
120653
  if (!token) {
120530
- await writeLine2(ctx, `${import_picocolors12.default.red("✗")} Missing --token. Pass --token sbp_... or set SUPABASE_ACCESS_TOKEN. Generate one at https://supabase.com/dashboard/account/tokens`);
120654
+ await writeLine2(ctx, `${import_picocolors13.default.red("✗")} Missing --token. Pass --token sbp_... or set SUPABASE_ACCESS_TOKEN. Generate one at https://supabase.com/dashboard/account/tokens`);
120531
120655
  throw new SilentExitError(1);
120532
120656
  }
120533
120657
  if (options.inspectOnly) {
@@ -120576,182 +120700,182 @@ function registerMigrateCommands(cli, ctx) {
120576
120700
  async function renderInspectHuman(ctx, result) {
120577
120701
  if (result.status === "failed") {
120578
120702
  const err = result.error;
120579
- await writeLine2(ctx, `${import_picocolors12.default.red("✗")} ${err?.title ?? "Inspection failed"}`);
120703
+ await writeLine2(ctx, `${import_picocolors13.default.red("✗")} ${err?.title ?? "Inspection failed"}`);
120580
120704
  if (err?.explanation)
120581
120705
  await writeLine2(ctx, ` ${err.explanation}`);
120582
120706
  if (err?.suggestion)
120583
- await writeLine2(ctx, import_picocolors12.default.dim(` ${err.suggestion}`));
120707
+ await writeLine2(ctx, import_picocolors13.default.dim(` ${err.suggestion}`));
120584
120708
  return;
120585
120709
  }
120586
- await writeLine2(ctx, `${import_picocolors12.default.green("✓")} ${result.summary}`);
120710
+ await writeLine2(ctx, `${import_picocolors13.default.green("✓")} ${result.summary}`);
120587
120711
  await writeLine2(ctx, "");
120588
- await writeLine2(ctx, import_picocolors12.default.bold("Tables"));
120712
+ await writeLine2(ctx, import_picocolors13.default.bold("Tables"));
120589
120713
  if (result.tables.length === 0) {
120590
- await writeLine2(ctx, import_picocolors12.default.dim(" (none in user schemas)"));
120714
+ await writeLine2(ctx, import_picocolors13.default.dim(" (none in user schemas)"));
120591
120715
  }
120592
120716
  for (const table of result.tables) {
120593
120717
  const pkCount = table.columns.filter((c) => c.isPrimaryKey).length;
120594
120718
  const fkCount = table.columns.filter((c) => c.foreignKey !== null).length;
120595
120719
  const policyCount = table.rlsPolicies.length;
120596
120720
  const rowEstimate = table.estimatedRowCount.toLocaleString();
120597
- await writeLine2(ctx, ` ${import_picocolors12.default.cyan(`${table.schema}.${table.name}`)} ${import_picocolors12.default.dim(`cols=${table.columns.length} pk=${pkCount} fk=${fkCount} rls=${policyCount} rows≈${rowEstimate}`)}`);
120721
+ await writeLine2(ctx, ` ${import_picocolors13.default.cyan(`${table.schema}.${table.name}`)} ${import_picocolors13.default.dim(`cols=${table.columns.length} pk=${pkCount} fk=${fkCount} rls=${policyCount} rows≈${rowEstimate}`)}`);
120598
120722
  }
120599
120723
  if (result.storageBuckets.length > 0) {
120600
120724
  await writeLine2(ctx, "");
120601
- await writeLine2(ctx, import_picocolors12.default.bold("Storage buckets"));
120725
+ await writeLine2(ctx, import_picocolors13.default.bold("Storage buckets"));
120602
120726
  for (const bucket of result.storageBuckets) {
120603
120727
  const visibility = bucket.public ? "public" : "private";
120604
- await writeLine2(ctx, ` ${import_picocolors12.default.cyan(bucket.name)} ${import_picocolors12.default.dim(`(${visibility})`)}`);
120728
+ await writeLine2(ctx, ` ${import_picocolors13.default.cyan(bucket.name)} ${import_picocolors13.default.dim(`(${visibility})`)}`);
120605
120729
  }
120606
120730
  }
120607
120731
  if (result.authUsersCount !== null) {
120608
120732
  await writeLine2(ctx, "");
120609
- await writeLine2(ctx, `${import_picocolors12.default.bold("Auth users")}: ${result.authUsersCount}`);
120733
+ await writeLine2(ctx, `${import_picocolors13.default.bold("Auth users")}: ${result.authUsersCount}`);
120610
120734
  }
120611
120735
  if (result.warnings.length > 0) {
120612
120736
  await writeLine2(ctx, "");
120613
- await writeLine2(ctx, import_picocolors12.default.bold(`Warnings (${result.warnings.length})`));
120737
+ await writeLine2(ctx, import_picocolors13.default.bold(`Warnings (${result.warnings.length})`));
120614
120738
  for (const warning of result.warnings) {
120615
- await writeLine2(ctx, ` ${import_picocolors12.default.yellow("⚠")} ${warning}`);
120739
+ await writeLine2(ctx, ` ${import_picocolors13.default.yellow("⚠")} ${warning}`);
120616
120740
  }
120617
120741
  }
120618
120742
  await writeLine2(ctx, "");
120619
- await writeLine2(ctx, import_picocolors12.default.dim("--inspect-only is read-only. Drop the flag to also write the PocketBase schema preview + pb_migrate.js data-import script to ./migration-preview/."));
120743
+ await writeLine2(ctx, import_picocolors13.default.dim("--inspect-only is read-only. Drop the flag to also write the PocketBase schema preview + pb_migrate.js data-import script to ./migration-preview/."));
120620
120744
  }
120621
120745
  async function renderSchemaHuman(ctx, result, opts) {
120622
120746
  if (result.status === "failed") {
120623
120747
  const err = result.error;
120624
- await writeLine2(ctx, `${import_picocolors12.default.red("✗")} ${err?.title ?? "Migration preview failed"}`);
120748
+ await writeLine2(ctx, `${import_picocolors13.default.red("✗")} ${err?.title ?? "Migration preview failed"}`);
120625
120749
  if (err?.explanation)
120626
120750
  await writeLine2(ctx, ` ${err.explanation}`);
120627
120751
  if (err?.suggestion)
120628
- await writeLine2(ctx, import_picocolors12.default.dim(` ${err.suggestion}`));
120752
+ await writeLine2(ctx, import_picocolors13.default.dim(` ${err.suggestion}`));
120629
120753
  return;
120630
120754
  }
120631
- await writeLine2(ctx, `${import_picocolors12.default.green("✓")} ${result.summary}`);
120755
+ await writeLine2(ctx, `${import_picocolors13.default.green("✓")} ${result.summary}`);
120632
120756
  await writeLine2(ctx, "");
120633
- await writeLine2(ctx, import_picocolors12.default.bold("Files written"));
120757
+ await writeLine2(ctx, import_picocolors13.default.bold("Files written"));
120634
120758
  for (const path of result.filesWritten) {
120635
- await writeLine2(ctx, ` ${import_picocolors12.default.cyan(path)}`);
120759
+ await writeLine2(ctx, ` ${import_picocolors13.default.cyan(path)}`);
120636
120760
  }
120637
120761
  const preview = result.preview;
120638
120762
  if (!preview)
120639
120763
  return;
120640
120764
  if (preview.collections.length > 0) {
120641
120765
  await writeLine2(ctx, "");
120642
- await writeLine2(ctx, import_picocolors12.default.bold("Collections"));
120766
+ await writeLine2(ctx, import_picocolors13.default.bold("Collections"));
120643
120767
  for (const collection of preview.collections) {
120644
120768
  const fields = collection.schema.length;
120645
120769
  const relations = collection.schema.filter((f) => f.type === "relation").length;
120646
- await writeLine2(ctx, ` ${import_picocolors12.default.cyan(collection.name)} ${import_picocolors12.default.dim(`← ${collection.sourceTable} fields=${fields} relations=${relations}`)}`);
120770
+ await writeLine2(ctx, ` ${import_picocolors13.default.cyan(collection.name)} ${import_picocolors13.default.dim(`← ${collection.sourceTable} fields=${fields} relations=${relations}`)}`);
120647
120771
  }
120648
120772
  }
120649
120773
  if (preview.warnings.length > 0) {
120650
120774
  await writeLine2(ctx, "");
120651
- await writeLine2(ctx, import_picocolors12.default.bold(`Warnings (${preview.warnings.length})`));
120775
+ await writeLine2(ctx, import_picocolors13.default.bold(`Warnings (${preview.warnings.length})`));
120652
120776
  for (const warning of preview.warnings.slice(0, 10)) {
120653
- await writeLine2(ctx, ` ${import_picocolors12.default.yellow("⚠")} ${warning}`);
120777
+ await writeLine2(ctx, ` ${import_picocolors13.default.yellow("⚠")} ${warning}`);
120654
120778
  }
120655
120779
  if (preview.warnings.length > 10) {
120656
- await writeLine2(ctx, import_picocolors12.default.dim(` …and ${preview.warnings.length - 10} more — see MIGRATION_NOTES.md`));
120780
+ await writeLine2(ctx, import_picocolors13.default.dim(` …and ${preview.warnings.length - 10} more — see MIGRATION_NOTES.md`));
120657
120781
  }
120658
120782
  }
120659
120783
  await writeLine2(ctx, "");
120660
- await writeLine2(ctx, import_picocolors12.default.dim("Preview only — nothing has touched your PocketBase yet. ./migration-preview/ includes pb_migrate.js, the data-import script: review MIGRATION_NOTES.md, then run it against your app's PocketBase to copy your rows over."));
120784
+ await writeLine2(ctx, import_picocolors13.default.dim("Preview only — nothing has touched your PocketBase yet. ./migration-preview/ includes pb_migrate.js, the data-import script: review MIGRATION_NOTES.md, then run it against your app's PocketBase to copy your rows over."));
120661
120785
  if (!opts.clientRewriteRequested) {
120662
- await writeLine2(ctx, import_picocolors12.default.dim("Add --rewrite-client to also convert your Supabase SDK calls to PocketBase (preview first; --apply writes in place)."));
120786
+ await writeLine2(ctx, import_picocolors13.default.dim("Add --rewrite-client to also convert your Supabase SDK calls to PocketBase (preview first; --apply writes in place)."));
120663
120787
  }
120664
120788
  }
120665
120789
  async function renderRewriteHuman(ctx, result) {
120666
120790
  await writeLine2(ctx, "");
120667
120791
  if (result.status === "failed") {
120668
120792
  const err = result.error;
120669
- await writeLine2(ctx, `${import_picocolors12.default.red("✗")} ${err?.title ?? "Client rewrite failed"}`);
120793
+ await writeLine2(ctx, `${import_picocolors13.default.red("✗")} ${err?.title ?? "Client rewrite failed"}`);
120670
120794
  if (err?.explanation)
120671
120795
  await writeLine2(ctx, ` ${err.explanation}`);
120672
120796
  if (err?.suggestion)
120673
- await writeLine2(ctx, import_picocolors12.default.dim(` ${err.suggestion}`));
120797
+ await writeLine2(ctx, import_picocolors13.default.dim(` ${err.suggestion}`));
120674
120798
  return;
120675
120799
  }
120676
- await writeLine2(ctx, `${import_picocolors12.default.green("✓")} ${result.summary}`);
120800
+ await writeLine2(ctx, `${import_picocolors13.default.green("✓")} ${result.summary}`);
120677
120801
  if (result.filesWritten.length > 0) {
120678
120802
  await writeLine2(ctx, "");
120679
- await writeLine2(ctx, import_picocolors12.default.bold("Files written"));
120803
+ await writeLine2(ctx, import_picocolors13.default.bold("Files written"));
120680
120804
  for (const path of result.filesWritten.slice(0, 20)) {
120681
- await writeLine2(ctx, ` ${import_picocolors12.default.cyan(path)}`);
120805
+ await writeLine2(ctx, ` ${import_picocolors13.default.cyan(path)}`);
120682
120806
  }
120683
120807
  if (result.filesWritten.length > 20) {
120684
- await writeLine2(ctx, import_picocolors12.default.dim(` …and ${result.filesWritten.length - 20} more`));
120808
+ await writeLine2(ctx, import_picocolors13.default.dim(` …and ${result.filesWritten.length - 20} more`));
120685
120809
  }
120686
120810
  }
120687
120811
  if (result.flags.length > 0) {
120688
120812
  await writeLine2(ctx, "");
120689
- await writeLine2(ctx, import_picocolors12.default.bold("Needs review (see migration-preview/REWRITE_NOTES.md)"));
120813
+ await writeLine2(ctx, import_picocolors13.default.bold("Needs review (see migration-preview/REWRITE_NOTES.md)"));
120690
120814
  for (const flag of result.flags.slice(0, 15)) {
120691
120815
  const loc = flag.line !== undefined ? `:${flag.line}` : "";
120692
120816
  const tag = flag.kind === "manual_review" ? "rewritten, verify semantics" : "left untouched";
120693
- await writeLine2(ctx, ` ${import_picocolors12.default.yellow("⚠")} ${import_picocolors12.default.cyan(`${flag.file}${loc}`)} ${flag.pattern} ${import_picocolors12.default.dim(`(${tag}) — ${flag.note}`)}`);
120817
+ await writeLine2(ctx, ` ${import_picocolors13.default.yellow("⚠")} ${import_picocolors13.default.cyan(`${flag.file}${loc}`)} ${flag.pattern} ${import_picocolors13.default.dim(`(${tag}) — ${flag.note}`)}`);
120694
120818
  }
120695
120819
  if (result.flags.length > 15) {
120696
- await writeLine2(ctx, import_picocolors12.default.dim(` …and ${result.flags.length - 15} more — see REWRITE_NOTES.md`));
120820
+ await writeLine2(ctx, import_picocolors13.default.dim(` …and ${result.flags.length - 15} more — see REWRITE_NOTES.md`));
120697
120821
  }
120698
120822
  }
120699
120823
  if (result.skipped.length > 0) {
120700
- await writeLine2(ctx, import_picocolors12.default.dim(`
120824
+ await writeLine2(ctx, import_picocolors13.default.dim(`
120701
120825
  ${result.skipped.length} file(s) couldn't be parsed and were skipped — listed in REWRITE_NOTES.md.`));
120702
120826
  }
120703
120827
  await writeLine2(ctx, "");
120704
120828
  if (result.mode === "apply") {
120705
- await writeLine2(ctx, import_picocolors12.default.dim(result.changedCount > 0 ? "Files rewritten in place. Originals are in ./migration-preview/backup/ — diff against them (or git) before committing." : "No files were rewritten — the work left is the manual review above."));
120829
+ await writeLine2(ctx, import_picocolors13.default.dim(result.changedCount > 0 ? "Files rewritten in place. Originals are in ./migration-preview/backup/ — diff against them (or git) before committing." : "No files were rewritten — the work left is the manual review above."));
120706
120830
  } else {
120707
- await writeLine2(ctx, import_picocolors12.default.dim(result.changedCount > 0 ? "Preview only — diff ./migration-preview/rewritten/ against your project, then re-run with --rewrite-client --apply to write in place." : "Preview only — no files needed automatic rewriting; work through the manual review above."));
120831
+ await writeLine2(ctx, import_picocolors13.default.dim(result.changedCount > 0 ? "Preview only — diff ./migration-preview/rewritten/ against your project, then re-run with --rewrite-client --apply to write in place." : "Preview only — no files needed automatic rewriting; work through the manual review above."));
120708
120832
  }
120709
120833
  }
120710
120834
  async function renderStatusHuman(ctx, result) {
120711
120835
  if (result.status === "failed") {
120712
120836
  const err = result.error;
120713
- await writeLine2(ctx, `${import_picocolors12.default.red("✗")} ${err?.title ?? "Migration status scan failed"}`);
120837
+ await writeLine2(ctx, `${import_picocolors13.default.red("✗")} ${err?.title ?? "Migration status scan failed"}`);
120714
120838
  if (err?.explanation)
120715
120839
  await writeLine2(ctx, ` ${err.explanation}`);
120716
120840
  if (err?.suggestion)
120717
- await writeLine2(ctx, import_picocolors12.default.dim(` ${err.suggestion}`));
120841
+ await writeLine2(ctx, import_picocolors13.default.dim(` ${err.suggestion}`));
120718
120842
  return;
120719
120843
  }
120720
- await writeLine2(ctx, import_picocolors12.default.bold("Migration status"));
120844
+ await writeLine2(ctx, import_picocolors13.default.bold("Migration status"));
120721
120845
  await writeLine2(ctx, "");
120722
120846
  for (const check2 of result.checks) {
120723
- const mark = check2.status === "pass" ? import_picocolors12.default.green("✓") : check2.status === "fail" ? import_picocolors12.default.red("✗") : check2.status === "warn" ? import_picocolors12.default.yellow("⚠") : import_picocolors12.default.dim("•");
120724
- const count = check2.count > 0 ? import_picocolors12.default.dim(` (${check2.count})`) : "";
120725
- await writeLine2(ctx, ` ${mark} ${check2.label}${count} ${import_picocolors12.default.dim(`— ${check2.detail}`)}`);
120847
+ const mark = check2.status === "pass" ? import_picocolors13.default.green("✓") : check2.status === "fail" ? import_picocolors13.default.red("✗") : check2.status === "warn" ? import_picocolors13.default.yellow("⚠") : import_picocolors13.default.dim("•");
120848
+ const count = check2.count > 0 ? import_picocolors13.default.dim(` (${check2.count})`) : "";
120849
+ await writeLine2(ctx, ` ${mark} ${check2.label}${count} ${import_picocolors13.default.dim(`— ${check2.detail}`)}`);
120726
120850
  for (const file2 of (check2.files ?? []).slice(0, 10)) {
120727
- await writeLine2(ctx, import_picocolors12.default.dim(` - ${file2}`));
120851
+ await writeLine2(ctx, import_picocolors13.default.dim(` - ${file2}`));
120728
120852
  }
120729
120853
  if ((check2.files?.length ?? 0) > 10) {
120730
- await writeLine2(ctx, import_picocolors12.default.dim(` …and ${(check2.files?.length ?? 0) - 10} more`));
120854
+ await writeLine2(ctx, import_picocolors13.default.dim(` …and ${(check2.files?.length ?? 0) - 10} more`));
120731
120855
  }
120732
120856
  }
120733
120857
  if (result.build) {
120734
120858
  await writeLine2(ctx, "");
120735
- const mark = !result.build.ran ? import_picocolors12.default.dim("•") : result.build.passed ? import_picocolors12.default.green("✓") : import_picocolors12.default.red("✗");
120736
- await writeLine2(ctx, ` ${mark} Build ${import_picocolors12.default.dim(`— ${result.build.detail}`)}`);
120859
+ const mark = !result.build.ran ? import_picocolors13.default.dim("•") : result.build.passed ? import_picocolors13.default.green("✓") : import_picocolors13.default.red("✗");
120860
+ await writeLine2(ctx, ` ${mark} Build ${import_picocolors13.default.dim(`— ${result.build.detail}`)}`);
120737
120861
  }
120738
120862
  await writeLine2(ctx, "");
120739
- await writeLine2(ctx, import_picocolors12.default.dim(result.summary));
120863
+ await writeLine2(ctx, import_picocolors13.default.dim(result.summary));
120740
120864
  }
120741
120865
 
120742
120866
  // src/commands/sharing.ts
120743
- var import_picocolors13 = __toESM(require_picocolors(), 1);
120867
+ var import_picocolors14 = __toESM(require_picocolors(), 1);
120744
120868
  function registerSharingCommands(cli, ctx) {
120745
120869
  cli.command("share [action] [arg] [valueOrRole]", "Per-app collaborator invites: list | invite <email> | role <shareId> <role> | revoke <shareId>").option("--app <name>", "App name (overrides percher.toml)").option("--role <role>", "(invite only) viewer | admin — viewer is read-only, admin can deploy + change config").action(async (action, arg, valueOrRole, options) => {
120746
120870
  if (action === "list" || action === undefined) {
120747
120871
  const rows = await shareList(ctx, { app: options.app });
120748
120872
  if (rows.length === 0) {
120749
- await writeLine2(ctx, import_picocolors13.default.dim("No collaborators."));
120873
+ await writeLine2(ctx, import_picocolors14.default.dim("No collaborators."));
120750
120874
  return;
120751
120875
  }
120752
120876
  for (const r of rows) {
120753
- const state = r.accepted ? import_picocolors13.default.green("accepted") : import_picocolors13.default.yellow("pending");
120754
- const roleStr = r.role === "admin" ? import_picocolors13.default.cyan("admin") : import_picocolors13.default.dim("viewer");
120877
+ const state = r.accepted ? import_picocolors14.default.green("accepted") : import_picocolors14.default.yellow("pending");
120878
+ const roleStr = r.role === "admin" ? import_picocolors14.default.cyan("admin") : import_picocolors14.default.dim("viewer");
120755
120879
  await writeLine2(ctx, `${r.id} ${roleStr.padEnd(15)} ${state.padEnd(15)} ${r.email}`);
120756
120880
  }
120757
120881
  return;
@@ -120769,8 +120893,8 @@ function registerSharingCommands(cli, ctx) {
120769
120893
  email: arg,
120770
120894
  role: roleRaw
120771
120895
  });
120772
- await writeLine2(ctx, `${import_picocolors13.default.green("✓")} Invited ${created.email} as ${created.role} (id ${created.id})`);
120773
- await writeLine2(ctx, import_picocolors13.default.dim(`Pending until accepted — invite expires ${new Date(created.expiresAt).toLocaleDateString()}.`));
120896
+ await writeLine2(ctx, `${import_picocolors14.default.green("✓")} Invited ${created.email} as ${created.role} (id ${created.id})`);
120897
+ await writeLine2(ctx, import_picocolors14.default.dim(`Pending until accepted — invite expires ${new Date(created.expiresAt).toLocaleDateString()}.`));
120774
120898
  return;
120775
120899
  }
120776
120900
  if (action === "role") {
@@ -120785,7 +120909,7 @@ function registerSharingCommands(cli, ctx) {
120785
120909
  shareId: arg,
120786
120910
  role: valueOrRole
120787
120911
  });
120788
- await writeLine2(ctx, `${import_picocolors13.default.green("✓")} ${updated.email} is now ${import_picocolors13.default.cyan(updated.role)}`);
120912
+ await writeLine2(ctx, `${import_picocolors14.default.green("✓")} ${updated.email} is now ${import_picocolors14.default.cyan(updated.role)}`);
120789
120913
  return;
120790
120914
  }
120791
120915
  if (action === "revoke") {
@@ -120793,7 +120917,7 @@ function registerSharingCommands(cli, ctx) {
120793
120917
  throw new Error("Usage: percher share revoke <shareId> [--app <name>]");
120794
120918
  }
120795
120919
  await shareRevoke(ctx, { app: options.app, shareId: arg });
120796
- await writeLine2(ctx, `${import_picocolors13.default.green("✓")} Revoked share ${arg}`);
120920
+ await writeLine2(ctx, `${import_picocolors14.default.green("✓")} Revoked share ${arg}`);
120797
120921
  return;
120798
120922
  }
120799
120923
  throw new Error(`Unknown share action "${action}". Use list | invite <email> | role <shareId> <role> | revoke <shareId>.`);
@@ -120806,11 +120930,11 @@ function registerSharingCommands(cli, ctx) {
120806
120930
  if (action === undefined || action === "list") {
120807
120931
  const pending = await invitesList(ctx, {});
120808
120932
  if (pending.length === 0) {
120809
- await writeLine2(ctx, import_picocolors13.default.dim("No pending invites."));
120933
+ await writeLine2(ctx, import_picocolors14.default.dim("No pending invites."));
120810
120934
  return;
120811
120935
  }
120812
120936
  for (const inv of pending) {
120813
- const roleStr = inv.role === "admin" ? import_picocolors13.default.cyan("admin") : import_picocolors13.default.dim("viewer");
120937
+ const roleStr = inv.role === "admin" ? import_picocolors14.default.cyan("admin") : import_picocolors14.default.dim("viewer");
120814
120938
  await writeLine2(ctx, `${inv.id} ${roleStr.padEnd(15)} ${inv.appName.padEnd(24)} from ${inv.invitedBy}`);
120815
120939
  }
120816
120940
  return;
@@ -120821,10 +120945,10 @@ function registerSharingCommands(cli, ctx) {
120821
120945
  }
120822
120946
  const accepted = await invitesAccept(ctx, { shareId });
120823
120947
  if (accepted.alreadyAccepted) {
120824
- await writeLine2(ctx, import_picocolors13.default.dim(`Invite ${shareId} was already accepted.`));
120948
+ await writeLine2(ctx, import_picocolors14.default.dim(`Invite ${shareId} was already accepted.`));
120825
120949
  return;
120826
120950
  }
120827
- await writeLine2(ctx, `${import_picocolors13.default.green("✓")} Accepted — you now have ${import_picocolors13.default.cyan(accepted.role)} access to the shared app.`);
120951
+ await writeLine2(ctx, `${import_picocolors14.default.green("✓")} Accepted — you now have ${import_picocolors14.default.cyan(accepted.role)} access to the shared app.`);
120828
120952
  return;
120829
120953
  }
120830
120954
  if (action === "decline") {
@@ -120832,7 +120956,7 @@ function registerSharingCommands(cli, ctx) {
120832
120956
  throw new Error("Usage: percher invites decline <shareId>");
120833
120957
  }
120834
120958
  await invitesDecline(ctx, { shareId });
120835
- await writeLine2(ctx, `${import_picocolors13.default.green("✓")} Declined invite ${shareId}.`);
120959
+ await writeLine2(ctx, `${import_picocolors14.default.green("✓")} Declined invite ${shareId}.`);
120836
120960
  return;
120837
120961
  }
120838
120962
  throw new Error(`Unknown invites action "${action}". Use list | accept <shareId> | decline <shareId>.`);
@@ -120843,7 +120967,7 @@ function registerSharingCommands(cli, ctx) {
120843
120967
  }
120844
120968
 
120845
120969
  // src/commands/status.ts
120846
- var import_picocolors14 = __toESM(require_picocolors(), 1);
120970
+ var import_picocolors15 = __toESM(require_picocolors(), 1);
120847
120971
  function registerStatusCommands(cli, ctx) {
120848
120972
  cli.command("status", "Show Percher platform status (API + worker health)").option("--json", "Output JSON instead of formatted text").action(async (options) => {
120849
120973
  const result = await status(ctx, { json: Boolean(options.json) });
@@ -120859,34 +120983,34 @@ function registerStatusCommands(cli, ctx) {
120859
120983
  });
120860
120984
  }
120861
120985
  async function renderHumanStatus(ctx, result) {
120862
- const symbol2 = result.verdict === "operational" ? import_picocolors14.default.green("✓") : result.verdict === "worker-degraded" ? import_picocolors14.default.yellow("⚠") : import_picocolors14.default.red("✗");
120986
+ const symbol2 = result.verdict === "operational" ? import_picocolors15.default.green("✓") : result.verdict === "worker-degraded" ? import_picocolors15.default.yellow("⚠") : import_picocolors15.default.red("✗");
120863
120987
  await writeLine2(ctx, `${symbol2} ${result.summary}`);
120864
120988
  if (!result.health)
120865
120989
  return;
120866
120990
  const worker = result.health.worker;
120867
120991
  if (!worker) {
120868
- await writeLine2(ctx, import_picocolors14.default.dim(" worker: not configured (local Docker socket)"));
120992
+ await writeLine2(ctx, import_picocolors15.default.dim(" worker: not configured (local Docker socket)"));
120869
120993
  return;
120870
120994
  }
120871
120995
  if (worker.health) {
120872
120996
  const ok = worker.health.ok;
120873
- const symbol3 = ok ? import_picocolors14.default.green("✓") : import_picocolors14.default.red("✗");
120997
+ const symbol3 = ok ? import_picocolors15.default.green("✓") : import_picocolors15.default.red("✗");
120874
120998
  const lastOk = worker.health.lastOkAt ?? "never";
120875
- await writeLine2(ctx, ` ${symbol3} worker health: ${ok ? "ok" : `failing (${worker.health.consecutiveErrs} consecutive)`} last-ok=${import_picocolors14.default.dim(lastOk)}`);
120999
+ await writeLine2(ctx, ` ${symbol3} worker health: ${ok ? "ok" : `failing (${worker.health.consecutiveErrs} consecutive)`} last-ok=${import_picocolors15.default.dim(lastOk)}`);
120876
121000
  }
120877
121001
  if (worker.circuitBreaker) {
120878
121002
  const breaker = worker.circuitBreaker;
120879
- const symbol3 = breaker.open ? import_picocolors14.default.red("✗") : import_picocolors14.default.green("✓");
121003
+ const symbol3 = breaker.open ? import_picocolors15.default.red("✗") : import_picocolors15.default.green("✓");
120880
121004
  const state = breaker.open ? "open (deploys fail fast)" : "closed";
120881
121005
  await writeLine2(ctx, ` ${symbol3} circuit breaker: ${state} ${breaker.failuresInWindow}/${breaker.threshold} failures in last ${Math.round(breaker.windowMs / 1000)}s`);
120882
121006
  }
120883
121007
  if (result.health.version) {
120884
- await writeLine2(ctx, import_picocolors14.default.dim(` api version: ${result.health.version}`));
121008
+ await writeLine2(ctx, import_picocolors15.default.dim(` api version: ${result.health.version}`));
120885
121009
  }
120886
121010
  }
120887
121011
 
120888
121012
  // src/commands/token.ts
120889
- var import_picocolors15 = __toESM(require_picocolors(), 1);
121013
+ var import_picocolors16 = __toESM(require_picocolors(), 1);
120890
121014
  var VALID_SCOPES = ["deploy", "env", "read", "full"];
120891
121015
  function scopeLabel(scopes, appName) {
120892
121016
  const s = scopes && scopes.length > 0 ? scopes.join("+") : "full";
@@ -120895,22 +121019,22 @@ function scopeLabel(scopes, appName) {
120895
121019
  function registerTokenCommands(cli, ctx) {
120896
121020
  cli.command("token [action] [arg]", "Manage API tokens: create | list | revoke").option("--scope <scope>", "(create) deploy | env | read | full (default: full)").option("--app <name>", "(create) bind the token to a single app").option("--ttl-days <n>", "(create) token lifetime in days").action(async (action, arg, options) => {
120897
121021
  if (!ctx.client.token) {
120898
- await writeLine2(ctx, `${import_picocolors15.default.red("✗")} Not logged in. Run: percher login`);
121022
+ await writeLine2(ctx, `${import_picocolors16.default.red("✗")} Not logged in. Run: percher login`);
120899
121023
  throw new SilentExitError;
120900
121024
  }
120901
121025
  if (action === "create") {
120902
121026
  if (!arg) {
120903
- await writeLine2(ctx, `${import_picocolors15.default.red("✗")} A name is required. Usage: percher token create <name> [--scope deploy] [--app <name>]`);
121027
+ await writeLine2(ctx, `${import_picocolors16.default.red("✗")} A name is required. Usage: percher token create <name> [--scope deploy] [--app <name>]`);
120904
121028
  throw new SilentExitError;
120905
121029
  }
120906
121030
  const scope = options.scope ?? "full";
120907
121031
  if (!VALID_SCOPES.includes(scope)) {
120908
- await writeLine2(ctx, `${import_picocolors15.default.red("✗")} Invalid --scope "${options.scope}". Use one of: ${VALID_SCOPES.join(", ")}.`);
121032
+ await writeLine2(ctx, `${import_picocolors16.default.red("✗")} Invalid --scope "${options.scope}". Use one of: ${VALID_SCOPES.join(", ")}.`);
120909
121033
  throw new SilentExitError;
120910
121034
  }
120911
121035
  const ttlDays = options.ttlDays !== undefined ? Number(options.ttlDays) : undefined;
120912
121036
  if (ttlDays !== undefined && (!Number.isInteger(ttlDays) || ttlDays <= 0)) {
120913
- await writeLine2(ctx, `${import_picocolors15.default.red("✗")} --ttl-days must be a positive whole number.`);
121037
+ await writeLine2(ctx, `${import_picocolors16.default.red("✗")} --ttl-days must be a positive whole number.`);
120914
121038
  throw new SilentExitError;
120915
121039
  }
120916
121040
  const created = await tokenCreate(ctx, {
@@ -120919,36 +121043,36 @@ function registerTokenCommands(cli, ctx) {
120919
121043
  app: options.app,
120920
121044
  ttlDays
120921
121045
  });
120922
- await writeLine2(ctx, `${import_picocolors15.default.green("✓")} Token created: ${import_picocolors15.default.bold(created.name)} ${import_picocolors15.default.dim(`(${scopeLabel(created.scopes, created.appName)})`)}`);
121046
+ await writeLine2(ctx, `${import_picocolors16.default.green("✓")} Token created: ${import_picocolors16.default.bold(created.name)} ${import_picocolors16.default.dim(`(${scopeLabel(created.scopes, created.appName)})`)}`);
120923
121047
  await writeLine2(ctx, "");
120924
- await writeLine2(ctx, ` ${import_picocolors15.default.bold(created.token)}`);
120925
- await writeLine2(ctx, import_picocolors15.default.yellow(" ↑ Store this now — the secret is shown only once and can't be retrieved."));
121048
+ await writeLine2(ctx, ` ${import_picocolors16.default.bold(created.token)}`);
121049
+ await writeLine2(ctx, import_picocolors16.default.yellow(" ↑ Store this now — the secret is shown only once and can't be retrieved."));
120926
121050
  await writeLine2(ctx, "");
120927
- await writeLine2(ctx, import_picocolors15.default.dim(" Use it in CI / an agent:"));
120928
- await writeLine2(ctx, import_picocolors15.default.dim(` PERCHER_TOKEN=${created.token} bunx percher publish`));
121051
+ await writeLine2(ctx, import_picocolors16.default.dim(" Use it in CI / an agent:"));
121052
+ await writeLine2(ctx, import_picocolors16.default.dim(` PERCHER_TOKEN=${created.token} bunx percher publish`));
120929
121053
  return;
120930
121054
  }
120931
121055
  if (action === "list") {
120932
121056
  const tokens = await tokenList(ctx);
120933
121057
  if (tokens.length === 0) {
120934
- await writeLine2(ctx, import_picocolors15.default.dim("No API tokens."));
121058
+ await writeLine2(ctx, import_picocolors16.default.dim("No API tokens."));
120935
121059
  return;
120936
121060
  }
120937
121061
  for (const t of tokens) {
120938
121062
  const expires = t.expiresAt ? `expires ${new Date(t.expiresAt).toISOString().slice(0, 10)}` : "no expiry";
120939
- await writeLine2(ctx, `${import_picocolors15.default.bold(t.id)} ${t.name.padEnd(20)} ${import_picocolors15.default.dim(scopeLabel(t.scopes, t.appName))} ${import_picocolors15.default.dim(expires)}`);
121063
+ await writeLine2(ctx, `${import_picocolors16.default.bold(t.id)} ${t.name.padEnd(20)} ${import_picocolors16.default.dim(scopeLabel(t.scopes, t.appName))} ${import_picocolors16.default.dim(expires)}`);
120940
121064
  }
120941
121065
  await writeLine2(ctx, "");
120942
- await writeLine2(ctx, import_picocolors15.default.dim("Revoke one with: percher token revoke <id>"));
121066
+ await writeLine2(ctx, import_picocolors16.default.dim("Revoke one with: percher token revoke <id>"));
120943
121067
  return;
120944
121068
  }
120945
121069
  if (action === "revoke") {
120946
121070
  if (!arg) {
120947
- await writeLine2(ctx, `${import_picocolors15.default.red("✗")} A token id is required. Usage: percher token revoke <id> (list ids with \`percher token list\`).`);
121071
+ await writeLine2(ctx, `${import_picocolors16.default.red("✗")} A token id is required. Usage: percher token revoke <id> (list ids with \`percher token list\`).`);
120948
121072
  throw new SilentExitError;
120949
121073
  }
120950
121074
  await tokenRevoke(ctx, { id: arg });
120951
- await writeLine2(ctx, `${import_picocolors15.default.green("✓")} Token revoked.`);
121075
+ await writeLine2(ctx, `${import_picocolors16.default.green("✓")} Token revoked.`);
120952
121076
  return;
120953
121077
  }
120954
121078
  throw new Error(`Unknown token action "${action ?? ""}". Use: create | list | revoke.`);
@@ -120959,17 +121083,17 @@ function registerTokenCommands(cli, ctx) {
120959
121083
  }
120960
121084
 
120961
121085
  // src/commands/transfers.ts
120962
- var import_picocolors16 = __toESM(require_picocolors(), 1);
121086
+ var import_picocolors17 = __toESM(require_picocolors(), 1);
120963
121087
  function registerTransfersCommands(cli, ctx) {
120964
121088
  cli.command("transfer [emailOrTransferId]", "Transfer ownership of an app: `transfer <email>` initiates; `transfer --cancel <id>` cancels; `transfer --list` shows history").option("--app <name>", "App name (overrides percher.toml)").option("--cancel", "Cancel a pending transfer by id (positional arg is the transferId)").option("--list", "List the transfer history for this app").action(async (emailOrTransferId, options) => {
120965
121089
  if (options.list) {
120966
121090
  const rows = await transferListForApp(ctx, { app: options.app });
120967
121091
  if (rows.length === 0) {
120968
- await writeLine2(ctx, import_picocolors16.default.dim("No transfers for this app."));
121092
+ await writeLine2(ctx, import_picocolors17.default.dim("No transfers for this app."));
120969
121093
  return;
120970
121094
  }
120971
121095
  for (const r of rows) {
120972
- const status2 = r.status === "pending" ? import_picocolors16.default.yellow("pending") : r.status === "accepted" ? import_picocolors16.default.green("accepted") : import_picocolors16.default.dim(r.status);
121096
+ const status2 = r.status === "pending" ? import_picocolors17.default.yellow("pending") : r.status === "accepted" ? import_picocolors17.default.green("accepted") : import_picocolors17.default.dim(r.status);
120973
121097
  await writeLine2(ctx, `${r.id} ${status2.padEnd(20)} → ${r.toEmail} (${r.createdAt})`);
120974
121098
  }
120975
121099
  return;
@@ -120982,7 +121106,7 @@ function registerTransfersCommands(cli, ctx) {
120982
121106
  app: options.app,
120983
121107
  transferId: emailOrTransferId
120984
121108
  });
120985
- await writeLine2(ctx, `${import_picocolors16.default.green("✓")} Cancelled transfer ${emailOrTransferId}`);
121109
+ await writeLine2(ctx, `${import_picocolors17.default.green("✓")} Cancelled transfer ${emailOrTransferId}`);
120986
121110
  return;
120987
121111
  }
120988
121112
  if (!emailOrTransferId) {
@@ -120992,17 +121116,17 @@ function registerTransfersCommands(cli, ctx) {
120992
121116
  app: options.app,
120993
121117
  email: emailOrTransferId
120994
121118
  });
120995
- await writeLine2(ctx, `${import_picocolors16.default.green("✓")} Transfer initiated to ${initiated.toEmail} (id ${initiated.id})`);
120996
- await writeLine2(ctx, import_picocolors16.default.dim(`Expires ${new Date(initiated.expiresAt).toLocaleDateString()}. The recipient must accept via 'percher transfers accept ${initiated.id}'.`));
121119
+ await writeLine2(ctx, `${import_picocolors17.default.green("✓")} Transfer initiated to ${initiated.toEmail} (id ${initiated.id})`);
121120
+ await writeLine2(ctx, import_picocolors17.default.dim(`Expires ${new Date(initiated.expiresAt).toLocaleDateString()}. The recipient must accept via 'percher transfers accept ${initiated.id}'.`));
120997
121121
  if (initiated.notes) {
120998
- await writeLine2(ctx, import_picocolors16.default.yellow(` ⚠ ${initiated.notes}`));
121122
+ await writeLine2(ctx, import_picocolors17.default.yellow(` ⚠ ${initiated.notes}`));
120999
121123
  }
121000
121124
  });
121001
121125
  cli.command("transfers [action] [transferId]", "Incoming ownership transfers: list (default) | accept <transferId> | decline <transferId>").action(async (action, transferId) => {
121002
121126
  if (action === undefined || action === "list") {
121003
121127
  const rows = await transfersIncoming(ctx, {});
121004
121128
  if (rows.length === 0) {
121005
- await writeLine2(ctx, import_picocolors16.default.dim("No pending incoming transfers."));
121129
+ await writeLine2(ctx, import_picocolors17.default.dim("No pending incoming transfers."));
121006
121130
  return;
121007
121131
  }
121008
121132
  for (const r of rows) {
@@ -121015,7 +121139,7 @@ function registerTransfersCommands(cli, ctx) {
121015
121139
  throw new Error("Usage: percher transfers accept <transferId>");
121016
121140
  }
121017
121141
  const accepted = await transfersAccept(ctx, { transferId });
121018
- await writeLine2(ctx, `${import_picocolors16.default.green("✓")} You now own ${import_picocolors16.default.cyan(accepted.appName)}. Run 'percher open --app ${accepted.appName}' to see it.`);
121142
+ await writeLine2(ctx, `${import_picocolors17.default.green("✓")} You now own ${import_picocolors17.default.cyan(accepted.appName)}. Run 'percher open --app ${accepted.appName}' to see it.`);
121019
121143
  return;
121020
121144
  }
121021
121145
  if (action === "decline") {
@@ -121023,7 +121147,7 @@ function registerTransfersCommands(cli, ctx) {
121023
121147
  throw new Error("Usage: percher transfers decline <transferId>");
121024
121148
  }
121025
121149
  await transfersDecline(ctx, { transferId });
121026
- await writeLine2(ctx, `${import_picocolors16.default.green("✓")} Declined transfer ${transferId}.`);
121150
+ await writeLine2(ctx, `${import_picocolors17.default.green("✓")} Declined transfer ${transferId}.`);
121027
121151
  return;
121028
121152
  }
121029
121153
  throw new Error(`Unknown transfers action "${action}". Use list | accept <id> | decline <id>.`);
@@ -121097,52 +121221,52 @@ async function createCliContext() {
121097
121221
 
121098
121222
  // src/output.ts
121099
121223
  init_dist();
121100
- var import_picocolors17 = __toESM(require_picocolors(), 1);
121224
+ var import_picocolors18 = __toESM(require_picocolors(), 1);
121101
121225
  async function renderError(stderr, error48) {
121102
121226
  const writer = stderr.getWriter();
121103
121227
  const encoder = new TextEncoder;
121104
121228
  const lines = [];
121105
121229
  if (error48 instanceof PercherTomlError) {
121106
- lines.push(`${import_picocolors17.default.red(`error (${error48.code}):`)} ${error48.message}`);
121230
+ lines.push(`${import_picocolors18.default.red(`error (${error48.code}):`)} ${error48.message}`);
121107
121231
  if (error48.tomlLine !== undefined) {
121108
- lines.push(import_picocolors17.default.dim(` at percher.toml line ${error48.tomlLine}`));
121232
+ lines.push(import_picocolors18.default.dim(` at percher.toml line ${error48.tomlLine}`));
121109
121233
  }
121110
121234
  for (const issue2 of error48.issues ?? []) {
121111
- lines.push(`${import_picocolors17.default.dim(" · ") + import_picocolors17.default.yellow(issue2.path.join("."))}: ${issue2.message}`);
121235
+ lines.push(`${import_picocolors18.default.dim(" · ") + import_picocolors18.default.yellow(issue2.path.join("."))}: ${issue2.message}`);
121112
121236
  }
121113
121237
  } else if (error48 instanceof SunsetError) {
121114
121238
  if (error48.gateCode === "platform_delete_gated") {
121115
- lines.push(`${import_picocolors17.default.yellow("Percher is shutting down — deletes are paused.")}`);
121239
+ lines.push(`${import_picocolors18.default.yellow("Percher is shutting down — deletes are paused.")}`);
121116
121240
  lines.push(error48.message);
121117
- lines.push(import_picocolors17.default.dim(` To delete and permanently forfeit your export window, confirm with your password in the dashboard: ${error48.dashboardUrl}`));
121118
- lines.push(import_picocolors17.default.dim(" The CLI cannot perform this delete during sunset — it must go through the dashboard."));
121241
+ lines.push(import_picocolors18.default.dim(` To delete and permanently forfeit your export window, confirm with your password in the dashboard: ${error48.dashboardUrl}`));
121242
+ lines.push(import_picocolors18.default.dim(" The CLI cannot perform this delete during sunset — it must go through the dashboard."));
121119
121243
  } else {
121120
- lines.push(`${import_picocolors17.default.yellow("Percher is in read-only mode ahead of shutdown.")}`);
121244
+ lines.push(`${import_picocolors18.default.yellow("Percher is in read-only mode ahead of shutdown.")}`);
121121
121245
  lines.push(error48.message);
121122
- lines.push(import_picocolors17.default.dim(` Your apps keep running. Export them any time from the dashboard: ${error48.dashboardUrl}`));
121246
+ lines.push(import_picocolors18.default.dim(` Your apps keep running. Export them any time from the dashboard: ${error48.dashboardUrl}`));
121123
121247
  }
121124
- lines.push(import_picocolors17.default.dim(` Final data deletion: ${error48.finalDeletionDate}`));
121248
+ lines.push(import_picocolors18.default.dim(` Final data deletion: ${error48.finalDeletionDate}`));
121125
121249
  } else if (error48 instanceof PercherApiError) {
121126
121250
  if (error48.status === 502 || error48.status === 503) {
121127
- lines.push(`${import_picocolors17.default.red("error:")} Percher API is temporarily unavailable (HTTP ${error48.status}). Try again in a few seconds.`);
121251
+ lines.push(`${import_picocolors18.default.red("error:")} Percher API is temporarily unavailable (HTTP ${error48.status}). Try again in a few seconds.`);
121128
121252
  } else {
121129
- lines.push(`${import_picocolors17.default.red("error:")} ${error48.message}`);
121130
- lines.push(import_picocolors17.default.dim(` ${error48.code} (HTTP ${error48.status})`));
121253
+ lines.push(`${import_picocolors18.default.red("error:")} ${error48.message}`);
121254
+ lines.push(import_picocolors18.default.dim(` ${error48.code} (HTTP ${error48.status})`));
121131
121255
  }
121132
121256
  if (error48.requestId)
121133
- lines.push(import_picocolors17.default.dim(` request: ${error48.requestId}`));
121257
+ lines.push(import_picocolors18.default.dim(` request: ${error48.requestId}`));
121134
121258
  } else if (error48 instanceof PercherCoreError) {
121135
- lines.push(`${import_picocolors17.default.red(`error (${error48.code}):`)} ${error48.message}`);
121259
+ lines.push(`${import_picocolors18.default.red(`error (${error48.code}):`)} ${error48.message}`);
121136
121260
  if (error48.hint)
121137
- lines.push(import_picocolors17.default.dim(` ${error48.hint}`));
121261
+ lines.push(import_picocolors18.default.dim(` ${error48.hint}`));
121138
121262
  } else if (error48 instanceof Error) {
121139
- lines.push(`${import_picocolors17.default.red("unexpected error:")} ${error48.message}`);
121263
+ lines.push(`${import_picocolors18.default.red("unexpected error:")} ${error48.message}`);
121140
121264
  if (error48.stack)
121141
- lines.push(import_picocolors17.default.dim(error48.stack.split(`
121265
+ lines.push(import_picocolors18.default.dim(error48.stack.split(`
121142
121266
  `).slice(1, 4).join(`
121143
121267
  `)));
121144
121268
  } else {
121145
- lines.push(`${import_picocolors17.default.red("unexpected error:")} ${String(error48)}`);
121269
+ lines.push(`${import_picocolors18.default.red("unexpected error:")} ${String(error48)}`);
121146
121270
  }
121147
121271
  try {
121148
121272
  await writer.write(encoder.encode(`${lines.join(`
@@ -121155,7 +121279,7 @@ async function renderError(stderr, error48) {
121155
121279
  // package.json
121156
121280
  var package_default = {
121157
121281
  name: "percher",
121158
- version: "0.5.22",
121282
+ version: "0.5.23",
121159
121283
  description: "CLI for the Percher hosting platform — publish apps with one command",
121160
121284
  type: "module",
121161
121285
  bin: {
@@ -121182,8 +121306,8 @@ var package_default = {
121182
121306
  typecheck: "tsc --noEmit"
121183
121307
  },
121184
121308
  dependencies: {
121185
- "@percher/client": "^0.2.21",
121186
- "@percher/core": "^0.4.27",
121309
+ "@percher/client": "^0.2.22",
121310
+ "@percher/core": "^0.4.28",
121187
121311
  "@percher/toml": "^0.1.10",
121188
121312
  cac: "^7.0.0",
121189
121313
  picocolors: "^1.1.1"
@@ -121255,7 +121379,7 @@ async function showTelemetryNotice() {
121255
121379
  }
121256
121380
 
121257
121381
  // src/version-check.ts
121258
- var import_picocolors18 = __toESM(require_picocolors(), 1);
121382
+ var import_picocolors19 = __toESM(require_picocolors(), 1);
121259
121383
  async function checkForUpdates() {
121260
121384
  try {
121261
121385
  const res = await fetch("https://registry.npmjs.org/percher/latest", {
@@ -121266,9 +121390,9 @@ async function checkForUpdates() {
121266
121390
  const data2 = await res.json();
121267
121391
  const latest = data2.version;
121268
121392
  if (latest && latest !== VERSION) {
121269
- console.error(import_picocolors18.default.yellow(`
121393
+ console.error(import_picocolors19.default.yellow(`
121270
121394
  Update available: ${VERSION} → ${latest}`));
121271
- console.error(import_picocolors18.default.dim(`Run: bunx percher@latest
121395
+ console.error(import_picocolors19.default.dim(`Run: bunx percher@latest
121272
121396
  `));
121273
121397
  }
121274
121398
  } catch {}
@@ -121279,6 +121403,7 @@ async function run(argv, ctxOverride) {
121279
121403
  const cli = cac("percher");
121280
121404
  const ctx = ctxOverride ?? await createCliContext();
121281
121405
  registerAppsCommands(cli, ctx);
121406
+ registerAnalyticsCommands(cli, ctx);
121282
121407
  registerDeploysCommands(cli, ctx, argv);
121283
121408
  registerEnvCommands(cli, ctx);
121284
121409
  registerExportCommands(cli, ctx);