percher 0.5.0 → 0.5.1

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.
@@ -104962,249 +104962,26 @@ async function cacheReset(ctx, input = {}) {
104962
104962
  }
104963
104963
  return ctx.client.apps.resetCache(name);
104964
104964
  }
104965
- // ../core/dist/commands/create.js
104966
- import { join as join6, resolve as resolve4 } from "node:path";
104967
-
104968
- // ../core/dist/templates.js
104969
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync3 } from "node:fs";
104970
- import { basename, join as join5, normalize, relative, resolve as resolve3 } from "node:path";
104971
- var __dirname = "C:\\dev\\Percher\\packages\\core\\dist";
104972
- var TEMPLATES_DIR = resolve3(import.meta.dirname ?? __dirname, "../templates");
104973
- var PLACEHOLDER = "__APP_NAME__";
104974
- function listTemplates() {
104975
- return readdirSync(TEMPLATES_DIR).filter((name) => {
104976
- const full = join5(TEMPLATES_DIR, name);
104977
- return statSync(full).isDirectory();
104978
- });
104979
- }
104980
- function scaffoldTemplate(input) {
104981
- const { template, appName, targetDir, force } = input;
104982
- const available = listTemplates();
104983
- if (!available.includes(template)) {
104984
- throw new Error(`Unknown template "${template}". Available: ${available.join(", ")}`);
104985
- }
104986
- const absTarget = resolve3(targetDir);
104987
- if (existsSync3(absTarget)) {
104988
- const entries = readdirSync(absTarget);
104989
- if (entries.length > 0 && !force) {
104990
- throw new Error(`Target directory "${absTarget}" is not empty. Use --force to overwrite.`);
104991
- }
104992
- }
104993
- const srcDir = join5(TEMPLATES_DIR, template);
104994
- const files = [];
104995
- function copyDir(src, dest) {
104996
- mkdirSync3(dest, { recursive: true });
104997
- for (const entry of readdirSync(src)) {
104998
- const srcPath = join5(src, entry);
104999
- const destPath = join5(dest, entry);
105000
- const rel = relative(absTarget, normalize(destPath));
105001
- if (rel.startsWith("..") || resolve3(destPath) === resolve3(absTarget, "..", basename(absTarget))) {
105002
- throw new Error(`Template contains path traversal: ${entry}`);
105003
- }
105004
- if (statSync(srcPath).isDirectory()) {
105005
- copyDir(srcPath, destPath);
105006
- } else {
105007
- if (existsSync3(destPath) && !force) {
105008
- continue;
105009
- }
105010
- let content = readFileSync4(srcPath, "utf-8");
105011
- content = content.replaceAll(PLACEHOLDER, appName);
105012
- content = content.replace(/\r\n/g, `
105013
- `);
105014
- writeFileSync3(destPath, content, "utf-8");
105015
- files.push(relative(absTarget, destPath).replace(/\\/g, "/"));
105016
- }
105017
- }
105018
- }
105019
- copyDir(srcDir, absTarget);
105020
- return { files };
105021
- }
105022
-
105023
- // ../core/dist/commands/create.js
105024
- var createInputSchema = exports_external.object({
105025
- name: exports_external.string().regex(APP_NAME_RE, "must be 3-40 chars, lowercase, start with a letter, [a-z0-9-] only"),
105026
- template: exports_external.string().min(1, "Template is required"),
105027
- directory: exports_external.string().optional(),
105028
- force: exports_external.boolean().optional()
105029
- });
105030
- async function create(ctx, input) {
105031
- const available = listTemplates();
105032
- if (!available.includes(input.template)) {
105033
- throw new PercherCoreError(`Unknown template "${input.template}". Available: ${available.join(", ")}`, {
105034
- code: "create.invalid-template",
105035
- hint: `Run "percher create --list-templates" to see available templates.`
105036
- });
105037
- }
105038
- let targetDir;
105039
- if (input.directory) {
105040
- targetDir = resolve4(ctx.cwd, input.directory);
105041
- if (!targetDir.startsWith(resolve4(ctx.cwd))) {
105042
- throw new PercherCoreError("Directory must be within the current workspace", {
105043
- code: "create.failed"
105044
- });
105045
- }
105046
- } else {
105047
- targetDir = join6(ctx.cwd, input.name);
105048
- }
105049
- try {
105050
- const result = scaffoldTemplate({
105051
- template: input.template,
105052
- appName: input.name,
105053
- targetDir,
105054
- force: input.force
105055
- });
105056
- return {
105057
- ...result,
105058
- name: input.name,
105059
- template: input.template,
105060
- directory: targetDir
105061
- };
105062
- } catch (err) {
105063
- const msg = err instanceof Error ? err.message : String(err);
105064
- if (msg.includes("not empty")) {
105065
- throw new PercherCoreError(msg, {
105066
- code: "create.dir-not-empty",
105067
- hint: "Use --force to overwrite existing files."
105068
- });
105069
- }
105070
- throw new PercherCoreError(msg, { code: "create.failed" });
105071
- }
105072
- }
105073
- // ../core/dist/commands/dashboard.js
105074
- var dashboardInputSchema = exports_external.object({
105075
- app: exports_external.string().optional(),
105076
- tab: exports_external.string().optional()
105077
- });
105078
- var DEFAULT_BASE = "https://percher.app";
105079
- var TAB_SHAPE = /^[a-z][a-z0-9-]{0,31}$/;
105080
- var SETTINGS_ANCHOR_TABS = new Set(["github", "env", "domains", "crash-diagnosis"]);
105081
- async function dashboard(ctx, input = {}) {
105082
- const name = input.app ?? readPercherTomlAppName(ctx.cwd);
105083
- if (!name) {
105084
- throw new PercherCoreError("No app specified and no percher.toml found", {
105085
- code: "dashboard.no-app",
105086
- hint: "Pass an app name or run from a directory with percher.toml."
105087
- });
105088
- }
105089
- const tab = input.tab ?? "overview";
105090
- if (!TAB_SHAPE.test(tab)) {
105091
- throw new PercherCoreError(`Invalid tab "${tab}"`, {
105092
- code: "dashboard.invalid-tab",
105093
- hint: "Tab must be lowercase letters, digits, and hyphens (≤32 chars)."
105094
- });
105095
- }
105096
- const base = ctx.env.PERCHER_DASHBOARD_URL ?? DEFAULT_BASE;
105097
- const appBase = `${base}/apps/${encodeURIComponent(name)}`;
105098
- const url2 = SETTINGS_ANCHOR_TABS.has(tab) ? `${appBase}/settings#${tab}` : `${appBase}?tab=${tab}`;
105099
- await ctx.openUrl(url2);
105100
- return url2;
105101
- }
105102
- // ../core/dist/commands/data.js
105103
- var dataInputSchema = exports_external.object({
105104
- app: exports_external.string().optional(),
105105
- json: exports_external.boolean().optional()
105106
- });
105107
- async function data(ctx, input = {}) {
105108
- const name = input.app ?? readPercherTomlAppName(ctx.cwd);
105109
- if (!name) {
105110
- throw new PercherCoreError("No app specified and no percher.toml found", {
105111
- code: "data.no-app",
105112
- hint: "Pass an app name or run from a directory with percher.toml."
105113
- });
105114
- }
105115
- const status = await ctx.client.data.getStatus(name);
105116
- const adminLink = await ctx.client.data.getAdminLink(name);
105117
- let collections = [];
105118
- let stats = { collections: 0, totalRecords: 0 };
105119
- let warning = null;
105120
- try {
105121
- const cols = await ctx.client.data.getCollections(name);
105122
- collections = cols.map((c) => ({ name: c.name, type: c.type }));
105123
- stats = await ctx.client.data.getStats(name);
105124
- } catch (err) {
105125
- const code = err.code;
105126
- if (code === "PB_AUTH_FAILED") {
105127
- warning = `Could not authenticate with PocketBase — collections + stats unavailable. Try \`percher data reset-superuser ${name}\` to rotate the credentials.`;
105128
- } else {
105129
- throw err;
105130
- }
105131
- }
105132
- return {
105133
- mode: status.mode,
105134
- status: status.status,
105135
- version: status.version ?? null,
105136
- collections,
105137
- stats,
105138
- adminUrl: adminLink.adminUrl ?? null,
105139
- warning
105140
- };
105141
- }
105142
- // ../core/dist/commands/data-export.js
105143
- import { resolve as resolve5 } from "node:path";
105144
- var dataExportInputSchema = exports_external.object({
105145
- app: exports_external.string().optional().describe("App name or id. Defaults to the app in percher.toml."),
105146
- out: exports_external.string().optional().describe("Output file path. Defaults to ./<app>-pb-<timestamp>.tar.gz in the current directory.")
105147
- });
105148
- async function dataExport(ctx, input) {
105149
- const appName = input.app ?? readPercherTomlAppName(ctx.cwd);
105150
- if (!appName) {
105151
- throw new PercherCoreError("No app specified and no percher.toml found", {
105152
- code: "data-export.no-app",
105153
- hint: "Pass an app name with --app <name> or run from a directory with percher.toml."
105154
- });
105155
- }
105156
- ctx.status?.(`Exporting ${appName} data...`);
105157
- const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
105158
- const fileName = input.out ?? `${appName}-pb-${stamp}.tar.gz`;
105159
- const path = resolve5(ctx.cwd, fileName);
105160
- const res = await ctx.client.apps.streamDataExport(appName);
105161
- const bytes = await writeResponseToFile(res, path);
105162
- return {
105163
- app: appName,
105164
- path,
105165
- bytes,
105166
- exportedAt: new Date().toISOString()
105167
- };
105168
- }
105169
- // ../core/dist/commands/delete.js
105170
- var deleteInputSchema = exports_external.object({
105171
- app: exports_external.string().optional().describe("App name to delete (defaults to percher.toml app name)"),
105172
- confirm: exports_external.boolean().describe("Must be true to confirm deletion. This is a destructive operation that permanently removes the app, its container, database, domains, env vars, and deploy history.")
105173
- });
105174
- async function deleteApp(ctx, input) {
105175
- const name = input.app ?? readPercherTomlAppName(ctx.cwd);
105176
- if (!name) {
105177
- throw new PercherCoreError("No app specified and no percher.toml found", {
105178
- code: "env.no-app",
105179
- hint: "Pass an app name or run from a directory with percher.toml."
105180
- });
105181
- }
105182
- if (!input.confirm) {
105183
- throw new PercherCoreError(`Deletion requires explicit confirmation. Set confirm: true to delete "${name}" permanently.`, {
105184
- code: "delete.confirmation-required",
105185
- hint: `This will permanently delete the app "${name}" and all its data. Pass confirm: true to proceed.`
105186
- });
105187
- }
105188
- await ctx.client.apps.delete(name);
105189
- }
104965
+ // ../core/dist/commands/capabilities.js
104966
+ var capabilitiesInputSchema = exports_external.object({});
105190
104967
  // ../client/dist/abortable-sleep.js
105191
104968
  function abortableSleep(ms, signal) {
105192
- return new Promise((resolve6) => {
104969
+ return new Promise((resolve3) => {
105193
104970
  if (!signal) {
105194
- setTimeout(resolve6, ms);
104971
+ setTimeout(resolve3, ms);
105195
104972
  return;
105196
104973
  }
105197
104974
  if (signal.aborted) {
105198
- resolve6();
104975
+ resolve3();
105199
104976
  return;
105200
104977
  }
105201
104978
  const onAbort = () => {
105202
104979
  clearTimeout(t);
105203
- resolve6();
104980
+ resolve3();
105204
104981
  };
105205
104982
  const t = setTimeout(() => {
105206
104983
  signal.removeEventListener("abort", onAbort);
105207
- resolve6();
104984
+ resolve3();
105208
104985
  }, ms);
105209
104986
  signal.addEventListener("abort", onAbort, { once: true });
105210
104987
  });
@@ -105223,9 +105000,9 @@ function isAutoForwardedBuildKey(name) {
105223
105000
  return AUTO_FORWARD_PREFIXES.some((p) => name.startsWith(p));
105224
105001
  }
105225
105002
  // ../client/dist/config.js
105226
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync, writeFileSync as writeFileSync4 } from "node:fs";
105003
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
105227
105004
  import { homedir } from "node:os";
105228
- import { dirname as dirname3, join as join7 } from "node:path";
105005
+ import { dirname as dirname3, join as join5 } from "node:path";
105229
105006
  var DEFAULT_API_URL = "https://api.percher.run";
105230
105007
  function home() {
105231
105008
  return process.env.HOME ?? process.env.USERPROFILE ?? homedir();
@@ -105233,15 +105010,15 @@ function home() {
105233
105010
  function configPath() {
105234
105011
  const xdg = process.env.XDG_CONFIG_HOME;
105235
105012
  if (xdg)
105236
- return join7(xdg, "percher", "config.json");
105237
- return join7(home(), ".percher", "config.json");
105013
+ return join5(xdg, "percher", "config.json");
105014
+ return join5(home(), ".percher", "config.json");
105238
105015
  }
105239
105016
  function readFile2() {
105240
105017
  const p = configPath();
105241
- if (!existsSync4(p))
105018
+ if (!existsSync3(p))
105242
105019
  return {};
105243
105020
  try {
105244
- const parsed = JSON.parse(readFileSync5(p, "utf8"));
105021
+ const parsed = JSON.parse(readFileSync4(p, "utf8"));
105245
105022
  return parsed ?? {};
105246
105023
  } catch {
105247
105024
  return {};
@@ -105253,16 +105030,17 @@ function loadConfig() {
105253
105030
  const envToken = process.env.PERCHER_TOKEN;
105254
105031
  return {
105255
105032
  apiUrl: envUrl ?? file2.apiUrl ?? DEFAULT_API_URL,
105256
- token: envToken ?? file2.token ?? null
105033
+ token: envToken ?? file2.token ?? null,
105034
+ anonClaim: file2.anonClaim ?? null
105257
105035
  };
105258
105036
  }
105259
105037
  function saveConfig(partial2) {
105260
105038
  const p = configPath();
105261
- mkdirSync4(dirname3(p), { recursive: true });
105039
+ mkdirSync3(dirname3(p), { recursive: true });
105262
105040
  const current = readFile2();
105263
105041
  const merged = { ...current, ...partial2 };
105264
105042
  const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
105265
- writeFileSync4(tmp, JSON.stringify(merged, null, 2), "utf8");
105043
+ writeFileSync3(tmp, JSON.stringify(merged, null, 2), "utf8");
105266
105044
  renameSync(tmp, p);
105267
105045
  }
105268
105046
 
@@ -105559,6 +105337,20 @@ class AlertsResource {
105559
105337
  }
105560
105338
  }
105561
105339
 
105340
+ // ../client/dist/resources/anon.js
105341
+ class AnonResource {
105342
+ http;
105343
+ constructor(http) {
105344
+ this.http = http;
105345
+ }
105346
+ requestChallenge() {
105347
+ return this.http.get("/anon/challenge");
105348
+ }
105349
+ createAnonApp(input) {
105350
+ return this.http.post("/anon/apps", input);
105351
+ }
105352
+ }
105353
+
105562
105354
  // ../client/dist/resources/apps-types.js
105563
105355
  function isDeployAlreadyInProgress(res) {
105564
105356
  return res.status === "already_in_progress";
@@ -105582,6 +105374,18 @@ class AppsResource {
105582
105374
  get(id, opts) {
105583
105375
  return this.http.get(`/apps/${encodeURIComponent(id)}`, opts);
105584
105376
  }
105377
+ claim(appRef, input) {
105378
+ return this.http.post(`/apps/${encodeURIComponent(appRef)}/claim`, input);
105379
+ }
105380
+ createInspectLink(appRef) {
105381
+ return this.http.post(`/apps/${encodeURIComponent(appRef)}/inspect-link`, {});
105382
+ }
105383
+ inspectLinkStatus(appRef) {
105384
+ return this.http.get(`/apps/${encodeURIComponent(appRef)}/inspect-link`);
105385
+ }
105386
+ revokeInspectLink(appRef) {
105387
+ return this.http.deleteJson(`/apps/${encodeURIComponent(appRef)}/inspect-link`);
105388
+ }
105585
105389
  async deploy(id, input) {
105586
105390
  const params = new URLSearchParams;
105587
105391
  if (input.type === "preview")
@@ -106076,6 +105880,9 @@ class PlatformResource {
106076
105880
  health() {
106077
105881
  return this.http.get("/health");
106078
105882
  }
105883
+ capabilities() {
105884
+ return this.http.get("/capabilities");
105885
+ }
106079
105886
  preflight() {
106080
105887
  return this.http.get("/publish/preflight");
106081
105888
  }
@@ -106178,73 +105985,331 @@ class TransfersResource {
106178
105985
  initiate(appRef, body) {
106179
105986
  return this.http.post(`/apps/${encodeURIComponent(appRef)}/transfer`, body);
106180
105987
  }
106181
- cancel(appRef, transferId) {
106182
- return this.http.delete(`/apps/${encodeURIComponent(appRef)}/transfer/${encodeURIComponent(transferId)}`);
105988
+ cancel(appRef, transferId) {
105989
+ return this.http.delete(`/apps/${encodeURIComponent(appRef)}/transfer/${encodeURIComponent(transferId)}`);
105990
+ }
105991
+ listForApp(appRef) {
105992
+ return this.http.get(`/apps/${encodeURIComponent(appRef)}/transfer`);
105993
+ }
105994
+ listIncoming() {
105995
+ return this.http.get("/transfers/incoming");
105996
+ }
105997
+ accept(transferId) {
105998
+ return this.http.post(`/transfers/${encodeURIComponent(transferId)}/accept`, {});
105999
+ }
106000
+ decline(transferId) {
106001
+ return this.http.post(`/transfers/${encodeURIComponent(transferId)}/decline`, {});
106002
+ }
106003
+ }
106004
+
106005
+ // ../client/dist/client.js
106006
+ class PercherClient {
106007
+ apiUrl;
106008
+ token;
106009
+ sessionId;
106010
+ apps;
106011
+ anon;
106012
+ data;
106013
+ env;
106014
+ envScan;
106015
+ export;
106016
+ logs;
106017
+ auth;
106018
+ billing;
106019
+ account;
106020
+ alerts;
106021
+ platform;
106022
+ security;
106023
+ search;
106024
+ sharing;
106025
+ invites;
106026
+ transfers;
106027
+ constructor(options = {}) {
106028
+ const defaults = loadConfig();
106029
+ this.apiUrl = options.apiUrl ?? defaults.apiUrl;
106030
+ this.token = options.token !== undefined ? options.token : defaults.token;
106031
+ this.sessionId = options.sessionId;
106032
+ const http = new Http({
106033
+ apiUrl: this.apiUrl,
106034
+ token: this.token,
106035
+ fetchImpl: options.fetchImpl,
106036
+ clientVersion: options.clientVersion,
106037
+ sessionId: options.sessionId
106038
+ });
106039
+ this.apps = new AppsResource(http);
106040
+ this.anon = new AnonResource(http);
106041
+ this.data = new DataResource(http);
106042
+ this.env = new EnvResource(http);
106043
+ this.envScan = new EnvScanResource(http);
106044
+ this.export = new ExportResource(http);
106045
+ this.logs = new LogsResource(http);
106046
+ this.auth = new AuthResource(http);
106047
+ this.billing = new BillingResource(http);
106048
+ this.account = new AccountResource(http);
106049
+ this.alerts = new AlertsResource(http);
106050
+ this.platform = new PlatformResource(http);
106051
+ this.security = new SecurityResource(http);
106052
+ this.search = new SearchResource(http);
106053
+ this.sharing = new SharingResource(http);
106054
+ this.invites = new InvitesResource(http);
106055
+ this.transfers = new TransfersResource(http);
106056
+ }
106057
+ }
106058
+ // ../core/dist/commands/claim.js
106059
+ var claimInputSchema = exports_external.object({
106060
+ app: exports_external.string().optional().describe("App name to claim (defaults to the persisted anon state or percher.toml)"),
106061
+ claimToken: exports_external.string().optional().describe("The one-time claim token returned by the anonymous publish (pcl_…)")
106062
+ });
106063
+ async function claim(ctx, input = {}) {
106064
+ const state = loadConfig().anonClaim;
106065
+ const app = input.app ?? state?.app ?? readPercherTomlAppName(ctx.cwd) ?? undefined;
106066
+ const claimToken = input.claimToken ?? state?.claimToken;
106067
+ if (!app) {
106068
+ return {
106069
+ status: "needs_token",
106070
+ summary: "No app to claim. Pass --app (or run from the project that published it) and the claim token."
106071
+ };
106072
+ }
106073
+ if (!claimToken) {
106074
+ return {
106075
+ status: "needs_token",
106076
+ summary: "No claim token found. Pass claimToken explicitly — it was returned by the anonymous publish."
106077
+ };
106078
+ }
106079
+ const result = await ctx.client.apps.claim(app, { claimToken });
106080
+ if (state)
106081
+ saveConfig({ anonClaim: null });
106082
+ return {
106083
+ status: "claimed",
106084
+ app: result.name,
106085
+ url: result.url,
106086
+ summary: `Claimed ${result.name} — anonymous restrictions lift on the next deploy.`
106087
+ };
106088
+ }
106089
+ // ../core/dist/commands/create.js
106090
+ import { join as join7, resolve as resolve4 } from "node:path";
106091
+
106092
+ // ../core/dist/templates.js
106093
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync4 } from "node:fs";
106094
+ import { basename, join as join6, normalize, relative, resolve as resolve3 } from "node:path";
106095
+ var __dirname = "C:\\dev\\Percher-lakebed-clean\\packages\\core\\dist";
106096
+ var TEMPLATES_DIR = resolve3(import.meta.dirname ?? __dirname, "../templates");
106097
+ var PLACEHOLDER = "__APP_NAME__";
106098
+ function listTemplates() {
106099
+ return readdirSync(TEMPLATES_DIR).filter((name) => {
106100
+ const full = join6(TEMPLATES_DIR, name);
106101
+ return statSync(full).isDirectory();
106102
+ });
106103
+ }
106104
+ function scaffoldTemplate(input) {
106105
+ const { template, appName, targetDir, force } = input;
106106
+ const available = listTemplates();
106107
+ if (!available.includes(template)) {
106108
+ throw new Error(`Unknown template "${template}". Available: ${available.join(", ")}`);
106109
+ }
106110
+ const absTarget = resolve3(targetDir);
106111
+ if (existsSync4(absTarget)) {
106112
+ const entries = readdirSync(absTarget);
106113
+ if (entries.length > 0 && !force) {
106114
+ throw new Error(`Target directory "${absTarget}" is not empty. Use --force to overwrite.`);
106115
+ }
106116
+ }
106117
+ const srcDir = join6(TEMPLATES_DIR, template);
106118
+ const files = [];
106119
+ function copyDir(src, dest) {
106120
+ mkdirSync4(dest, { recursive: true });
106121
+ for (const entry of readdirSync(src)) {
106122
+ const srcPath = join6(src, entry);
106123
+ const destPath = join6(dest, entry);
106124
+ const rel = relative(absTarget, normalize(destPath));
106125
+ if (rel.startsWith("..") || resolve3(destPath) === resolve3(absTarget, "..", basename(absTarget))) {
106126
+ throw new Error(`Template contains path traversal: ${entry}`);
106127
+ }
106128
+ if (statSync(srcPath).isDirectory()) {
106129
+ copyDir(srcPath, destPath);
106130
+ } else {
106131
+ if (existsSync4(destPath) && !force) {
106132
+ continue;
106133
+ }
106134
+ let content = readFileSync5(srcPath, "utf-8");
106135
+ content = content.replaceAll(PLACEHOLDER, appName);
106136
+ content = content.replace(/\r\n/g, `
106137
+ `);
106138
+ writeFileSync4(destPath, content, "utf-8");
106139
+ files.push(relative(absTarget, destPath).replace(/\\/g, "/"));
106140
+ }
106141
+ }
106142
+ }
106143
+ copyDir(srcDir, absTarget);
106144
+ return { files };
106145
+ }
106146
+
106147
+ // ../core/dist/commands/create.js
106148
+ var createInputSchema = exports_external.object({
106149
+ name: exports_external.string().regex(APP_NAME_RE, "must be 3-40 chars, lowercase, start with a letter, [a-z0-9-] only"),
106150
+ template: exports_external.string().min(1, "Template is required"),
106151
+ directory: exports_external.string().optional(),
106152
+ force: exports_external.boolean().optional()
106153
+ });
106154
+ async function create(ctx, input) {
106155
+ const available = listTemplates();
106156
+ if (!available.includes(input.template)) {
106157
+ throw new PercherCoreError(`Unknown template "${input.template}". Available: ${available.join(", ")}`, {
106158
+ code: "create.invalid-template",
106159
+ hint: `Run "percher create --list-templates" to see available templates.`
106160
+ });
106161
+ }
106162
+ let targetDir;
106163
+ if (input.directory) {
106164
+ targetDir = resolve4(ctx.cwd, input.directory);
106165
+ if (!targetDir.startsWith(resolve4(ctx.cwd))) {
106166
+ throw new PercherCoreError("Directory must be within the current workspace", {
106167
+ code: "create.failed"
106168
+ });
106169
+ }
106170
+ } else {
106171
+ targetDir = join7(ctx.cwd, input.name);
106172
+ }
106173
+ try {
106174
+ const result = scaffoldTemplate({
106175
+ template: input.template,
106176
+ appName: input.name,
106177
+ targetDir,
106178
+ force: input.force
106179
+ });
106180
+ return {
106181
+ ...result,
106182
+ name: input.name,
106183
+ template: input.template,
106184
+ directory: targetDir
106185
+ };
106186
+ } catch (err) {
106187
+ const msg = err instanceof Error ? err.message : String(err);
106188
+ if (msg.includes("not empty")) {
106189
+ throw new PercherCoreError(msg, {
106190
+ code: "create.dir-not-empty",
106191
+ hint: "Use --force to overwrite existing files."
106192
+ });
106193
+ }
106194
+ throw new PercherCoreError(msg, { code: "create.failed" });
106195
+ }
106196
+ }
106197
+ // ../core/dist/commands/dashboard.js
106198
+ var dashboardInputSchema = exports_external.object({
106199
+ app: exports_external.string().optional(),
106200
+ tab: exports_external.string().optional()
106201
+ });
106202
+ var DEFAULT_BASE = "https://percher.app";
106203
+ var TAB_SHAPE = /^[a-z][a-z0-9-]{0,31}$/;
106204
+ var SETTINGS_ANCHOR_TABS = new Set(["github", "env", "domains", "crash-diagnosis"]);
106205
+ async function dashboard(ctx, input = {}) {
106206
+ const name = input.app ?? readPercherTomlAppName(ctx.cwd);
106207
+ if (!name) {
106208
+ throw new PercherCoreError("No app specified and no percher.toml found", {
106209
+ code: "dashboard.no-app",
106210
+ hint: "Pass an app name or run from a directory with percher.toml."
106211
+ });
106183
106212
  }
106184
- listForApp(appRef) {
106185
- return this.http.get(`/apps/${encodeURIComponent(appRef)}/transfer`);
106213
+ const tab = input.tab ?? "overview";
106214
+ if (!TAB_SHAPE.test(tab)) {
106215
+ throw new PercherCoreError(`Invalid tab "${tab}"`, {
106216
+ code: "dashboard.invalid-tab",
106217
+ hint: "Tab must be lowercase letters, digits, and hyphens (≤32 chars)."
106218
+ });
106186
106219
  }
106187
- listIncoming() {
106188
- return this.http.get("/transfers/incoming");
106220
+ const base = ctx.env.PERCHER_DASHBOARD_URL ?? DEFAULT_BASE;
106221
+ const appBase = `${base}/apps/${encodeURIComponent(name)}`;
106222
+ const url2 = SETTINGS_ANCHOR_TABS.has(tab) ? `${appBase}/settings#${tab}` : `${appBase}?tab=${tab}`;
106223
+ await ctx.openUrl(url2);
106224
+ return url2;
106225
+ }
106226
+ // ../core/dist/commands/data.js
106227
+ var dataInputSchema = exports_external.object({
106228
+ app: exports_external.string().optional(),
106229
+ json: exports_external.boolean().optional()
106230
+ });
106231
+ async function data(ctx, input = {}) {
106232
+ const name = input.app ?? readPercherTomlAppName(ctx.cwd);
106233
+ if (!name) {
106234
+ throw new PercherCoreError("No app specified and no percher.toml found", {
106235
+ code: "data.no-app",
106236
+ hint: "Pass an app name or run from a directory with percher.toml."
106237
+ });
106189
106238
  }
106190
- accept(transferId) {
106191
- return this.http.post(`/transfers/${encodeURIComponent(transferId)}/accept`, {});
106239
+ const status = await ctx.client.data.getStatus(name);
106240
+ const adminLink = await ctx.client.data.getAdminLink(name);
106241
+ let collections = [];
106242
+ let stats = { collections: 0, totalRecords: 0 };
106243
+ let warning = null;
106244
+ try {
106245
+ const cols = await ctx.client.data.getCollections(name);
106246
+ collections = cols.map((c) => ({ name: c.name, type: c.type }));
106247
+ stats = await ctx.client.data.getStats(name);
106248
+ } catch (err) {
106249
+ const code = err.code;
106250
+ if (code === "PB_AUTH_FAILED") {
106251
+ warning = `Could not authenticate with PocketBase — collections + stats unavailable. Try \`percher data reset-superuser ${name}\` to rotate the credentials.`;
106252
+ } else {
106253
+ throw err;
106254
+ }
106192
106255
  }
106193
- decline(transferId) {
106194
- return this.http.post(`/transfers/${encodeURIComponent(transferId)}/decline`, {});
106256
+ return {
106257
+ mode: status.mode,
106258
+ status: status.status,
106259
+ version: status.version ?? null,
106260
+ collections,
106261
+ stats,
106262
+ adminUrl: adminLink.adminUrl ?? null,
106263
+ warning
106264
+ };
106265
+ }
106266
+ // ../core/dist/commands/data-export.js
106267
+ import { resolve as resolve5 } from "node:path";
106268
+ var dataExportInputSchema = exports_external.object({
106269
+ app: exports_external.string().optional().describe("App name or id. Defaults to the app in percher.toml."),
106270
+ out: exports_external.string().optional().describe("Output file path. Defaults to ./<app>-pb-<timestamp>.tar.gz in the current directory.")
106271
+ });
106272
+ async function dataExport(ctx, input) {
106273
+ const appName = input.app ?? readPercherTomlAppName(ctx.cwd);
106274
+ if (!appName) {
106275
+ throw new PercherCoreError("No app specified and no percher.toml found", {
106276
+ code: "data-export.no-app",
106277
+ hint: "Pass an app name with --app <name> or run from a directory with percher.toml."
106278
+ });
106195
106279
  }
106280
+ ctx.status?.(`Exporting ${appName} data...`);
106281
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
106282
+ const fileName = input.out ?? `${appName}-pb-${stamp}.tar.gz`;
106283
+ const path = resolve5(ctx.cwd, fileName);
106284
+ const res = await ctx.client.apps.streamDataExport(appName);
106285
+ const bytes = await writeResponseToFile(res, path);
106286
+ return {
106287
+ app: appName,
106288
+ path,
106289
+ bytes,
106290
+ exportedAt: new Date().toISOString()
106291
+ };
106196
106292
  }
106197
-
106198
- // ../client/dist/client.js
106199
- class PercherClient {
106200
- apiUrl;
106201
- token;
106202
- sessionId;
106203
- apps;
106204
- data;
106205
- env;
106206
- envScan;
106207
- export;
106208
- logs;
106209
- auth;
106210
- billing;
106211
- account;
106212
- alerts;
106213
- platform;
106214
- security;
106215
- search;
106216
- sharing;
106217
- invites;
106218
- transfers;
106219
- constructor(options = {}) {
106220
- const defaults = loadConfig();
106221
- this.apiUrl = options.apiUrl ?? defaults.apiUrl;
106222
- this.token = options.token !== undefined ? options.token : defaults.token;
106223
- this.sessionId = options.sessionId;
106224
- const http = new Http({
106225
- apiUrl: this.apiUrl,
106226
- token: this.token,
106227
- fetchImpl: options.fetchImpl,
106228
- clientVersion: options.clientVersion,
106229
- sessionId: options.sessionId
106293
+ // ../core/dist/commands/delete.js
106294
+ var deleteInputSchema = exports_external.object({
106295
+ app: exports_external.string().optional().describe("App name to delete (defaults to percher.toml app name)"),
106296
+ confirm: exports_external.boolean().describe("Must be true to confirm deletion. This is a destructive operation that permanently removes the app, its container, database, domains, env vars, and deploy history.")
106297
+ });
106298
+ async function deleteApp(ctx, input) {
106299
+ const name = input.app ?? readPercherTomlAppName(ctx.cwd);
106300
+ if (!name) {
106301
+ throw new PercherCoreError("No app specified and no percher.toml found", {
106302
+ code: "env.no-app",
106303
+ hint: "Pass an app name or run from a directory with percher.toml."
106304
+ });
106305
+ }
106306
+ if (!input.confirm) {
106307
+ throw new PercherCoreError(`Deletion requires explicit confirmation. Set confirm: true to delete "${name}" permanently.`, {
106308
+ code: "delete.confirmation-required",
106309
+ hint: `This will permanently delete the app "${name}" and all its data. Pass confirm: true to proceed.`
106230
106310
  });
106231
- this.apps = new AppsResource(http);
106232
- this.data = new DataResource(http);
106233
- this.env = new EnvResource(http);
106234
- this.envScan = new EnvScanResource(http);
106235
- this.export = new ExportResource(http);
106236
- this.logs = new LogsResource(http);
106237
- this.auth = new AuthResource(http);
106238
- this.billing = new BillingResource(http);
106239
- this.account = new AccountResource(http);
106240
- this.alerts = new AlertsResource(http);
106241
- this.platform = new PlatformResource(http);
106242
- this.security = new SecurityResource(http);
106243
- this.search = new SearchResource(http);
106244
- this.sharing = new SharingResource(http);
106245
- this.invites = new InvitesResource(http);
106246
- this.transfers = new TransfersResource(http);
106247
106311
  }
106312
+ await ctx.client.apps.delete(name);
106248
106313
  }
106249
106314
  // ../core/dist/commands/deploys.js
106250
106315
  var deploysListInputSchema = exports_external.object({
@@ -109277,6 +109342,30 @@ async function init(ctx, input = {}) {
109277
109342
  var insightsInputSchema = exports_external.object({
109278
109343
  app: exports_external.string().optional()
109279
109344
  });
109345
+ // ../core/dist/commands/inspect-link.js
109346
+ var inspectLinkInputSchema = exports_external.object({
109347
+ app: exports_external.string().optional().describe("App name (defaults to percher.toml)"),
109348
+ revoke: exports_external.boolean().optional().describe("Revoke the app's inspect link instead of creating one")
109349
+ });
109350
+ async function inspectLink(ctx, input = {}) {
109351
+ const app = input.app ?? readPercherTomlAppName(ctx.cwd);
109352
+ if (!app) {
109353
+ throw new PercherCoreError("No app specified and no percher.toml found", {
109354
+ code: "env.no-app",
109355
+ hint: "Pass an app name or run from a directory with percher.toml."
109356
+ });
109357
+ }
109358
+ if (input.revoke) {
109359
+ await ctx.client.apps.revokeInspectLink(app);
109360
+ return { revoked: true, app, summary: `Inspect link for ${app} revoked.` };
109361
+ }
109362
+ const { inspectUrl } = await ctx.client.apps.createInspectLink(app);
109363
+ return {
109364
+ inspectUrl,
109365
+ app,
109366
+ summary: `Share ${inspectUrl} — read-only status + deploy history, no login needed. Re-running rotates the link; --revoke kills it.`
109367
+ };
109368
+ }
109280
109369
  // ../core/dist/commands/login.js
109281
109370
  var loginInputSchema = exports_external.object({
109282
109371
  token: exports_external.string().optional(),
@@ -110276,24 +110365,23 @@ function topologicalSort(collections) {
110276
110365
  continue;
110277
110366
  if (!byName.has(target))
110278
110367
  continue;
110279
- dependsOn.get(c.name).add(target);
110280
- dependents.get(target).add(c.name);
110368
+ dependsOn.get(c.name)?.add(target);
110369
+ dependents.get(target)?.add(c.name);
110281
110370
  }
110282
110371
  }
110283
110372
  const inputOrder = collections.map((c) => c.name);
110284
110373
  const inDegree = new Map;
110285
110374
  for (const name of inputOrder)
110286
- inDegree.set(name, dependsOn.get(name).size);
110375
+ inDegree.set(name, dependsOn.get(name)?.size ?? 0);
110287
110376
  const ready = [];
110288
110377
  for (const name of inputOrder) {
110289
110378
  if (inDegree.get(name) === 0)
110290
110379
  ready.push(name);
110291
110380
  }
110292
110381
  const out = [];
110293
- while (ready.length > 0) {
110294
- const name = ready.shift();
110382
+ for (let name = ready.shift();name !== undefined; name = ready.shift()) {
110295
110383
  out.push(name);
110296
- for (const dependent of dependents.get(name)) {
110384
+ for (const dependent of dependents.get(name) ?? []) {
110297
110385
  const next = (inDegree.get(dependent) ?? 0) - 1;
110298
110386
  inDegree.set(dependent, next);
110299
110387
  if (next === 0)
@@ -111013,6 +111101,35 @@ async function previewBranchShow(ctx, input = {}) {
111013
111101
  import { existsSync as existsSync14 } from "node:fs";
111014
111102
  import { join as join20 } from "node:path";
111015
111103
 
111104
+ // ../shared/dist/pow.js
111105
+ import { createHash as createHash3 } from "node:crypto";
111106
+ function digest(nonce, solution) {
111107
+ return createHash3("sha256").update(`${nonce}:${solution}`).digest("hex");
111108
+ }
111109
+ function leadingZeroBits(hexDigest) {
111110
+ let bits = 0;
111111
+ for (const ch of hexDigest) {
111112
+ const nibble = Number.parseInt(ch, 16);
111113
+ if (nibble === 0) {
111114
+ bits += 4;
111115
+ continue;
111116
+ }
111117
+ bits += Math.clz32(nibble) - 28;
111118
+ break;
111119
+ }
111120
+ return bits;
111121
+ }
111122
+ function solvePow(nonce, difficulty, maxIterations = 50000000) {
111123
+ if (difficulty <= 0)
111124
+ return "0";
111125
+ for (let i = 0;i < maxIterations; i++) {
111126
+ const solution = i.toString(36);
111127
+ if (leadingZeroBits(digest(nonce, solution)) >= difficulty)
111128
+ return solution;
111129
+ }
111130
+ return null;
111131
+ }
111132
+
111016
111133
  // ../core/dist/event-renderer.js
111017
111134
  function renderDeployEvent(event) {
111018
111135
  if (event.stage === "queue") {
@@ -111819,7 +111936,8 @@ var publishInputSchema = exports_external.object({
111819
111936
  message: exports_external.string().optional().describe("Deploy note (visible in deploy history and version timeline)"),
111820
111937
  dryRun: exports_external.boolean().optional().describe("Show what would be deployed (file count, bytes, top files) without uploading or building. Useful for verifying the bundle before a real deploy."),
111821
111938
  noCache: exports_external.boolean().optional().describe("Skip the image-cache lookup and force a fresh build. Use this when you suspect the cached image doesn't reflect your latest source — the new build's resulting image still gets cached, so future deploys benefit."),
111822
- waitForLive: exports_external.boolean().optional().describe("If true (default), publish blocks until the deploy is live or failed. If false, publish returns as soon as the deploy is queued and the agent can resume with percher_wait_for_deploy. Recommended for AI agents — gives back control quickly with a deployId so they can decide between waiting, asking the user, or working on something else.")
111939
+ waitForLive: exports_external.boolean().optional().describe("If true (default), publish blocks until the deploy is live or failed. If false, publish returns as soon as the deploy is queued and the agent can resume with percher_wait_for_deploy. Recommended for AI agents — gives back control quickly with a deployId so they can decide between waiting, asking the user, or working on something else."),
111940
+ anonymous: exports_external.boolean().optional().describe("Publish WITHOUT an account. Creates a throwaway anonymous app that expires in 72h unless claimed (percher_claim_app). The app runs with no outbound network, no database, and no env vars until claimed. Only honored when the caller has no token; ignored once signed in. Server-gated — fails with ANON_DEPLOYS_DISABLED if the instance hasn't enabled it.")
111823
111941
  });
111824
111942
  var SUSPICIOUS_BYTES = 50 * 1024 * 1024;
111825
111943
  var SUSPICIOUS_FILES = 1e4;
@@ -112320,6 +112438,16 @@ async function executeDeploy(opts) {
112320
112438
  };
112321
112439
  }
112322
112440
  async function handleUnauthorized(ctx, config2, tarball, input) {
112441
+ if (input.anonymous) {
112442
+ return handleAnonymousPublish(ctx, config2, input, tarball);
112443
+ }
112444
+ const anonAlternative = {
112445
+ action: "retry",
112446
+ suggestedTool: "percher_publish",
112447
+ args: { ...input, anonymous: true },
112448
+ when: "to publish without an account (expires in 72h, claim later with percher_claim_app)"
112449
+ };
112450
+ const needsLoginRecovery = { ...RECOVERY_NEEDS_LOGIN, alternativeActions: [anonAlternative] };
112323
112451
  if (!ctx.interactiveLogin) {
112324
112452
  try {
112325
112453
  const deviceClient = new PercherClient({
@@ -112334,7 +112462,7 @@ async function handleUnauthorized(ctx, config2, tarball, input) {
112334
112462
  loginCode: userCode,
112335
112463
  fileCount: tarball.fileCount,
112336
112464
  bytes: tarball.bytes,
112337
- recovery: RECOVERY_NEEDS_LOGIN,
112465
+ recovery: needsLoginRecovery,
112338
112466
  summary: `Login required: open ${loginUrl} (code: ${userCode}).`,
112339
112467
  configPath: tomlPathFor2(ctx.cwd),
112340
112468
  bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
@@ -112349,7 +112477,7 @@ async function handleUnauthorized(ctx, config2, tarball, input) {
112349
112477
  explanation: "No valid Percher token found.",
112350
112478
  suggestion: "Run the percher_login tool first, or set PERCHER_TOKEN environment variable."
112351
112479
  },
112352
- recovery: RECOVERY_NEEDS_LOGIN,
112480
+ recovery: needsLoginRecovery,
112353
112481
  summary: "Login required — run percher_login or set PERCHER_TOKEN.",
112354
112482
  configPath: tomlPathFor2(ctx.cwd),
112355
112483
  bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
@@ -112379,6 +112507,87 @@ async function handleUnauthorized(ctx, config2, tarball, input) {
112379
112507
  missingBuildEnvKeys: []
112380
112508
  });
112381
112509
  }
112510
+ async function handleAnonymousPublish(ctx, config2, input, tarball) {
112511
+ const anonClient = new PercherClient({
112512
+ apiUrl: ctx.client.apiUrl,
112513
+ sessionId: ctx.client.sessionId
112514
+ });
112515
+ let created;
112516
+ try {
112517
+ const ch = await anonClient.anon.requestChallenge();
112518
+ const solution = solvePow(ch.nonce, ch.difficulty);
112519
+ if (solution === null) {
112520
+ throw new Error("proof-of-work unsolvable (difficulty too high)");
112521
+ }
112522
+ created = await anonClient.anon.createAnonApp({
112523
+ name: config2.app.name,
112524
+ powChallengeId: ch.challengeId,
112525
+ powSolution: solution
112526
+ });
112527
+ } catch (err) {
112528
+ const code = err.code;
112529
+ const detail = code === "ANON_CAPACITY" ? "Anonymous capacity is full right now." : "Anonymous deploys are not enabled on this instance.";
112530
+ return {
112531
+ status: "needs_login",
112532
+ fileCount: tarball.fileCount,
112533
+ bytes: tarball.bytes,
112534
+ error: {
112535
+ title: "Anonymous publish unavailable",
112536
+ explanation: detail,
112537
+ suggestion: "Run the percher_login tool to publish with an account instead."
112538
+ },
112539
+ recovery: RECOVERY_NEEDS_LOGIN,
112540
+ summary: `${detail} Log in to publish instead.`,
112541
+ configPath: tomlPathFor2(ctx.cwd),
112542
+ bundle: { fileCount: tarball.fileCount, bytes: tarball.bytes }
112543
+ };
112544
+ }
112545
+ saveConfig({
112546
+ token: created.token,
112547
+ anonClaim: {
112548
+ app: created.app.name,
112549
+ claimToken: created.claimToken,
112550
+ expiresAt: created.expiresAt
112551
+ }
112552
+ });
112553
+ const newClient = new PercherClient({
112554
+ apiUrl: ctx.client.apiUrl,
112555
+ token: created.token,
112556
+ sessionId: ctx.client.sessionId
112557
+ });
112558
+ ctx.client = newClient;
112559
+ ctx.setClient?.(newClient);
112560
+ config2.app.name = created.app.name;
112561
+ const t0 = Date.now();
112562
+ const freshTarball = await createTarball({ cwd: ctx.cwd, config: config2 });
112563
+ const packageMs = Date.now() - t0;
112564
+ const { app, firstDeploy } = await ensureApp2(ctx, config2);
112565
+ const result = await executeDeploy({
112566
+ ctx,
112567
+ config: config2,
112568
+ input,
112569
+ app,
112570
+ firstDeploy,
112571
+ tarball: freshTarball,
112572
+ t0,
112573
+ packageMs,
112574
+ missingBuildEnvKeys: []
112575
+ });
112576
+ return {
112577
+ ...result,
112578
+ anonymous: true,
112579
+ claim: {
112580
+ claimUrl: created.claimUrl,
112581
+ claimToken: created.claimToken,
112582
+ expiresAt: created.expiresAt
112583
+ },
112584
+ warnings: [
112585
+ ...result.warnings ?? [],
112586
+ `Anonymous app: expires ${created.expiresAt} unless claimed — run percher_claim_app (claim token is in claim.claimToken).`,
112587
+ "No outbound network, no database, and no env vars until claimed."
112588
+ ]
112589
+ };
112590
+ }
112382
112591
  async function loadOrGenerate(ctx) {
112383
112592
  const tomlPath = join20(ctx.cwd, "percher.toml");
112384
112593
  if (!existsSync14(tomlPath)) {
@@ -113761,9 +113970,43 @@ var PLAN_CATALOG = {
113761
113970
  vcpuPool: 4,
113762
113971
  diskPoolMb: 75000
113763
113972
  }
113973
+ },
113974
+ anonymous: {
113975
+ label: "Anonymous",
113976
+ priceEur: 0,
113977
+ hidden: true,
113978
+ limits: {
113979
+ apps: 1,
113980
+ deploysPerMonth: 20,
113981
+ deploysPerHour: 6,
113982
+ memory: "128mb",
113983
+ cpu: 0.25,
113984
+ pids: 64,
113985
+ activePreviewsPerApp: 0,
113986
+ customDomainsPerApp: 0,
113987
+ removesPercherBadge: false,
113988
+ autoSleep: true,
113989
+ inactivityWarningDays: 1,
113990
+ inactivityGraceDays: 1,
113991
+ deployRetention: 2,
113992
+ logRetentionDays: 1,
113993
+ buildLogRetentionDays: 1,
113994
+ cronJobs: 0,
113995
+ storageMb: 0,
113996
+ instances: 1,
113997
+ kvStorageMb: 0,
113998
+ uptimeIntervalSec: 0,
113999
+ dailyLiveDeploys: 10,
114000
+ dailyPreviewDeploys: 0,
114001
+ userConcurrentDeploys: 1,
114002
+ ramPoolMb: 128,
114003
+ vcpuPool: 0.25,
114004
+ diskPoolMb: 200
114005
+ }
113764
114006
  }
113765
114007
  };
113766
- var PLAN_IDS = Object.keys(PLAN_CATALOG);
114008
+ var ALL_PLAN_IDS = Object.keys(PLAN_CATALOG);
114009
+ var PLAN_IDS = Object.entries(PLAN_CATALOG).filter(([, info]) => !info.hidden).map(([id]) => id);
113767
114010
 
113768
114011
  // ../core/dist/index.js
113769
114012
  init_qr();
@@ -114392,7 +114635,16 @@ async function handlePublishResult(ctx, result, options = {}) {
114392
114635
  await writeLine(ctx, qr);
114393
114636
  } catch {}
114394
114637
  }
114395
- if (result.firstDeploy) {
114638
+ if (result.anonymous && result.claim) {
114639
+ await writeLine(ctx, "");
114640
+ await writeLine(ctx, import_picocolors4.default.bold("Anonymous app — claim it to keep it:"));
114641
+ await writeLine(ctx, ` Expires: ${new Date(result.claim.expiresAt).toLocaleString()}`);
114642
+ await writeLine(ctx, ` Claim token: ${result.claim.claimToken}`);
114643
+ await writeLine(ctx, ` ${import_picocolors4.default.cyan("percher claim")} claim into your account (lifts the limits on next deploy)`);
114644
+ for (const w of result.warnings ?? []) {
114645
+ await writeLine(ctx, import_picocolors4.default.dim(` ${w}`));
114646
+ }
114647
+ } else if (result.firstDeploy) {
114396
114648
  await writeLine(ctx, "");
114397
114649
  await writeLine(ctx, import_picocolors4.default.bold("First deploy — next steps:"));
114398
114650
  await writeLine(ctx, ` ${import_picocolors4.default.cyan("percher dev")} watch for changes and re-deploy`);
@@ -114417,7 +114669,7 @@ function registerAppsCommands(cli, ctx) {
114417
114669
  await writeLine(ctx, `${import_picocolors4.default.green("✓")} wrote ${file2}`);
114418
114670
  }
114419
114671
  });
114420
- cli.command("publish", "Publish this project to Percher (recommended deploy command)").option("--force", "Skip size warnings").option("--preview", "Deploy as preview (does not replace the live version)").option("-m, --message <msg>", "Deploy note (visible in deploy history)").option("--dry-run", "Show what would be deployed (file count, bytes) without uploading").option("--no-cache", "Skip image-cache lookup and force a fresh build (use when you suspect cached image is stale)").action(async (options) => {
114672
+ cli.command("publish", "Publish this project to Percher (recommended deploy command)").option("--force", "Skip size warnings").option("--preview", "Deploy as preview (does not replace the live version)").option("-m, --message <msg>", "Deploy note (visible in deploy history)").option("--dry-run", "Show what would be deployed (file count, bytes) without uploading").option("--no-cache", "Skip image-cache lookup and force a fresh build (use when you suspect cached image is stale)").option("--anonymous", "Publish without an account — a 72h sandbox you can claim later (where enabled)").action(async (options) => {
114421
114673
  const { detectCiBranchMessage: detectCiBranchMessage2 } = await Promise.resolve().then(() => exports_ci_env);
114422
114674
  let messageOverride = options.message;
114423
114675
  if (options.preview && !options.message) {
@@ -114432,7 +114684,8 @@ function registerAppsCommands(cli, ctx) {
114432
114684
  preview: options.preview,
114433
114685
  message: messageOverride,
114434
114686
  dryRun: options.dryRun,
114435
- noCache: options.cache === false
114687
+ noCache: options.cache === false,
114688
+ anonymous: options.anonymous
114436
114689
  });
114437
114690
  await handlePublishResult(ctx, result, { preview: options.preview });
114438
114691
  });
@@ -114625,6 +114878,21 @@ function registerAppsCommands(cli, ctx) {
114625
114878
  const result = await resume(ctx, { app });
114626
114879
  await writeLine(ctx, `${import_picocolors4.default.green("✓")} ${result.message}`);
114627
114880
  });
114881
+ 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) => {
114882
+ const result = await claim(ctx, { app: options.app, claimToken: options.claimToken });
114883
+ if (result.status === "claimed") {
114884
+ await writeLine(ctx, `${import_picocolors4.default.green("✓")} ${result.summary}`);
114885
+ } else {
114886
+ await writeLine(ctx, `${import_picocolors4.default.yellow("!")} ${result.summary}`);
114887
+ }
114888
+ });
114889
+ 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) => {
114890
+ const result = await inspectLink(ctx, {
114891
+ app: app ?? options.app,
114892
+ revoke: options.revoke
114893
+ });
114894
+ await writeLine(ctx, `${import_picocolors4.default.green("✓")} ${result.summary}`);
114895
+ });
114628
114896
  cli.command("cache <action> [app]", "Manage the app's build cache (reset)").action(async (action, app) => {
114629
114897
  if (action !== "reset") {
114630
114898
  throw new Error(`Unknown cache action "${action}". Use: percher cache reset [app]`);
@@ -115564,7 +115832,7 @@ async function renderError(stderr, error48) {
115564
115832
  // package.json
115565
115833
  var package_default = {
115566
115834
  name: "percher",
115567
- version: "0.5.0",
115835
+ version: "0.5.1",
115568
115836
  description: "CLI for the Percher hosting platform — publish apps with one command",
115569
115837
  type: "module",
115570
115838
  bin: {
@@ -115588,9 +115856,9 @@ var package_default = {
115588
115856
  typecheck: "tsc --noEmit"
115589
115857
  },
115590
115858
  dependencies: {
115591
- "@percher/client": "^0.2.7",
115592
- "@percher/core": "^0.4.0",
115593
- "@percher/toml": "^0.1.3",
115859
+ "@percher/client": "^0.2.10",
115860
+ "@percher/core": "^0.4.4",
115861
+ "@percher/toml": "^0.1.5",
115594
115862
  cac: "^7.0.0",
115595
115863
  picocolors: "^1.1.1"
115596
115864
  },