@patchgg/cli 0.1.0 → 0.1.2

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.
Files changed (3) hide show
  1. package/README.md +95 -65
  2. package/dist/cli.js +653 -174
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -49,14 +49,14 @@ var require_picocolors = __commonJS({
49
49
  var argv = p.argv || [];
50
50
  var env = p.env || {};
51
51
  var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
52
- var formatter = (open3, close, replace = open3) => (input2) => {
52
+ var formatter = (open3, close, replace2 = open3) => (input2) => {
53
53
  let string4 = "" + input2, index = string4.indexOf(close, open3.length);
54
- return ~index ? open3 + replaceClose(string4, close, replace, index) + close : open3 + string4 + close;
54
+ return ~index ? open3 + replaceClose(string4, close, replace2, index) + close : open3 + string4 + close;
55
55
  };
56
- var replaceClose = (string4, close, replace, index) => {
56
+ var replaceClose = (string4, close, replace2, index) => {
57
57
  let result = "", cursor = 0;
58
58
  do {
59
- result += string4.substring(cursor, index) + replace;
59
+ result += string4.substring(cursor, index) + replace2;
60
60
  cursor = index + close.length;
61
61
  index = string4.indexOf(close, cursor);
62
62
  } while (~index);
@@ -119,7 +119,9 @@ function fromResponse(response, body) {
119
119
  const error62 = body?.error;
120
120
  const code = error62?.code ?? `http_${response.status}`;
121
121
  const message = error62?.message ?? `The API answered ${response.status} ${response.statusText}`;
122
- return new CliError(exitCodeFor(response.status), code, message, error62?.details);
122
+ const failure2 = new CliError(exitCodeFor(response.status), code, message, error62?.details);
123
+ failure2.fromApi = true;
124
+ return failure2;
123
125
  }
124
126
  function exitCodeFor(status2) {
125
127
  if (status2 === 401 || status2 === 403 || status2 === 507) return EXIT.auth;
@@ -163,6 +165,8 @@ var init_errors = __esm({
163
165
  exitCode;
164
166
  code;
165
167
  details;
168
+ /** Patch's API said it, rather than pch itself. */
169
+ fromApi;
166
170
  };
167
171
  }
168
172
  });
@@ -172,7 +176,7 @@ var VERSION;
172
176
  var init_version = __esm({
173
177
  "src/version.ts"() {
174
178
  "use strict";
175
- VERSION = true ? "0.1.0" : JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
179
+ VERSION = true ? "0.1.2" : JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
176
180
  }
177
181
  });
178
182
 
@@ -39734,6 +39738,62 @@ init_errors();
39734
39738
  import { spawn } from "node:child_process";
39735
39739
  import { resolve } from "node:path";
39736
39740
  init_errors();
39741
+
39742
+ // src/install.ts
39743
+ import { existsSync, realpathSync } from "node:fs";
39744
+ import { createRequire } from "node:module";
39745
+ import { dirname as dirname2, join as join2, sep } from "node:path";
39746
+ var require2 = createRequire(import.meta.url);
39747
+ function seaApi() {
39748
+ try {
39749
+ return require2("node:sea");
39750
+ } catch {
39751
+ return null;
39752
+ }
39753
+ }
39754
+ var sea = seaApi();
39755
+ var isExecutable = sea?.isSea() ?? false;
39756
+ function embeddedText(key) {
39757
+ if (!sea || !isExecutable) throw new Error(`pch isn't an executable, so ${key} isn't in it.`);
39758
+ return sea.getAsset(key, "utf8");
39759
+ }
39760
+ function installKind(script = process.argv[1], versions = process.versions, executable = isExecutable) {
39761
+ if (executable) return "executable";
39762
+ if (versions.electron) return "desktop";
39763
+ let path2 = script ?? "";
39764
+ try {
39765
+ path2 = realpathSync(path2);
39766
+ } catch {
39767
+ }
39768
+ const parts = path2.split(/[\\/]/);
39769
+ const at = parts.lastIndexOf("node_modules");
39770
+ if (at < 0 || parts[at + 1] !== "@patchgg" || parts[at + 2] !== "cli") return "other";
39771
+ if (/[\\/]pnpm[\\/]global[\\/]/.test(path2)) return "pnpm";
39772
+ const owner = parts.slice(0, at).join(sep);
39773
+ return existsSync(join2(owner || sep, "package.json")) ? "other" : "npm";
39774
+ }
39775
+ function updateCommand(kind) {
39776
+ if (kind === "executable") return "pch update";
39777
+ if (kind === "npm") return "npm install -g @patchgg/cli";
39778
+ if (kind === "pnpm") return "pnpm add -g @patchgg/cli";
39779
+ return null;
39780
+ }
39781
+ function executableName(platform = process.platform, arch = process.arch) {
39782
+ const os = { linux: "linux", darwin: "macos", win32: "windows" }[platform];
39783
+ if (!os || arch !== "x64" && arch !== "arm64") return null;
39784
+ return `pch-${os}-${arch}${os === "windows" ? ".exe" : ""}`;
39785
+ }
39786
+ var CDNS = {
39787
+ production: "https://cdn.patch.gg",
39788
+ staging: "https://cdn-staging.patch.gg",
39789
+ local: null
39790
+ };
39791
+ function releasesUrl(env, vars = process.env) {
39792
+ const base = vars.PCH_CDN_URL ?? CDNS[env];
39793
+ return base ? `${base.replace(/\/+$/, "")}/cli` : null;
39794
+ }
39795
+
39796
+ // src/git.ts
39737
39797
  function runGit(args2, options = {}) {
39738
39798
  return new Promise((done, fail) => {
39739
39799
  const child = spawn("git", args2, {
@@ -39813,7 +39873,7 @@ function gitFailed(args2, result, advice = "") {
39813
39873
  );
39814
39874
  }
39815
39875
  var helperKey = (url2) => `credential.${url2}.helper`;
39816
- var PCH_GONE = "pch isn't where it was, so git can't sign in to Patch. Install it again with npm i -g @patchgg/cli, then run pch login.";
39876
+ var PCH_GONE = `pch isn't where it was, so git can't sign in to Patch. Install it again with ${isExecutable ? "the installer at https://docs.patch.gg/cli" : "npm i -g @patchgg/cli"}, then run pch login.`;
39817
39877
  function helperCommand({ config: config2, target }) {
39818
39878
  const usesToken = Boolean(process.env.PCH_TOKEN);
39819
39879
  const vars = {
@@ -39827,10 +39887,11 @@ function helperCommand({ config: config2, target }) {
39827
39887
  ELECTRON_RUN_AS_NODE: process.env.ELECTRON_RUN_AS_NODE
39828
39888
  };
39829
39889
  const settings = Object.entries(vars).filter(([, value]) => value).map(([name, value]) => `export ${name}=${shellQuote(value)}; `).join("");
39830
- const node2 = shellQuote(process.execPath);
39831
- const script = shellQuote(process.argv[1]);
39832
- const here = [node2, ...nodeFlags().map(shellQuote), script].join(" ");
39833
- return `!f() { ${settings}if [ -x ${node2} ] && [ -e ${script} ]; then ${here} git-credential "$1"; elif command -v pch >/dev/null; then pch git-credential "$1"; else echo "${PCH_GONE}" >&2; fi; }; f`;
39890
+ const program3 = shellQuote(process.execPath);
39891
+ const script = isExecutable ? null : shellQuote(process.argv[1]);
39892
+ const here = script ? [program3, ...nodeFlags().map(shellQuote), script].join(" ") : program3;
39893
+ const there = script ? `[ -x ${program3} ] && [ -e ${script} ]` : `[ -x ${program3} ]`;
39894
+ return `!f() { ${settings}if ${there}; then ${here} git-credential "$1"; elif command -v pch >/dev/null; then pch git-credential "$1"; else echo "${PCH_GONE}" >&2; fi; }; f`;
39834
39895
  }
39835
39896
  function nodeFlags() {
39836
39897
  return process.execArgv.map(
@@ -39920,6 +39981,12 @@ init_guide_words();
39920
39981
  // src/output.ts
39921
39982
  var import_picocolors = __toESM(require_picocolors(), 1);
39922
39983
  init_errors();
39984
+ var UNICODE = process.platform !== "win32" || Boolean(process.env.WT_SESSION || process.env.TERM_PROGRAM);
39985
+ var TICK = UNICODE ? "\u2714" : "\u221A";
39986
+ var SPINNER = UNICODE ? ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] : ["-", "\\", "|", "/"];
39987
+ function forAPerson(json2, agent2) {
39988
+ return !json2 && Boolean(process.stdout.isTTY && process.stderr.isTTY) && !agent2;
39989
+ }
39923
39990
  var Output = class {
39924
39991
  constructor(json2) {
39925
39992
  this.json = json2;
@@ -39940,6 +40007,21 @@ var Output = class {
39940
40007
  if (!this.json) process.stderr.write(`${import_picocolors.default.dim("\u203A")} ${text}
39941
40008
  `);
39942
40009
  }
40010
+ /**
40011
+ * A spinner on stderr, for a person at a terminal while pch waits on them. Call what it returns
40012
+ * to stop it: that takes its line away, so whatever comes next starts on a clean one.
40013
+ */
40014
+ spin(text) {
40015
+ let frame = 0;
40016
+ const draw = () => process.stderr.write(`\r${import_picocolors.default.cyan(SPINNER[frame++ % SPINNER.length])} ${text}`);
40017
+ draw();
40018
+ const timer = setInterval(draw, 80);
40019
+ timer.unref();
40020
+ return () => {
40021
+ clearInterval(timer);
40022
+ process.stderr.write("\r\x1B[2K");
40023
+ };
40024
+ }
39943
40025
  /**
39944
40026
  * How far a long job has got. With --json it's a line of JSON on stderr, so whoever's waiting can
39945
40027
  * see it's moving while stdout stays the one result.
@@ -39949,15 +40031,17 @@ var Output = class {
39949
40031
  `);
39950
40032
  else this.step(text);
39951
40033
  }
39952
- error(error62) {
40034
+ error(error62, command) {
39953
40035
  const failure2 = error62 instanceof CliError ? error62 : new CliError(
39954
40036
  EXIT.error,
39955
40037
  "unexpected_error",
39956
40038
  error62 instanceof Error ? error62.message : String(error62)
39957
40039
  );
40040
+ const feedback2 = feedbackFor(failure2, command);
39958
40041
  if (this.json) {
39959
40042
  const body = {
39960
- error: { code: failure2.code, message: failure2.message, details: failure2.details }
40043
+ error: { code: failure2.code, message: failure2.message, details: failure2.details },
40044
+ ...feedback2 ? { feedback: feedback2 } : {}
39961
40045
  };
39962
40046
  process.stderr.write(`${JSON.stringify(body)}
39963
40047
  `);
@@ -39970,10 +40054,19 @@ var Output = class {
39970
40054
  process.stderr.write(` ${import_picocolors.default.dim(issue2.path || "\xB7")} ${issue2.message}
39971
40055
  `);
39972
40056
  }
40057
+ if (feedback2) process.stderr.write(`${import_picocolors.default.dim(feedback2.say)}
40058
+ `);
39973
40059
  }
39974
40060
  return failure2.exitCode;
39975
40061
  }
39976
40062
  };
40063
+ function feedbackFor(failure2, command) {
40064
+ if (!failure2.fromApi && failure2.exitCode !== EXIT.error) return null;
40065
+ const tool = command ? ` --tool "${command}"` : "";
40066
+ return {
40067
+ say: `If this error didn't tell you what to do, or it looks like Patch's fault, tell Patch's team, then carry on: pch feedback send --kind call_error${tool} --error-code ${failure2.code} "<what got in your way>"`
40068
+ };
40069
+ }
39977
40070
  function issuesOf(details) {
39978
40071
  const issues = details?.issues;
39979
40072
  return Array.isArray(issues) ? issues : [];
@@ -40020,7 +40113,7 @@ async function login(ctx, options) {
40020
40113
  })
40021
40114
  )
40022
40115
  }));
40023
- const name = ctx.target.accountName ?? (target.env === "production" ? "default" : target.env);
40116
+ const name = ctx.target.accountName ?? defaultAccount(target.env);
40024
40117
  const account2 = {
40025
40118
  env: target.env,
40026
40119
  token: key.key,
@@ -40046,9 +40139,13 @@ async function login(ctx, options) {
40046
40139
  look_first: LOOK_FIRST
40047
40140
  },
40048
40141
  () => {
40049
- out.line(
40050
- `Signed in as ${import_picocolors2.default.bold(me.username ? `@${me.username}` : me.email)} on ${target.env} (account "${name}").`
40051
- );
40142
+ const who = import_picocolors2.default.bold(me.username ? `@${me.username}` : me.email);
40143
+ if (forAPerson(out.json, runningIn())) {
40144
+ const where = target.env === "production" ? "" : ` on ${target.env}`;
40145
+ const named = name === defaultAccount(target.env) ? "" : import_picocolors2.default.dim(` (account "${name}")`);
40146
+ return signedIn(ctx, `Signed in as ${who}${where}${named}`, me.username, git, gitIdentity);
40147
+ }
40148
+ out.line(`Signed in as ${who} on ${target.env} (account "${name}").`);
40052
40149
  if (!me.username) out.line(`Choose a username at ${target.origin}/welcome to publish.`);
40053
40150
  if (git.helper) out.line(`Git is signed in too: Patch repos at ${git.url} just work.`);
40054
40151
  if (gitIdentity) out.line(gitIdentity);
@@ -40075,15 +40172,53 @@ async function loginWithKey(ctx) {
40075
40172
  look_first: LOOK_FIRST
40076
40173
  },
40077
40174
  () => {
40078
- out.line(
40079
- `PCH_TOKEN works: ${import_picocolors2.default.bold(me.username ? `@${me.username}` : me.email)} on ${target.env}.`
40080
- );
40175
+ const who = import_picocolors2.default.bold(me.username ? `@${me.username}` : me.email);
40176
+ if (forAPerson(out.json, runningIn())) {
40177
+ const where = target.env === "production" ? "" : ` on ${target.env}`;
40178
+ return signedIn(ctx, `PCH_TOKEN works: ${who}${where}`, me.username, git, gitIdentity);
40179
+ }
40180
+ out.line(`PCH_TOKEN works: ${who} on ${target.env}.`);
40081
40181
  if (git.helper) out.line(`Git is signed in too: Patch repos at ${git.url} just work.`);
40082
40182
  if (gitIdentity) out.line(gitIdentity);
40083
40183
  out.line(LOOK_FIRST);
40084
40184
  }
40085
40185
  );
40086
40186
  }
40187
+ function defaultAccount(env) {
40188
+ return env === "production" ? "default" : env;
40189
+ }
40190
+ function signedIn(ctx, headline, username, git, gitIdentity) {
40191
+ const { out, target } = ctx;
40192
+ out.line(`${import_picocolors2.default.green(TICK)} ${headline}`);
40193
+ if (git.helper) out.line(import_picocolors2.default.dim(" Git works with Patch repos too."));
40194
+ if (!username) out.line(` Choose a username at ${target.origin}/welcome before you publish.`);
40195
+ if (gitIdentity) out.line(indented(gitIdentity));
40196
+ const pch = pchOn(target.env);
40197
+ const next = [
40198
+ [`${pch} search "<idea>"`, "find a game or block to start from"],
40199
+ [`${pch} agent install`, "give your coding agent Patch's tools"],
40200
+ [`${pch} guide`, "how to make, test and publish"]
40201
+ ];
40202
+ const width = Math.max(...next.map(([command]) => command.length));
40203
+ out.line();
40204
+ for (const [command, what] of next) {
40205
+ out.line(` ${import_picocolors2.default.cyan(command.padEnd(width))} ${import_picocolors2.default.dim(what)}`);
40206
+ }
40207
+ out.line();
40208
+ }
40209
+ function indented(text) {
40210
+ const width = Math.min(process.stdout.columns || 80, 100) - 2;
40211
+ const lines = [];
40212
+ let line = "";
40213
+ for (const word of text.split(" ")) {
40214
+ if (line && line.length + 1 + word.length > width) {
40215
+ lines.push(line);
40216
+ line = word;
40217
+ } else line = line ? `${line} ${word}` : word;
40218
+ }
40219
+ if (line) lines.push(line);
40220
+ return lines.map((each) => ` ${each}`).join("\n");
40221
+ }
40087
40222
  async function setUpGit(ctx) {
40088
40223
  const { gitUrl: url2 } = ctx.target;
40089
40224
  if (!url2) return { url: url2, helper: false, lfs: false };
@@ -40104,19 +40239,55 @@ async function withDeviceSession(ctx, options, use) {
40104
40239
  if (!started.response.ok) throw authError(started.body, "Couldn't start a login");
40105
40240
  const device = started.body;
40106
40241
  const code = formatUserCode(device.user_code);
40107
- process.stderr.write(
40108
- `This waits for a person to approve it in a browser, which can take a few minutes. The code only works while this keeps running, so don't put a short timeout on it.
40242
+ const opened = options.browser !== false && Boolean(process.stdout.isTTY);
40243
+ if (opened) openBrowser(device.verification_uri_complete);
40244
+ let stopWaiting = () => {
40245
+ };
40246
+ if (forAPerson(out.json, runningIn())) {
40247
+ const how = opened ? "It's open in your browser. Approve it there if it shows this code." : "Open the link, enter the code and approve it.";
40248
+ process.stderr.write(
40249
+ `
40250
+ ${import_picocolors2.default.dim("Open")} ${import_picocolors2.default.cyan(device.verification_uri)}
40251
+ ${import_picocolors2.default.dim("Code")} ${import_picocolors2.default.bold(code)}
40252
+
40253
+ ${import_picocolors2.default.dim(how)}
40254
+
40255
+ `
40256
+ );
40257
+ stopWaiting = out.spin("Waiting for you to approve\u2026");
40258
+ } else {
40259
+ process.stderr.write(
40260
+ `This waits for a person to approve it in a browser, which can take a few minutes. The code only works while this keeps running, so don't put a short timeout on it.
40109
40261
 
40110
40262
  To sign in, open ${import_picocolors2.default.bold(device.verification_uri)} and enter ${import_picocolors2.default.bold(code)}
40111
40263
 
40112
40264
  `
40113
- );
40114
- if (options.browser !== false && process.stdout.isTTY)
40115
- openBrowser(device.verification_uri_complete);
40116
- out.step("Waiting for you to approve in the browser\u2026");
40265
+ );
40266
+ out.step("Waiting for you to approve in the browser\u2026");
40267
+ }
40268
+ let sessionToken = null;
40269
+ try {
40270
+ sessionToken = await approval(ctx, device, options);
40271
+ } finally {
40272
+ stopWaiting();
40273
+ }
40274
+ if (!sessionToken) {
40275
+ throw new CliError(EXIT.auth, "expired_token", "The code expired. Run pch login again.");
40276
+ }
40277
+ try {
40278
+ return await use(createApi(target, { token: sessionToken }));
40279
+ } finally {
40280
+ await fetch(`${auth2}/sign-out`, {
40281
+ method: "POST",
40282
+ headers: { authorization: `Bearer ${sessionToken}`, "content-type": "application/json" },
40283
+ body: "{}"
40284
+ }).catch(() => void 0);
40285
+ }
40286
+ }
40287
+ async function approval(ctx, device, options) {
40288
+ const auth2 = `${ctx.target.origin}/api/auth`;
40117
40289
  let interval = (options.interval ?? device.interval) * 1e3;
40118
40290
  const deadline = Date.now() + device.expires_in * 1e3;
40119
- let sessionToken = null;
40120
40291
  while (Date.now() < deadline) {
40121
40292
  await sleep(interval);
40122
40293
  const polled = await postJson(`${auth2}/device/token`, {
@@ -40124,10 +40295,7 @@ To sign in, open ${import_picocolors2.default.bold(device.verification_uri)} and
40124
40295
  device_code: device.device_code,
40125
40296
  client_id: CLIENT_ID
40126
40297
  });
40127
- if (polled.response.ok) {
40128
- sessionToken = polled.body.access_token;
40129
- break;
40130
- }
40298
+ if (polled.response.ok) return polled.body.access_token;
40131
40299
  const error62 = polled.body.error;
40132
40300
  if (error62 === "authorization_pending") continue;
40133
40301
  if (error62 === "slow_down") {
@@ -40140,18 +40308,7 @@ To sign in, open ${import_picocolors2.default.bold(device.verification_uri)} and
40140
40308
  if (error62 === "expired_token") break;
40141
40309
  throw authError(polled.body, "The login failed");
40142
40310
  }
40143
- if (!sessionToken) {
40144
- throw new CliError(EXIT.auth, "expired_token", "The code expired. Run pch login again.");
40145
- }
40146
- try {
40147
- return await use(createApi(target, { token: sessionToken }));
40148
- } finally {
40149
- await fetch(`${auth2}/sign-out`, {
40150
- method: "POST",
40151
- headers: { authorization: `Bearer ${sessionToken}`, "content-type": "application/json" },
40152
- body: "{}"
40153
- }).catch(() => void 0);
40154
- }
40311
+ return null;
40155
40312
  }
40156
40313
  function runningIn(vars = process.env) {
40157
40314
  if (vars.CLAUDECODE === "1") return "Claude Code";
@@ -40308,7 +40465,7 @@ async function tokenRevoke(ctx, id) {
40308
40465
  var import_picocolors5 = __toESM(require_picocolors(), 1);
40309
40466
  import { constants } from "node:fs";
40310
40467
  import { access as access2, copyFile, lstat, mkdir as mkdir2, readdir as readdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
40311
- import { basename, dirname as dirname2, extname, join as join4, resolve as resolve4 } from "node:path";
40468
+ import { basename, dirname as dirname3, extname, join as join5, resolve as resolve4 } from "node:path";
40312
40469
  import { createInterface } from "node:readline";
40313
40470
 
40314
40471
  // src/project-files.ts
@@ -40316,7 +40473,7 @@ var import_semver = __toESM(require_semver2(), 1);
40316
40473
  import { createHash } from "node:crypto";
40317
40474
  import { createReadStream } from "node:fs";
40318
40475
  import { readdir, readFile as readFile2, stat } from "node:fs/promises";
40319
- import { join as join2, relative, resolve as resolve2, sep } from "node:path";
40476
+ import { join as join3, relative, resolve as resolve2, sep as sep2 } from "node:path";
40320
40477
 
40321
40478
  // node_modules/.pnpm/zod@4.6.7/node_modules/zod/index.js
40322
40479
  init_external();
@@ -42085,7 +42242,7 @@ var projectFileSchema = external_exports.object({
42085
42242
  var usage = (code, message, details) => new CliError(EXIT.usage, code, message, details);
42086
42243
  async function loadProject(dir) {
42087
42244
  const root = resolve2(dir);
42088
- const path2 = join2(root, "patch.json");
42245
+ const path2 = join3(root, "patch.json");
42089
42246
  let text;
42090
42247
  try {
42091
42248
  text = await readFile2(path2, "utf8");
@@ -42129,11 +42286,11 @@ async function loadProject(dir) {
42129
42286
  );
42130
42287
  }
42131
42288
  const paths = (await listFiles(filesRoot)).filter(
42132
- (name) => join2(filesRoot, name) !== path2 && !(filesRoot === root && (name === BRIEF_FILE || name.startsWith(`${BRIEF_FOLDER}/`)))
42289
+ (name) => join3(filesRoot, name) !== path2 && !(filesRoot === root && (name === BRIEF_FILE || name.startsWith(`${BRIEF_FOLDER}/`)))
42133
42290
  );
42134
42291
  const files = [];
42135
42292
  for (const name of paths) {
42136
- const absolute = join2(filesRoot, ...name.split("/"));
42293
+ const absolute = join3(filesRoot, ...name.split("/"));
42137
42294
  files.push({ path: name, absolute, ...await hashFile(absolute) });
42138
42295
  }
42139
42296
  return {
@@ -42179,11 +42336,11 @@ async function listFiles(root) {
42179
42336
  const found = [];
42180
42337
  async function walk(dir) {
42181
42338
  for (const entry of await readdir(dir, { withFileTypes: true })) {
42182
- const absolute = join2(dir, entry.name);
42339
+ const absolute = join3(dir, entry.name);
42183
42340
  if (entry.isDirectory()) {
42184
42341
  if (!SKIPPED_DIRS.has(entry.name)) await walk(absolute);
42185
42342
  } else if (entry.isFile() && !SKIPPED_FILES.has(entry.name)) {
42186
- found.push(relative(root, absolute).split(sep).join("/"));
42343
+ found.push(relative(root, absolute).split(sep2).join("/"));
42187
42344
  }
42188
42345
  }
42189
42346
  }
@@ -42204,7 +42361,7 @@ function hashFile(path2) {
42204
42361
  // src/commands/agent-guide.ts
42205
42362
  var import_picocolors4 = __toESM(require_picocolors(), 1);
42206
42363
  import { access, writeFile as writeFile2 } from "node:fs/promises";
42207
- import { join as join3, resolve as resolve3 } from "node:path";
42364
+ import { join as join4, resolve as resolve3 } from "node:path";
42208
42365
 
42209
42366
  // src/starter.ts
42210
42367
  var AGENT_GUIDE = `# Working on this project
@@ -42332,11 +42489,11 @@ Thumbs.db
42332
42489
 
42333
42490
  // src/commands/agent-guide.ts
42334
42491
  var AGENT_GUIDE_FILE = "AGENTS.md";
42335
- var GUIDES = [AGENT_GUIDE_FILE, "CLAUDE.md", join3(".claude", "CLAUDE.md")];
42492
+ var GUIDES = [AGENT_GUIDE_FILE, "CLAUDE.md", join4(".claude", "CLAUDE.md")];
42336
42493
  async function agentGuidesIn(root) {
42337
42494
  const found = [];
42338
42495
  for (const name of GUIDES) {
42339
- const there = await access(join3(root, name)).then(
42496
+ const there = await access(join4(root, name)).then(
42340
42497
  () => true,
42341
42498
  () => false
42342
42499
  );
@@ -42349,18 +42506,18 @@ async function agentGuideIn(root) {
42349
42506
  }
42350
42507
  async function writeAgentGuide(root) {
42351
42508
  const own2 = await agentGuideIn(root);
42352
- if (!own2) await writeFile2(join3(root, AGENT_GUIDE_FILE), AGENT_GUIDE);
42509
+ if (!own2) await writeFile2(join4(root, AGENT_GUIDE_FILE), AGENT_GUIDE);
42353
42510
  return { name: own2 ?? AGENT_GUIDE_FILE, written: !own2 };
42354
42511
  }
42355
42512
  async function agentGuide(ctx, dir) {
42356
42513
  const root = resolve3(dir);
42357
42514
  const { name, written } = await writeAgentGuide(root);
42358
- ctx.out.result({ path: join3(root, name), written }, () => {
42515
+ ctx.out.result({ path: join4(root, name), written }, () => {
42359
42516
  if (written) {
42360
- ctx.out.line(`Wrote ${import_picocolors4.default.bold(join3(root, name))}, for coding agents working in this folder.`);
42517
+ ctx.out.line(`Wrote ${import_picocolors4.default.bold(join4(root, name))}, for coding agents working in this folder.`);
42361
42518
  ctx.out.line(import_picocolors4.default.dim("Commit it: in a clone, pch publish refuses files that aren't."));
42362
42519
  } else {
42363
- ctx.out.line(`${join3(root, name)} is this folder's guide for agents, so it's left as it is.`);
42520
+ ctx.out.line(`${join4(root, name)} is this folder's guide for agents, so it's left as it is.`);
42364
42521
  ctx.out.line(
42365
42522
  import_picocolors4.default.dim("pch guide has how to run, check and publish, to add to it if it helps.")
42366
42523
  );
@@ -42413,7 +42570,7 @@ ${shown.join("\n")}
42413
42570
  ${said2.join("\n")}` : null;
42414
42571
  }
42415
42572
  async function gatherPictures(root, named, say) {
42416
- const folder = join4(root, BRIEF_FOLDER);
42573
+ const folder = join5(root, BRIEF_FOLDER);
42417
42574
  for (const path2 of named) {
42418
42575
  const from = resolve4(path2.trim().replace(/^["']|["']$/g, ""));
42419
42576
  const found = await lstat(from).catch(() => null);
@@ -42421,7 +42578,7 @@ async function gatherPictures(root, named, say) {
42421
42578
  say(`Left out ${path2}: pch takes a PNG, JPEG, GIF or WebP file it can read.`);
42422
42579
  continue;
42423
42580
  }
42424
- if (dirname2(from) === folder) continue;
42581
+ if (dirname3(from) === folder) continue;
42425
42582
  await mkdir2(folder, { recursive: true });
42426
42583
  await copyAside(from, folder);
42427
42584
  }
@@ -42433,7 +42590,7 @@ async function copyAside(from, folder) {
42433
42590
  const stem = basename(from, extension);
42434
42591
  for (let number4 = 1; ; number4++) {
42435
42592
  const name = number4 === 1 ? `${stem}${extension}` : `${stem}-${number4}${extension}`;
42436
- const copied = await copyFile(from, join4(folder, name), constants.COPYFILE_EXCL).then(
42593
+ const copied = await copyFile(from, join5(folder, name), constants.COPYFILE_EXCL).then(
42437
42594
  () => true,
42438
42595
  (error62) => {
42439
42596
  if (error62.code === "EEXIST") return false;
@@ -42457,7 +42614,7 @@ async function fromTerminal(ctx) {
42457
42614
  }
42458
42615
  async function agentBrief(ctx, dir) {
42459
42616
  const root = resolve4(dir);
42460
- const path2 = join4(root, BRIEF_FILE);
42617
+ const path2 = join5(root, BRIEF_FILE);
42461
42618
  const there = await access2(path2).then(
42462
42619
  () => true,
42463
42620
  () => false
@@ -42507,7 +42664,7 @@ async function agentBrief(ctx, dir) {
42507
42664
  async function guidesNotNaming(root) {
42508
42665
  const silent = [];
42509
42666
  for (const name of await agentGuidesIn(root)) {
42510
- const text = await readFile3(join4(root, name), "utf8").catch(() => "");
42667
+ const text = await readFile3(join5(root, name), "utf8").catch(() => "");
42511
42668
  if (!text.includes(BRIEF_FILE)) silent.push(name);
42512
42669
  }
42513
42670
  return silent;
@@ -42517,10 +42674,10 @@ async function guidesNotNaming(root) {
42517
42674
  var import_picocolors6 = __toESM(require_picocolors(), 1);
42518
42675
  init_errors();
42519
42676
  import { spawnSync } from "node:child_process";
42520
- import { existsSync } from "node:fs";
42677
+ import { existsSync as existsSync2 } from "node:fs";
42521
42678
  import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
42522
42679
  import { homedir as homedir2 } from "node:os";
42523
- import { delimiter, dirname as dirname3, join as join5, resolve as resolve5 } from "node:path";
42680
+ import { delimiter, dirname as dirname4, join as join6, resolve as resolve5 } from "node:path";
42524
42681
  var AGENT_IDS = ["claude", "codex", "cursor"];
42525
42682
  var AGENT_NAMES = {
42526
42683
  claude: "Claude Code",
@@ -42541,8 +42698,8 @@ async function agentInstall(ctx, named, options = {}) {
42541
42698
  const agents = [];
42542
42699
  for (const agent2 of wanted) agents.push(await install(agent2, server));
42543
42700
  const manual = agents.filter((entry) => entry.state === "manual");
42544
- const signedIn = Boolean(ctx.target.token);
42545
- ctx.out.result({ server, agents, signed_in: signedIn }, () => {
42701
+ const signedIn2 = Boolean(ctx.target.token);
42702
+ ctx.out.result({ server, agents, signed_in: signedIn2 }, () => {
42546
42703
  for (const entry of agents) {
42547
42704
  const name = AGENT_NAMES[entry.agent];
42548
42705
  if (entry.state === "manual") {
@@ -42554,7 +42711,7 @@ async function agentInstall(ctx, named, options = {}) {
42554
42711
  }
42555
42712
  }
42556
42713
  ctx.out.line(import_picocolors6.default.dim(`It runs ${commandLine(server)}, and holds no key.`));
42557
- if (!signedIn) ctx.out.line(`Sign in first, or its tools won't work: ${pchLogin(ctx)}.`);
42714
+ if (!signedIn2) ctx.out.line(`Sign in first, or its tools won't work: ${pchLogin(ctx)}.`);
42558
42715
  ctx.out.line(
42559
42716
  "Restart your agent to load its tools. Until then, pch guide prints the same guide."
42560
42717
  );
@@ -42617,7 +42774,7 @@ async function installCursor(server) {
42617
42774
  ...settings.mcpServers,
42618
42775
  [server.name]: { command: server.command, args: server.args }
42619
42776
  };
42620
- await mkdir3(dirname3(path2), { recursive: true });
42777
+ await mkdir3(dirname4(path2), { recursive: true });
42621
42778
  await writeFile4(path2, `${JSON.stringify(settings, null, 2)}
42622
42779
  `);
42623
42780
  return { agent: "cursor", state: had ? "updated" : "added", where: path2 };
@@ -42636,7 +42793,7 @@ function mcpServer({ target }, options = {}, found = {}) {
42636
42793
  return {
42637
42794
  name,
42638
42795
  command: process.execPath,
42639
- args: [...process.execArgv, resolve5(process.argv[1]), ...flags]
42796
+ args: isExecutable ? flags : [...process.execArgv, resolve5(process.argv[1]), ...flags]
42640
42797
  };
42641
42798
  }
42642
42799
  function commandLine(server) {
@@ -42657,7 +42814,7 @@ function pchLogin({ target }) {
42657
42814
  }
42658
42815
  function isHere(agent2) {
42659
42816
  if (onPath(agent2)) return true;
42660
- if (agent2 === "cursor") return existsSync(join5(homedir2(), ".cursor"));
42817
+ if (agent2 === "cursor") return existsSync2(join6(homedir2(), ".cursor"));
42661
42818
  return false;
42662
42819
  }
42663
42820
  function codexHas(server) {
@@ -42666,7 +42823,7 @@ function codexHas(server) {
42666
42823
  }
42667
42824
  function onPath(bin) {
42668
42825
  const names = process.platform === "win32" ? [`${bin}.cmd`, `${bin}.exe`, `${bin}.bat`, bin] : [bin];
42669
- return (process.env.PATH ?? "").split(delimiter).some((folder) => folder && names.some((name) => existsSync(join5(folder, name))));
42826
+ return (process.env.PATH ?? "").split(delimiter).some((folder) => folder && names.some((name) => existsSync2(join6(folder, name))));
42670
42827
  }
42671
42828
  function run(bin, args2) {
42672
42829
  const shell = process.platform === "win32";
@@ -42678,7 +42835,7 @@ function run(bin, args2) {
42678
42835
  var import_picocolors7 = __toESM(require_picocolors(), 1);
42679
42836
  import { createHash as createHash2 } from "node:crypto";
42680
42837
  import { mkdir as mkdir4, readFile as readFile5, stat as stat2, writeFile as writeFile5 } from "node:fs/promises";
42681
- import { basename as basename2, dirname as dirname4, extname as extname2, isAbsolute, join as join6, relative as relative2, resolve as resolve6, sep as sep2 } from "node:path";
42838
+ import { basename as basename2, dirname as dirname5, extname as extname2, isAbsolute, join as join7, relative as relative2, resolve as resolve6, sep as sep3 } from "node:path";
42682
42839
  import { setTimeout as sleep2 } from "node:timers/promises";
42683
42840
  init_errors();
42684
42841
  var DEFAULT_WAIT_SECONDS = 300;
@@ -42784,7 +42941,7 @@ async function assetsAdd(ctx, ids, options) {
42784
42941
  saved.push(path2);
42785
42942
  }
42786
42943
  if (!saved.length) continue;
42787
- const holds = saved.length === 1 ? saved[0] : dirname4(saved[0]);
42944
+ const holds = saved.length === 1 ? saved[0] : dirname5(saved[0]);
42788
42945
  credits.push({
42789
42946
  files: creditedFiles(holds, saved.length > 1, project2?.filesRoot ?? process.cwd()),
42790
42947
  license: item.license.spdx,
@@ -42871,7 +43028,7 @@ function placeInFolder(item, file2, taken) {
42871
43028
  taken.add(path2);
42872
43029
  return path2;
42873
43030
  }
42874
- var forwardSlashes = (path2) => path2.split(sep2).join("/");
43031
+ var forwardSlashes = (path2) => path2.split(sep3).join("/");
42875
43032
  var fromHere = (path2) => forwardSlashes(relative2(process.cwd(), path2));
42876
43033
  function creditedFiles(path2, folder, filesRoot) {
42877
43034
  const inRelease = relative2(filesRoot, path2);
@@ -42881,15 +43038,15 @@ function creditedFiles(path2, folder, filesRoot) {
42881
43038
  }
42882
43039
  async function projectFileFor(root) {
42883
43040
  const here = process.cwd();
42884
- for (let dir = root; ; dir = dirname4(dir)) {
42885
- const path2 = join6(dir, "patch.json");
43041
+ for (let dir = root; ; dir = dirname5(dir)) {
43042
+ const path2 = join7(dir, "patch.json");
42886
43043
  if (await stat2(path2).catch(() => null)) {
42887
43044
  const contents = await readJson(path2);
42888
43045
  if (!contents) return null;
42889
43046
  const files = typeof contents.files === "string" ? contents.files : ".";
42890
43047
  return { path: path2, filesRoot: resolve6(dir, files), contents };
42891
43048
  }
42892
- if (dir === here || relative2(here, dir).startsWith("..") || dir === dirname4(dir)) return null;
43049
+ if (dir === here || relative2(here, dir).startsWith("..") || dir === dirname5(dir)) return null;
42893
43050
  }
42894
43051
  }
42895
43052
  async function readJson(path2) {
@@ -42909,7 +43066,7 @@ async function addToCreditsFile(path2, lines) {
42909
43066
  async function addToRecipe(project2, credits) {
42910
43067
  const { recipe } = project2.contents;
42911
43068
  const inFile = typeof recipe === "string";
42912
- const path2 = inFile ? resolve6(dirname4(project2.path), recipe) : project2.path;
43069
+ const path2 = inFile ? resolve6(dirname5(project2.path), recipe) : project2.path;
42913
43070
  const held = inFile ? await readJson(path2) : recipe ?? {};
42914
43071
  if (!held || held.credits !== void 0 && !Array.isArray(held.credits)) return null;
42915
43072
  const before = held.credits ?? [];
@@ -42922,14 +43079,14 @@ async function addToRecipe(project2, credits) {
42922
43079
  }
42923
43080
  function inside(root, relative3) {
42924
43081
  const path2 = resolve6(root, relative3);
42925
- if (!path2.startsWith(root + sep2)) {
43082
+ if (!path2.startsWith(root + sep3)) {
42926
43083
  throw new CliError(EXIT.error, "asset_path_invalid", `${relative3} isn't inside ${root}.`);
42927
43084
  }
42928
43085
  return path2;
42929
43086
  }
42930
43087
  async function writeInside(root, relative3, contents) {
42931
43088
  const path2 = inside(root, relative3);
42932
- await mkdir4(dirname4(path2), { recursive: true });
43089
+ await mkdir4(dirname5(path2), { recursive: true });
42933
43090
  await writeFile5(path2, contents);
42934
43091
  }
42935
43092
  async function download(url2) {
@@ -42951,7 +43108,7 @@ async function saveChecked(url2, path2, sha256) {
42951
43108
  `${url2} didn't download as Patch says it should: try again.`
42952
43109
  );
42953
43110
  }
42954
- await mkdir4(dirname4(path2), { recursive: true });
43111
+ await mkdir4(dirname5(path2), { recursive: true });
42955
43112
  await writeFile5(path2, bytes);
42956
43113
  }
42957
43114
 
@@ -43116,9 +43273,9 @@ async function projectView(ctx, ref) {
43116
43273
  );
43117
43274
  }
43118
43275
  if (project2.urls?.play) {
43119
- const signedIn = ownOnly?.channel === "private" && !unserved ? ", signed in" : "";
43276
+ const signedIn2 = ownOnly?.channel === "private" && !unserved ? ", signed in" : "";
43120
43277
  const which = unserved && project2.latest_release ? ` (${project2.latest_release.version})` : "";
43121
- ctx.out.line(`Plays at: ${project2.urls.play}${signedIn}${which}`);
43278
+ ctx.out.line(`Plays at: ${project2.urls.play}${signedIn2}${which}`);
43122
43279
  }
43123
43280
  ctx.out.line(
43124
43281
  `Source: ${repo ? VISIBILITY_LABELS[repo.visibility] : SOURCE_LABELS[project2.source_visibility]} \xB7 code ${project2.code_license} \xB7 art ${project2.art_license}`
@@ -43454,8 +43611,8 @@ async function diffOf(ctx, project2, request) {
43454
43611
  }
43455
43612
  async function standing(ctx, project2, request) {
43456
43613
  const { diff, upToDate } = await diffOf(ctx, project2, request);
43457
- const update = upToDate === false ? await updateSteps(ctx, project2, request).catch(() => null) : null;
43458
- return { diff, fields: { up_to_date: upToDate, update } };
43614
+ const update2 = upToDate === false ? await updateSteps(ctx, project2, request).catch(() => null) : null;
43615
+ return { diff, fields: { up_to_date: upToDate, update: update2 } };
43459
43616
  }
43460
43617
  async function updateSteps(ctx, project2, request) {
43461
43618
  const repo = await unwrap(
@@ -43803,17 +43960,17 @@ async function changeRequestWithdraw(ctx, ref) {
43803
43960
 
43804
43961
  // src/commands/check.ts
43805
43962
  var import_picocolors10 = __toESM(require_picocolors(), 1);
43806
- import { existsSync as existsSync3 } from "node:fs";
43963
+ import { existsSync as existsSync4 } from "node:fs";
43807
43964
  import { mkdir as mkdir6, mkdtemp as mkdtemp2 } from "node:fs/promises";
43808
43965
  import { tmpdir as tmpdir2 } from "node:os";
43809
- import { basename as basename3, join as join11, resolve as resolve8 } from "node:path";
43966
+ import { basename as basename3, join as join12, resolve as resolve8 } from "node:path";
43810
43967
 
43811
43968
  // src/check/browser.ts
43812
43969
  import { spawn as spawn3 } from "node:child_process";
43813
- import { existsSync as existsSync2 } from "node:fs";
43970
+ import { existsSync as existsSync3 } from "node:fs";
43814
43971
  import { chmod as chmod2, mkdir as mkdir5, mkdtemp, rename, rm, writeFile as writeFile6 } from "node:fs/promises";
43815
43972
  import { tmpdir, userInfo } from "node:os";
43816
- import { delimiter as delimiter2, dirname as dirname5, join as join7, resolve as resolve7, sep as sep3 } from "node:path";
43973
+ import { delimiter as delimiter2, dirname as dirname6, join as join8, resolve as resolve7, sep as sep4 } from "node:path";
43817
43974
  import { inflateRawSync } from "node:zlib";
43818
43975
  init_errors();
43819
43976
  var VERSIONS = "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json";
@@ -43822,9 +43979,9 @@ function installedBrowsers(vars) {
43822
43979
  if (process.platform === "win32") {
43823
43980
  const places = [vars.ProgramFiles, vars["ProgramFiles(x86)"], vars.LOCALAPPDATA];
43824
43981
  return places.filter((place) => Boolean(place)).flatMap((place) => [
43825
- join7(place, "Google", "Chrome", "Application", "chrome.exe"),
43826
- join7(place, "Microsoft", "Edge", "Application", "msedge.exe"),
43827
- join7(place, "Chromium", "Application", "chrome.exe")
43982
+ join8(place, "Google", "Chrome", "Application", "chrome.exe"),
43983
+ join8(place, "Microsoft", "Edge", "Application", "msedge.exe"),
43984
+ join8(place, "Chromium", "Application", "chrome.exe")
43828
43985
  ]);
43829
43986
  }
43830
43987
  if (process.platform === "darwin") {
@@ -43835,7 +43992,7 @@ function installedBrowsers(vars) {
43835
43992
  ];
43836
43993
  }
43837
43994
  const names = ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser", "msedge"];
43838
- return (vars.PATH ?? "").split(delimiter2).filter(Boolean).flatMap((place) => names.map((name) => join7(place, name)));
43995
+ return (vars.PATH ?? "").split(delimiter2).filter(Boolean).flatMap((place) => names.map((name) => join8(place, name)));
43839
43996
  }
43840
43997
  function downloadPlatform() {
43841
43998
  const { platform, arch } = process;
@@ -43844,8 +44001,8 @@ function downloadPlatform() {
43844
44001
  if (platform === "linux") return arch === "x64" ? "linux64" : null;
43845
44002
  return null;
43846
44003
  }
43847
- var browsersFolder = () => join7(dirname5(configPath()), "browser");
43848
- var shellFile = (platform) => join7(
44004
+ var browsersFolder = () => join8(dirname6(configPath()), "browser");
44005
+ var shellFile = (platform) => join8(
43849
44006
  browsersFolder(),
43850
44007
  platform,
43851
44008
  `chrome-headless-shell-${platform}`,
@@ -43853,16 +44010,16 @@ var shellFile = (platform) => join7(
43853
44010
  );
43854
44011
  function findBrowser(named, vars = process.env) {
43855
44012
  if (named) {
43856
- if (!existsSync2(named)) {
44013
+ if (!existsSync3(named)) {
43857
44014
  throw new CliError(EXIT.usage, "browser_missing", `There's no browser at ${named}.`);
43858
44015
  }
43859
44016
  return { path: resolve7(named), headlessShell: /headless[-_]shell/i.test(named), from: "named" };
43860
44017
  }
43861
- const installed = installedBrowsers(vars).find((path2) => existsSync2(path2));
44018
+ const installed = installedBrowsers(vars).find((path2) => existsSync3(path2));
43862
44019
  if (installed) return { path: installed, headlessShell: false, from: "this PC" };
43863
44020
  const platform = downloadPlatform();
43864
44021
  const shell = platform && shellFile(platform);
43865
- return shell && existsSync2(shell) ? { path: shell, headlessShell: true, from: "downloaded" } : null;
44022
+ return shell && existsSync3(shell) ? { path: shell, headlessShell: true, from: "downloaded" } : null;
43866
44023
  }
43867
44024
  async function downloadBrowser(out) {
43868
44025
  const platform = downloadPlatform();
@@ -43873,7 +44030,7 @@ async function downloadBrowser(out) {
43873
44030
  `There's no browser on this PC, and none to download for ${process.platform} ${process.arch}. Install Chrome or Chromium, or name one with --browser.`
43874
44031
  );
43875
44032
  }
43876
- const folder = join7(browsersFolder(), platform);
44033
+ const folder = join8(browsersFolder(), platform);
43877
44034
  const failed3 = (why) => new CliError(
43878
44035
  EXIT.error,
43879
44036
  "browser_download_failed",
@@ -43921,7 +44078,7 @@ async function downloadBrowser(out) {
43921
44078
  await rm(folder, { recursive: true, force: true });
43922
44079
  await rename(unpacking, folder);
43923
44080
  const shell = shellFile(platform);
43924
- if (!existsSync2(shell)) throw failed3("the download didn't hold what it should");
44081
+ if (!existsSync3(shell)) throw failed3("the download didn't hold what it should");
43925
44082
  return { path: shell, headlessShell: true, from: "downloaded" };
43926
44083
  }
43927
44084
  async function unzip(zip, into) {
@@ -43943,14 +44100,14 @@ async function unzip(zip, into) {
43943
44100
  const name = zip.toString("utf8", at + 46, at + 46 + nameLength);
43944
44101
  at += 46 + nameLength + extraLength + commentLength;
43945
44102
  const target = resolve7(root, name);
43946
- if (!target.startsWith(root + sep3)) throw new Error(`a path leaves the zip: ${name}`);
44103
+ if (!target.startsWith(root + sep4)) throw new Error(`a path leaves the zip: ${name}`);
43947
44104
  if (name.endsWith("/")) {
43948
44105
  await mkdir5(target, { recursive: true });
43949
44106
  continue;
43950
44107
  }
43951
44108
  const start = local + 30 + zip.readUInt16LE(local + 26) + zip.readUInt16LE(local + 28);
43952
44109
  const body = zip.subarray(start, start + packed);
43953
- await mkdir5(dirname5(target), { recursive: true });
44110
+ await mkdir5(dirname6(target), { recursive: true });
43954
44111
  await writeFile6(target, method === 0 ? body : inflateRawSync(body));
43955
44112
  if (mode & 73) await chmod2(target, 493);
43956
44113
  }
@@ -44003,7 +44160,7 @@ var Browser = class _Browser {
44003
44160
  );
44004
44161
  }
44005
44162
  static async launch(found, sandboxOff) {
44006
- const profile = await mkdtemp(join7(tmpdir(), "pch-check-profile-"));
44163
+ const profile = await mkdtemp(join8(tmpdir(), "pch-check-profile-"));
44007
44164
  const child = spawn3(
44008
44165
  found.path,
44009
44166
  [
@@ -44196,7 +44353,7 @@ async function checkModel(file2) {
44196
44353
 
44197
44354
  // src/check/play.ts
44198
44355
  import { writeFile as writeFile7 } from "node:fs/promises";
44199
- import { join as join9 } from "node:path";
44356
+ import { join as join10 } from "node:path";
44200
44357
  import { setTimeout as wait } from "node:timers/promises";
44201
44358
 
44202
44359
  // src/check/pixels.ts
@@ -44284,7 +44441,7 @@ function changed(a, b) {
44284
44441
  import { createReadStream as createReadStream2 } from "node:fs";
44285
44442
  import { stat as stat3 } from "node:fs/promises";
44286
44443
  import { createServer } from "node:http";
44287
- import { join as join8 } from "node:path";
44444
+ import { join as join9 } from "node:path";
44288
44445
  var GAME_HOST_HEADERS = {
44289
44446
  "Content-Security-Policy": [
44290
44447
  "default-src 'self'",
@@ -44393,7 +44550,7 @@ async function serveGame(root, files) {
44393
44550
  notFound();
44394
44551
  return;
44395
44552
  }
44396
- const absolute = join8(root, ...path2.split("/"));
44553
+ const absolute = join9(root, ...path2.split("/"));
44397
44554
  const { type, encoding } = typeOf(path2);
44398
44555
  const html = type.startsWith("text/html");
44399
44556
  const headers = {
@@ -44700,7 +44857,7 @@ async function play(browser, options) {
44700
44857
  if (!shot) return null;
44701
44858
  const bytes = Buffer.from(shot.data, "base64");
44702
44859
  if (keep) {
44703
- const file2 = join9(options.out, name);
44860
+ const file2 = join10(options.out, name);
44704
44861
  await writeFile7(file2, bytes);
44705
44862
  observed.screenshots.push(file2);
44706
44863
  }
@@ -44824,7 +44981,7 @@ async function readRendering(browser, origin) {
44824
44981
 
44825
44982
  // src/check/report.ts
44826
44983
  import { readFile as readFile8 } from "node:fs/promises";
44827
- import { join as join10 } from "node:path";
44984
+ import { join as join11 } from "node:path";
44828
44985
  var NEEDS_GPU = /web ?gpu|web ?gl|graphics adapter|gpu adapter|no adapter/i;
44829
44986
  var seconds = (ms) => ms < 1e3 ? `${ms} ms` : `${(ms / 1e3).toFixed(ms < 1e4 ? 1 : 0)} s`;
44830
44987
  var limit = (ms) => `${ms / 1e3} s`;
@@ -44836,7 +44993,7 @@ async function pathsInText(root, files) {
44836
44993
  const byLowerCase = new Map(files.map((file2) => [file2.toLowerCase(), file2]));
44837
44994
  const found = [];
44838
44995
  for (const file2 of files.filter((name) => TEXT_FILES.test(name))) {
44839
- const text = await readFile8(join10(root, ...file2.split("/")), "utf8").catch(() => "");
44996
+ const text = await readFile8(join11(root, ...file2.split("/")), "utf8").catch(() => "");
44840
44997
  if (!text || text.length > TEXT_LIMIT) continue;
44841
44998
  const folder = file2.includes("/") ? file2.slice(0, file2.lastIndexOf("/") + 1) : "";
44842
44999
  for (const [, written] of text.matchAll(PATH_IN_TEXT)) {
@@ -45027,7 +45184,7 @@ var LABELS = {
45027
45184
  var ADVICE = "This is advice: nothing here stops you publishing, and it doesn't judge what kind of game this is. Patch's own test runs in Patch Desktop: pch test start.";
45028
45185
  async function findGame(dir) {
45029
45186
  const root = resolve8(dir);
45030
- if (existsSync3(join11(root, "patch.json"))) {
45187
+ if (existsSync4(join12(root, "patch.json"))) {
45031
45188
  const project2 = await loadProject(root);
45032
45189
  if (project2.recipe.mode !== "browser") {
45033
45190
  throw usage2(
@@ -45044,8 +45201,8 @@ async function findGame(dir) {
45044
45201
  };
45045
45202
  }
45046
45203
  for (const folder of ["", "dist", "build", "public", "out"]) {
45047
- if (existsSync3(join11(root, folder, "index.html"))) {
45048
- const here = join11(root, folder);
45204
+ if (existsSync4(join12(root, folder, "index.html"))) {
45205
+ const here = join12(root, folder);
45049
45206
  return { root: here, files: await listFiles(here), entry: "index.html", selfChecks: [] };
45050
45207
  }
45051
45208
  }
@@ -45072,9 +45229,9 @@ async function check2(ctx, dir, options) {
45072
45229
  );
45073
45230
  }
45074
45231
  const models = options.models === true ? await Promise.all(
45075
- game.files.filter((file2) => file2.toLowerCase().endsWith(".glb")).map((file2) => modelAt(join11(game.root, ...file2.split("/"))))
45232
+ game.files.filter((file2) => file2.toLowerCase().endsWith(".glb")).map((file2) => modelAt(join12(game.root, ...file2.split("/"))))
45076
45233
  ) : [];
45077
- const folder = options.out ? resolve8(options.out) : await mkdtemp2(join11(tmpdir2(), `pch-check-${basename3(resolve8(dir))}-`));
45234
+ const folder = options.out ? resolve8(options.out) : await mkdtemp2(join12(tmpdir2(), `pch-check-${basename3(resolve8(dir))}-`));
45078
45235
  await mkdir6(folder, { recursive: true });
45079
45236
  const found = findBrowser(options.browser) ?? await downloadBrowser(out);
45080
45237
  out.step(`Opening ${game.entry} from ${game.root}, muted, for ${seconds2} s or so`);
@@ -45217,7 +45374,7 @@ function printModels(ctx, models) {
45217
45374
  // src/commands/connect.ts
45218
45375
  var import_picocolors14 = __toESM(require_picocolors(), 1);
45219
45376
  import { readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
45220
- import { join as join14, resolve as resolve11 } from "node:path";
45377
+ import { join as join15, resolve as resolve11 } from "node:path";
45221
45378
  import { setTimeout as sleep5 } from "node:timers/promises";
45222
45379
  init_errors();
45223
45380
 
@@ -45265,7 +45422,7 @@ function sameName(a, b) {
45265
45422
  var import_picocolors13 = __toESM(require_picocolors(), 1);
45266
45423
  import { randomUUID as randomUUID2 } from "node:crypto";
45267
45424
  import { mkdir as mkdir7, readdir as readdir3, readFile as readFile11, rm as rm2, writeFile as writeFile9 } from "node:fs/promises";
45268
- import { join as join13, resolve as resolve10 } from "node:path";
45425
+ import { join as join14, resolve as resolve10 } from "node:path";
45269
45426
  import { setTimeout as sleep4 } from "node:timers/promises";
45270
45427
  init_errors();
45271
45428
  init_guide_words();
@@ -45307,7 +45464,7 @@ function unknownLicence(name) {
45307
45464
  );
45308
45465
  }
45309
45466
  async function licenceText(id, fill, texts = LICENCE_TEXTS) {
45310
- const text = await readFile9(new URL(`${id}.txt`, texts), "utf8");
45467
+ const text = isExecutable && texts === LICENCE_TEXTS ? embeddedText(`licences/${id}.txt`) : await readFile9(new URL(`${id}.txt`, texts), "utf8");
45311
45468
  return text.replace(
45312
45469
  /^Copyright \(c\) <year> <(?:copyright holders|owner)>/m,
45313
45470
  `Copyright (c) ${fill.year} ${fill.holder}`
@@ -45442,7 +45599,7 @@ async function versionTaken(ctx, ref, failure2) {
45442
45599
  // src/commands/init.ts
45443
45600
  var import_picocolors11 = __toESM(require_picocolors(), 1);
45444
45601
  import { readFile as readFile10, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
45445
- import { basename as basename4, join as join12, resolve as resolve9 } from "node:path";
45602
+ import { basename as basename4, join as join13, resolve as resolve9 } from "node:path";
45446
45603
  init_errors();
45447
45604
  init_guide_words();
45448
45605
  var FIRST_VERSION = "0.1.0";
@@ -45452,7 +45609,7 @@ function startingProjectFile(project2, version2 = FIRST_VERSION) {
45452
45609
  }
45453
45610
  async function init(ctx, dir, options) {
45454
45611
  const root = resolve9(dir);
45455
- const path2 = join12(root, "patch.json");
45612
+ const path2 = join13(root, "patch.json");
45456
45613
  if (!options.force && await readFile10(path2, "utf8").catch(() => null) !== null) {
45457
45614
  throw new CliError(
45458
45615
  EXIT.conflict,
@@ -45462,7 +45619,7 @@ async function init(ctx, dir, options) {
45462
45619
  }
45463
45620
  const name = await projectFor(ctx, root, options.project);
45464
45621
  const files = options.files ?? await builtFolder(root);
45465
- const hasRecipe = await stat4(join12(root, "recipe.json")).then(
45622
+ const hasRecipe = await stat4(join13(root, "recipe.json")).then(
45466
45623
  () => true,
45467
45624
  () => false
45468
45625
  );
@@ -45485,7 +45642,7 @@ async function init(ctx, dir, options) {
45485
45642
  "pch validate",
45486
45643
  "pch publish"
45487
45644
  ];
45488
- const agentGuide2 = { path: join12(root, guide2.name), written: guide2.written };
45645
+ const agentGuide2 = { path: join13(root, guide2.name), written: guide2.written };
45489
45646
  const result = { path: path2, ...contents, on_patch: onPatch2, agent_guide: agentGuide2 };
45490
45647
  ctx.out.result({ ...result, next, look_first: LOOK_FIRST }, () => {
45491
45648
  ctx.out.line(`Wrote ${import_picocolors11.default.bold(path2)} for ${import_picocolors11.default.bold(name)}.`);
@@ -45527,9 +45684,9 @@ async function builtFolder(root) {
45527
45684
  (found) => found.isFile(),
45528
45685
  () => false
45529
45686
  );
45530
- if (await isFile(join12(root, "index.html"))) return void 0;
45687
+ if (await isFile(join13(root, "index.html"))) return void 0;
45531
45688
  for (const folder of ["dist", "build", "public", "out"]) {
45532
- if (await isFile(join12(root, folder, "index.html"))) return folder;
45689
+ if (await isFile(join13(root, folder, "index.html"))) return folder;
45533
45690
  }
45534
45691
  return void 0;
45535
45692
  }
@@ -45775,7 +45932,7 @@ async function newRepo(ctx, ref, dir, options) {
45775
45932
  lfs,
45776
45933
  url: url2,
45777
45934
  look_first: LOOK_FIRST,
45778
- agent_guide: { path: join13(folder, AGENT_GUIDE_FILE), written: true },
45935
+ agent_guide: { path: join14(folder, AGENT_GUIDE_FILE), written: true },
45779
45936
  commits_as: commitsAs,
45780
45937
  ...options.visibility === "private" ? { who_can_see: PRIVATE_REPO } : {}
45781
45938
  },
@@ -45815,14 +45972,14 @@ async function clearFolder(folder, keepIt) {
45815
45972
  if (!keepIt) return rm2(folder, { recursive: true, force: true });
45816
45973
  const entries = await readdir3(folder).catch(() => []);
45817
45974
  await Promise.all(
45818
- entries.map((entry) => rm2(join13(folder, entry), { recursive: true, force: true }))
45975
+ entries.map((entry) => rm2(join14(folder, entry), { recursive: true, force: true }))
45819
45976
  );
45820
45977
  }
45821
45978
  async function firstCommit(ctx, folder, name, files) {
45822
45979
  await mkdir7(folder, { recursive: true });
45823
45980
  await mustGit(["init", "-q", "-b", FIRST_BRANCH], { cwd: folder });
45824
45981
  const commitsAs = await commitAsYourHandle(ctx, folder);
45825
- for (const [path2, text] of Object.entries(files)) await writeFile9(join13(folder, path2), text);
45982
+ for (const [path2, text] of Object.entries(files)) await writeFile9(join14(folder, path2), text);
45826
45983
  const lfs = await hasLfs();
45827
45984
  if (!lfs) ctx.out.step(NO_LFS);
45828
45985
  else if (!await lfsSetUp()) await setUpLfsHere(ctx, folder);
@@ -46018,6 +46175,11 @@ function showReview({ out }, project2, review) {
46018
46175
  out.line(
46019
46176
  ` ${ours ? nextStep(name, review.status, review.test, cloud).say : plain(review.next)}`
46020
46177
  );
46178
+ if (review.feedback_ask) {
46179
+ out.line(
46180
+ ` ${plain(review.feedback_ask.ask.replaceAll("send_feedback", "pch feedback send"))}`
46181
+ );
46182
+ }
46021
46183
  for (const run3 of review.check_runs) {
46022
46184
  out.line(` Checks (${run3.kind}): ${run3.status}${run3.summary ? `, ${run3.summary}` : ""}`);
46023
46185
  for (const finding of run3.findings) {
@@ -46200,7 +46362,7 @@ async function originOnGithub(ctx, dir) {
46200
46362
  );
46201
46363
  }
46202
46364
  async function readManifest(dir) {
46203
- const path2 = join14(dir, "patch.json");
46365
+ const path2 = join15(dir, "patch.json");
46204
46366
  let text;
46205
46367
  try {
46206
46368
  text = await readFile12(path2, "utf8");
@@ -46596,7 +46758,7 @@ async function nameInManifest(manifest, project2) {
46596
46758
  // src/commands/covers.ts
46597
46759
  var import_picocolors15 = __toESM(require_picocolors(), 1);
46598
46760
  import { mkdir as mkdir8, readFile as readFile13, writeFile as writeFile11 } from "node:fs/promises";
46599
- import { basename as basename5, extname as extname3, join as join15, resolve as resolve12 } from "node:path";
46761
+ import { basename as basename5, extname as extname3, join as join16, resolve as resolve12 } from "node:path";
46600
46762
  import { setTimeout as sleep7 } from "node:timers/promises";
46601
46763
  init_errors();
46602
46764
 
@@ -46817,13 +46979,13 @@ async function follow2(ctx, project2, job, wait2) {
46817
46979
  async function save(ctx, job, folder) {
46818
46980
  const ready = job.takes.filter((take) => take.state === "ready" && take.url);
46819
46981
  if (!ready.length || folder === void 0) return [];
46820
- const dir = resolve12(folder || join15("covers", job.id));
46982
+ const dir = resolve12(folder || join16("covers", job.id));
46821
46983
  await mkdir8(dir, { recursive: true });
46822
46984
  ctx.out.step(`Saving ${ready.length} take${ready.length === 1 ? "" : "s"} to ${dir}`);
46823
46985
  const saved = [];
46824
46986
  for (const take of ready) {
46825
- const file2 = join15(dir, `${take.shape}-${take.id}.jpg`);
46826
- const preview = join15(dir, `${take.shape}-${take.id}-card.jpg`);
46987
+ const file2 = join16(dir, `${take.shape}-${take.id}.jpg`);
46988
+ const preview = join16(dir, `${take.shape}-${take.id}-card.jpg`);
46827
46989
  for (const [url2, path2] of [
46828
46990
  [take.url, file2],
46829
46991
  [take.preview_url, preview]
@@ -46938,10 +47100,85 @@ function outFolder(out, byDefault) {
46938
47100
  return byDefault ? "" : void 0;
46939
47101
  }
46940
47102
 
46941
- // src/commands/git.ts
47103
+ // src/commands/feedback.ts
46942
47104
  var import_picocolors16 = __toESM(require_picocolors(), 1);
47105
+ var FEEDBACK_KINDS = ["failed", "bug", "call_error", "unhelpful", "idea"];
47106
+ var FEEDBACK_SEVERITIES = ["blocked", "slowed", "annoyed"];
47107
+ async function feedbackSend(ctx, summary, options) {
47108
+ requireToken(ctx.target);
47109
+ const sent = await unwrap(
47110
+ ctx.api.POST("/feedback", {
47111
+ body: {
47112
+ kind: options.kind,
47113
+ summary,
47114
+ goal: options.goal,
47115
+ expected: options.expected,
47116
+ actual: options.actual,
47117
+ tried: options.tried,
47118
+ workaround: options.workaround,
47119
+ cost: options.cost,
47120
+ severity: options.severity,
47121
+ add_to: options.addTo,
47122
+ details: options.details,
47123
+ would_help: options.wouldHelp,
47124
+ tool: options.tool,
47125
+ error_code: options.errorCode,
47126
+ project: options.project,
47127
+ agent: options.agent
47128
+ }
47129
+ })
47130
+ );
47131
+ const shown = { ...sent, next: sent.next.replaceAll("send_feedback", "pch feedback send") };
47132
+ ctx.out.result(shown, () => {
47133
+ ctx.out.line(
47134
+ `${options.addTo ? "Added to your report" : "Told Patch's team"}: ${import_picocolors16.default.bold(plain(sent.summary))}`
47135
+ );
47136
+ if (sent.known) {
47137
+ ctx.out.line(`Patch's team already answered a report like it: ${plain(sent.known.answer)}`);
47138
+ }
47139
+ if (sent.follow_up) {
47140
+ const flag = sent.follow_up.field.replaceAll("_", "-");
47141
+ ctx.out.line(`One more thing would help most: ${plain(sent.follow_up.question)}`);
47142
+ ctx.out.line(
47143
+ import_picocolors16.default.dim(
47144
+ ` pch feedback send --kind ${sent.kind} --add-to ${sent.id} --${flag} "..." "${plain(sent.summary)}"`
47145
+ )
47146
+ );
47147
+ } else {
47148
+ ctx.out.line(import_picocolors16.default.dim(plain(shown.next)));
47149
+ }
47150
+ });
47151
+ }
47152
+ async function feedbackList(ctx) {
47153
+ requireToken(ctx.target);
47154
+ const { data } = await unwrap(ctx.api.GET("/feedback"));
47155
+ ctx.out.result({ data }, () => {
47156
+ if (!data.length) {
47157
+ ctx.out.line("No reports yet. pch feedback send tells Patch's team what got in your way.");
47158
+ return;
47159
+ }
47160
+ for (const report of data) {
47161
+ ctx.out.line(
47162
+ `${import_picocolors16.default.dim(report.created_at.slice(0, 10))} ${statusWord(report.status)} ${plain(report.summary)}`
47163
+ );
47164
+ if (report.answer) ctx.out.line(` ${import_picocolors16.default.dim("Patch:")} ${plain(report.answer)}`);
47165
+ if (report.status === "open" && report.quality < 50) {
47166
+ ctx.out.line(import_picocolors16.default.dim(` Add to it: pch feedback send --add-to ${report.id} ...`));
47167
+ }
47168
+ }
47169
+ });
47170
+ }
47171
+ function statusWord(status2) {
47172
+ if (status2 === "fixed") return import_picocolors16.default.green("fixed ");
47173
+ if (status2 === "planned") return import_picocolors16.default.cyan("planned ");
47174
+ if (status2 === "declined") return import_picocolors16.default.dim("declined");
47175
+ return import_picocolors16.default.yellow("open ");
47176
+ }
47177
+
47178
+ // src/commands/git.ts
47179
+ var import_picocolors17 = __toESM(require_picocolors(), 1);
46943
47180
  import { stat as stat5 } from "node:fs/promises";
46944
- import { basename as basename6, join as join16, resolve as resolve13 } from "node:path";
47181
+ import { basename as basename6, join as join17, resolve as resolve13 } from "node:path";
46945
47182
 
46946
47183
  // src/direct-push.ts
46947
47184
  init_errors();
@@ -47674,8 +47911,8 @@ async function push(ctx, given) {
47674
47911
  },
47675
47912
  () => {
47676
47913
  const list = sent.map(({ ref, status: status2 }) => `${shortRef(ref)}${status2 === "deleted" ? " (deleted)" : ""}`).join(", ");
47677
- if (list) out.line(`${dryRun ? "Would push" : "Pushed"} ${list} to ${import_picocolors16.default.bold(repo)}.`);
47678
- else if (!quiet) out.line(`Nothing new to push to ${import_picocolors16.default.bold(repo)}.`);
47914
+ if (list) out.line(`${dryRun ? "Would push" : "Pushed"} ${list} to ${import_picocolors17.default.bold(repo)}.`);
47915
+ else if (!quiet) out.line(`Nothing new to push to ${import_picocolors17.default.bold(repo)}.`);
47679
47916
  if (pushed.notice) out.line(pushed.notice);
47680
47917
  }
47681
47918
  );
@@ -47706,7 +47943,7 @@ async function clone2(ctx, ref, dir) {
47706
47943
  if (lfsForThisOne) await setUpLfsHere(ctx, into);
47707
47944
  const commitsAs = target.token ? await commitAsYourHandle(ctx, into) : null;
47708
47945
  const guide2 = await agentGuideIn(into);
47709
- const brief = await stat5(join16(into, BRIEF_FILE)).then(
47946
+ const brief = await stat5(join17(into, BRIEF_FILE)).then(
47710
47947
  (found) => found.isFile(),
47711
47948
  () => false
47712
47949
  );
@@ -47722,10 +47959,10 @@ async function clone2(ctx, ref, dir) {
47722
47959
  brief: brief ? BRIEF_FILE : null
47723
47960
  },
47724
47961
  () => {
47725
- out.line(`Cloned ${import_picocolors16.default.bold(`${project2.owner}/${project2.slug}`)} into ${basename6(into)}`);
47962
+ out.line(`Cloned ${import_picocolors17.default.bold(`${project2.owner}/${project2.slug}`)} into ${basename6(into)}`);
47726
47963
  if (commitsAs) out.line(saidCommitsAs(commitsAs));
47727
- out.line(import_picocolors16.default.dim("Commit and push with plain git."));
47728
- if (!guide2) out.line(import_picocolors16.default.dim("It has no guide for coding agents: pch agent guide adds one."));
47964
+ out.line(import_picocolors17.default.dim("Commit and push with plain git."));
47965
+ if (!guide2) out.line(import_picocolors17.default.dim("It has no guide for coding agents: pch agent guide adds one."));
47729
47966
  if (brief) {
47730
47967
  out.line(
47731
47968
  repo.forked_from ? `Its ${BRIEF_FILE} came from ${repo.forked_from}, so it's that game's idea: keep what you want, then change or delete it.` : `Its ${BRIEF_FILE} is this game's idea and rules: read it first.`
@@ -47736,7 +47973,7 @@ async function clone2(ctx, ref, dir) {
47736
47973
  }
47737
47974
 
47738
47975
  // src/commands/media.ts
47739
- var import_picocolors17 = __toESM(require_picocolors(), 1);
47976
+ var import_picocolors18 = __toESM(require_picocolors(), 1);
47740
47977
  import { readFile as readFile14 } from "node:fs/promises";
47741
47978
  import { basename as basename7, extname as extname4, resolve as resolve14 } from "node:path";
47742
47979
  import { setTimeout as sleep8 } from "node:timers/promises";
@@ -47832,11 +48069,11 @@ function showAdded(ctx, ref, media2) {
47832
48069
  );
47833
48070
  }
47834
48071
  ctx.out.result(media2, () => {
47835
- ctx.out.line(`${import_picocolors17.default.green(`Added the ${media2.kind}`)} to ${import_picocolors17.default.bold(ref)}: ${media2.status}.`);
47836
- if (media2.status === "ready") ctx.out.line(import_picocolors17.default.dim(media2.url));
48072
+ ctx.out.line(`${import_picocolors18.default.green(`Added the ${media2.kind}`)} to ${import_picocolors18.default.bold(ref)}: ${media2.status}.`);
48073
+ if (media2.status === "ready") ctx.out.line(import_picocolors18.default.dim(media2.url));
47837
48074
  else {
47838
48075
  ctx.out.line(
47839
- import_picocolors17.default.dim(
48076
+ import_picocolors18.default.dim(
47840
48077
  `It shows once it's ready, which takes seconds for a picture, and for a clip about as long as it plays. Check with pch media list ${ref}.`
47841
48078
  )
47842
48079
  );
@@ -47856,7 +48093,7 @@ async function mediaList(ctx, ref) {
47856
48093
  for (const entry of media2.data) {
47857
48094
  const failed3 = entry.status === "failed" && entry.error ? `: ${entry.error}` : "";
47858
48095
  ctx.out.line(
47859
- `${entry.id} ${entry.kind.padEnd(10)} ${entry.status.padEnd(10)} ${formatSize(entry.size).padStart(8)}${failed3}${entry.alt ? import_picocolors17.default.dim(` ${entry.alt}`) : ""}`
48096
+ `${entry.id} ${entry.kind.padEnd(10)} ${entry.status.padEnd(10)} ${formatSize(entry.size).padStart(8)}${failed3}${entry.alt ? import_picocolors18.default.dim(` ${entry.alt}`) : ""}`
47860
48097
  );
47861
48098
  }
47862
48099
  });
@@ -47884,7 +48121,7 @@ async function mediaOrder(ctx, ref, ids) {
47884
48121
  }
47885
48122
 
47886
48123
  // src/commands/publish.ts
47887
- var import_picocolors18 = __toESM(require_picocolors(), 1);
48124
+ var import_picocolors19 = __toESM(require_picocolors(), 1);
47888
48125
  var import_semver2 = __toESM(require_semver2(), 1);
47889
48126
  import { open as open2, readFile as readFile15 } from "node:fs/promises";
47890
48127
  init_errors();
@@ -47991,7 +48228,7 @@ async function publish(ctx, dir, options) {
47991
48228
  out.result(result, () => {
47992
48229
  if (!submitted) {
47993
48230
  out.line(
47994
- `Uploaded ${import_picocolors18.default.bold(name)} as a private draft. It can't be published until it has passed a test on your PC, in Patch Desktop, where someone presses Start.`
48231
+ `Uploaded ${import_picocolors19.default.bold(name)} as a private draft. It can't be published until it has passed a test on your PC, in Patch Desktop, where someone presses Start.`
47995
48232
  );
47996
48233
  out.line(privateWords(release2.kind, true));
47997
48234
  if (openCode) out.line(openCode);
@@ -48009,7 +48246,7 @@ async function publish(ctx, dir, options) {
48009
48246
  ` ${entry.path.padEnd(40)} ${formatSize(entry.size).padStart(8)} sha256 ${entry.sha256}`
48010
48247
  );
48011
48248
  }
48012
- out.line(import_picocolors18.default.dim(url2));
48249
+ out.line(import_picocolors19.default.dim(url2));
48013
48250
  });
48014
48251
  }
48015
48252
  function fromCommit(source) {
@@ -48029,7 +48266,7 @@ async function releaseSubmit(ctx, ref) {
48029
48266
  ctx.out.result({ ...submitted, next, ...whoCanSee }, () => {
48030
48267
  showSubmitted(ctx, release2, submitted.status, submitted);
48031
48268
  if (openCode) ctx.out.line(openCode);
48032
- ctx.out.line(import_picocolors18.default.dim(projectUrl(ctx.target.origin, release2)));
48269
+ ctx.out.line(import_picocolors19.default.dim(projectUrl(ctx.target.origin, release2)));
48033
48270
  });
48034
48271
  }
48035
48272
  async function releasePromote(ctx, ref, options) {
@@ -48083,7 +48320,7 @@ async function releasePromote(ctx, ref, options) {
48083
48320
  const next = nextStep(name, "draft", null).command;
48084
48321
  ctx.out.result({ from: ref, release: { version: version2, status: "draft" }, url: url2, next }, () => {
48085
48322
  ctx.out.line(
48086
- `Made ${import_picocolors18.default.bold(name)} from ${ref}, as a draft for ${options.channel}. It needs a passed test of its own.`
48323
+ `Made ${import_picocolors19.default.bold(name)} from ${ref}, as a draft for ${options.channel}. It needs a passed test of its own.`
48087
48324
  );
48088
48325
  ctx.out.line(`Next: ${next}`);
48089
48326
  });
@@ -48091,7 +48328,7 @@ async function releasePromote(ctx, ref, options) {
48091
48328
  }
48092
48329
  ctx.out.result({ from: ref, release: submitted, url: url2 }, () => {
48093
48330
  showSubmitted(ctx, to, submitted.status, submitted);
48094
- ctx.out.line(import_picocolors18.default.dim(url2));
48331
+ ctx.out.line(import_picocolors19.default.dim(url2));
48095
48332
  });
48096
48333
  }
48097
48334
  function draftUntilTested(error62) {
@@ -48128,7 +48365,7 @@ function whoCanSeeIt(...words2) {
48128
48365
  function showSubmitted(ctx, ref, status2, { channel, kind }) {
48129
48366
  const name = releaseName(ref);
48130
48367
  if (status2 === "published") {
48131
- ctx.out.line(import_picocolors18.default.green(`Published ${name} to ${channel}`));
48368
+ ctx.out.line(import_picocolors19.default.green(`Published ${name} to ${channel}`));
48132
48369
  if (channel === "private") {
48133
48370
  ctx.out.line(privateWords(kind, false));
48134
48371
  ctx.out.line(`pch status ${name} says whether your team can play it yet, and where.`);
@@ -48282,11 +48519,11 @@ async function uploadFailed(file2, upload, response) {
48282
48519
  }
48283
48520
 
48284
48521
  // src/commands/release-test.ts
48285
- var import_picocolors19 = __toESM(require_picocolors(), 1);
48522
+ var import_picocolors20 = __toESM(require_picocolors(), 1);
48286
48523
  import { createHash as createHash3 } from "node:crypto";
48287
48524
  import { mkdir as mkdir9, mkdtemp as mkdtemp3, writeFile as writeFile12 } from "node:fs/promises";
48288
48525
  import { tmpdir as tmpdir3 } from "node:os";
48289
- import { join as join17, resolve as resolve15 } from "node:path";
48526
+ import { join as join18, resolve as resolve15 } from "node:path";
48290
48527
  import { setTimeout as sleep9 } from "node:timers/promises";
48291
48528
  init_errors();
48292
48529
  var HOLD_SECONDS = 50;
@@ -48394,9 +48631,19 @@ async function show3(ctx, release2, test2, { followed, ...extra } = {}) {
48394
48631
  const play2 = test2.state === "passed" ? await playFirst(ctx, release2) : null;
48395
48632
  const ask = test2.state === "waiting" ? askThePerson(test2) : play2;
48396
48633
  const noteAsk = test2.note_ask?.replaceAll("suggest_note", "pch note suggest");
48397
- const shown = { ...test2, ...extra, next: command, ...noteAsk ? { note_ask: noteAsk } : {} };
48634
+ const feedbackAsk = test2.feedback_ask ? {
48635
+ ...test2.feedback_ask,
48636
+ ask: test2.feedback_ask.ask.replaceAll("send_feedback", "pch feedback send")
48637
+ } : void 0;
48638
+ const shown = {
48639
+ ...test2,
48640
+ ...extra,
48641
+ next: command,
48642
+ ...noteAsk ? { note_ask: noteAsk } : {},
48643
+ ...feedbackAsk ? { feedback_ask: feedbackAsk } : {}
48644
+ };
48398
48645
  out.result({ ...shown, ...ask ? { ask } : {} }, () => {
48399
- const bold = import_picocolors19.default.bold(name);
48646
+ const bold = import_picocolors20.default.bold(name);
48400
48647
  if (test2.state === "waiting") {
48401
48648
  out.line(
48402
48649
  !test2.needs_person ? `${extra.started ? "Started a test of" : "The test of"} ${bold}. It's waiting for Patch Desktop to take it on.` : extra.started ? `Started a test of ${bold}. It's waiting for you in Patch Desktop: press Start on it there.` : `${bold}: its test is waiting for you in Patch Desktop. Press Start on it there.`
@@ -48412,30 +48659,31 @@ async function show3(ctx, release2, test2, { followed, ...extra } = {}) {
48412
48659
  `${bold}: it has been played and recorded. Look at the footage, then send your review.`
48413
48660
  );
48414
48661
  } else if (test2.state === "passed") {
48415
- out.line(`${bold}: ${import_picocolors19.default.green("its test passed.")} It's still private.`);
48662
+ out.line(`${bold}: ${import_picocolors20.default.green("its test passed.")} It's still private.`);
48416
48663
  if (test2.review) out.line(plain(test2.review.summary));
48417
48664
  if (play2) out.line(play2);
48418
48665
  out.line(
48419
48666
  "Publish it once you're happy it's ready: that's your yes, and nothing does it for you."
48420
48667
  );
48421
48668
  } else if (test2.state === "failed") {
48422
- out.line(`${bold}: ${import_picocolors19.default.red("its test failed")}, so this version can't be published.`);
48669
+ out.line(`${bold}: ${import_picocolors20.default.red("its test failed")}, so this version can't be published.`);
48423
48670
  showWhatFailed(ctx, test2);
48424
48671
  out.line(`pch status ${name} says how to fix it.`);
48425
48672
  out.line("Then move the version on in patch.json and publish that.");
48426
48673
  } else if (test2.state === "couldnt_run") {
48427
- out.line(`${bold}: ${import_picocolors19.default.yellow("its test couldn't run.")}`);
48674
+ out.line(`${bold}: ${import_picocolors20.default.yellow("its test couldn't run.")}`);
48428
48675
  if (test2.outcome) out.line(plain(test2.outcome.reason));
48429
48676
  } else {
48430
48677
  out.line(`${bold}: its test was cancelled.`);
48431
48678
  }
48432
48679
  if (noteAsk) out.line(plain(noteAsk));
48680
+ if (feedbackAsk) out.line(plain(feedbackAsk.ask));
48433
48681
  if (followed && GOING.has(test2.state)) {
48434
48682
  out.line("It isn't over, and nothing's wrong: that's as long as --wait follows it here.");
48435
48683
  }
48436
48684
  if (command) out.line(`Next: ${command}`);
48437
48685
  if (OPEN.has(test2.state) && !extra.started) {
48438
- out.line(import_picocolors19.default.dim(`To start over: pch test cancel ${name}, then pch test start ${name}`));
48686
+ out.line(import_picocolors20.default.dim(`To start over: pch test cancel ${name}, then pch test start ${name}`));
48439
48687
  }
48440
48688
  });
48441
48689
  if (test2.state === "failed") return EXIT.blocked;
@@ -48467,12 +48715,12 @@ async function testFootage(ctx, ref, options) {
48467
48715
  );
48468
48716
  const sheets = data.length ? await saveSheets(
48469
48717
  data,
48470
- options.out ? resolve15(options.out) : await mkdtemp3(join17(tmpdir3(), "pch-footage-"))
48718
+ options.out ? resolve15(options.out) : await mkdtemp3(join18(tmpdir3(), "pch-footage-"))
48471
48719
  ) : [];
48472
48720
  const next = data.length ? reviewCommand(name) : nextStep(name, "testing", test2).command;
48473
48721
  out.result({ test: test2.id, state: test2.state, data: sheets, next }, () => {
48474
48722
  if (!sheets.length) {
48475
- out.line(`${import_picocolors19.default.bold(name)} has no footage yet: its test is ${test2.state.replace("_", " ")}.`);
48723
+ out.line(`${import_picocolors20.default.bold(name)} has no footage yet: its test is ${test2.state.replace("_", " ")}.`);
48476
48724
  for (const result of test2.results) {
48477
48725
  if (result.footage_error) out.line(plain(result.footage_error));
48478
48726
  }
@@ -48500,7 +48748,7 @@ async function saveSheets(sheets, folder) {
48500
48748
  `Run ${sheet.run}'s sheet ${sheet.index} didn't download intact. Try again.`
48501
48749
  );
48502
48750
  }
48503
- const path2 = join17(folder, `run-${sheet.run}-sheet-${sheet.index}.jpg`);
48751
+ const path2 = join18(folder, `run-${sheet.run}-sheet-${sheet.index}.jpg`);
48504
48752
  await writeFile12(path2, bytes);
48505
48753
  const { url: _url2, ...kept } = sheet;
48506
48754
  saved.push({ ...kept, path: path2 });
@@ -48547,7 +48795,7 @@ async function testCancel(ctx, ref) {
48547
48795
  }
48548
48796
  const next = `pch test start ${name}`;
48549
48797
  ctx.out.result({ ...answer.data, next }, () => {
48550
- ctx.out.line(`Cancelled ${import_picocolors19.default.bold(name)}'s test. Start another: ${next}`);
48798
+ ctx.out.line(`Cancelled ${import_picocolors20.default.bold(name)}'s test. Start another: ${next}`);
48551
48799
  });
48552
48800
  }
48553
48801
  function withNext(failure2, release2, test2) {
@@ -48577,7 +48825,7 @@ async function testAutostart(ctx, state) {
48577
48825
  }
48578
48826
 
48579
48827
  // src/commands/search.ts
48580
- var import_picocolors20 = __toESM(require_picocolors(), 1);
48828
+ var import_picocolors21 = __toESM(require_picocolors(), 1);
48581
48829
  init_guide_words();
48582
48830
  var filter = (values) => values?.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean).join(",") || void 0;
48583
48831
  var licences = (values) => values.flatMap((value) => value.split(",")).map((value) => licenceId(value.trim()) ?? value.trim());
@@ -48615,13 +48863,13 @@ async function search(ctx, q, options) {
48615
48863
  out.result({ ...answer, ...aboutBlocks }, () => {
48616
48864
  if (!data.length) out.line("Nothing found.");
48617
48865
  for (const result of plain(data)) {
48618
- out.line(`${import_picocolors20.default.bold(result.handle)} ${import_picocolors20.default.dim(facts(result))}`);
48866
+ out.line(`${import_picocolors21.default.bold(result.handle)} ${import_picocolors21.default.dim(facts(result))}`);
48619
48867
  if (result.summary) out.line(` ${result.summary}`);
48620
48868
  if (result.demo) out.line(` Live demo: ${ctx.target.origin}/${result.owner}/${result.slug}`);
48621
- out.line(import_picocolors20.default.dim(` ${result.clone_url}`));
48869
+ out.line(import_picocolors21.default.dim(` ${result.clone_url}`));
48622
48870
  }
48623
48871
  if (blocks) out.line(BLOCK_WORDS);
48624
- if (cursor) out.line(import_picocolors20.default.dim("There are more; raise --limit."));
48872
+ if (cursor) out.line(import_picocolors21.default.dim("There are more; raise --limit."));
48625
48873
  });
48626
48874
  }
48627
48875
  function facts(result) {
@@ -48646,14 +48894,210 @@ async function categories(ctx, options) {
48646
48894
  const width = Math.max(0, ...data.map((category) => category.id.length));
48647
48895
  for (const category of data) {
48648
48896
  ctx.out.line(
48649
- `${import_picocolors20.default.bold(category.id.padEnd(width))} ${category.name} ${import_picocolors20.default.dim(`\xB7 ${category.for.join(", ")} \xB7 ${category.projects === 1 ? "1 project" : `${category.projects} projects`}`)}`
48897
+ `${import_picocolors21.default.bold(category.id.padEnd(width))} ${category.name} ${import_picocolors21.default.dim(`\xB7 ${category.for.join(", ")} \xB7 ${category.projects === 1 ? "1 project" : `${category.projects} projects`}`)}`
48650
48898
  );
48651
48899
  }
48652
48900
  });
48653
48901
  }
48654
48902
 
48903
+ // src/commands/update.ts
48904
+ var import_picocolors22 = __toESM(require_picocolors(), 1);
48905
+ var import_semver3 = __toESM(require_semver2(), 1);
48906
+ import { spawnSync as spawnSync2 } from "node:child_process";
48907
+ import { createHash as createHash4 } from "node:crypto";
48908
+ import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
48909
+ import { chmod as chmod3, rename as rename2, rm as rm3, writeFile as writeFile13 } from "node:fs/promises";
48910
+ import { basename as basename8, dirname as dirname7, join as join19 } from "node:path";
48911
+ import { gunzipSync } from "node:zlib";
48912
+ init_errors();
48913
+ init_version();
48914
+ var DAY = 24 * 60 * 60 * 1e3;
48915
+ var QUIET = /* @__PURE__ */ new Set(["mcp", "git-credential", "update"]);
48916
+ async function latestVersion2(releases, signal) {
48917
+ const response = await fetch(`${releases}/latest.json`, { signal });
48918
+ if (!response.ok) throw new Error(`${releases}/latest.json answered ${response.status}`);
48919
+ const { version: version2 } = await response.json();
48920
+ if (typeof version2 !== "string" || !import_semver3.default.valid(version2)) {
48921
+ throw new Error(`${releases}/latest.json names no version`);
48922
+ }
48923
+ return version2;
48924
+ }
48925
+ function parseSums(text) {
48926
+ const sums = /* @__PURE__ */ new Map();
48927
+ for (const line of text.split(/\r?\n/)) {
48928
+ const match = /^([0-9a-f]{64}) [ *](.+)$/i.exec(line.trim());
48929
+ if (match) sums.set(match[2], match[1].toLowerCase());
48930
+ }
48931
+ return sums;
48932
+ }
48933
+ function notReplaceable(kind) {
48934
+ const command = updateCommand(kind);
48935
+ if (kind === "desktop")
48936
+ return { command, message: "This pch came with Patch Desktop, and updates with it." };
48937
+ if (command)
48938
+ return {
48939
+ command,
48940
+ message: `This pch was installed with ${kind}. To update it, run: ${command}`
48941
+ };
48942
+ return {
48943
+ command,
48944
+ message: `This pch runs from ${process.argv[1]}, which pch update doesn't manage. Update it the way you installed it.`
48945
+ };
48946
+ }
48947
+ async function download2(url2) {
48948
+ const response = await fetch(url2, { signal: AbortSignal.timeout(10 * 6e4) }).catch(
48949
+ (error62) => {
48950
+ throw new CliError(
48951
+ EXIT.error,
48952
+ "download_failed",
48953
+ `Couldn't download ${url2}: ${error62.message}`
48954
+ );
48955
+ }
48956
+ );
48957
+ if (!response.ok) {
48958
+ throw new CliError(EXIT.error, "download_failed", `${url2} answered ${response.status}.`);
48959
+ }
48960
+ return Buffer.from(await response.arrayBuffer());
48961
+ }
48962
+ async function update(ctx) {
48963
+ const kind = installKind();
48964
+ if (kind !== "executable") {
48965
+ const { command, message } = notReplaceable(kind);
48966
+ ctx.out.result(
48967
+ { version: VERSION, install: kind, updated: false, command },
48968
+ () => ctx.out.line(message)
48969
+ );
48970
+ return EXIT.ok;
48971
+ }
48972
+ const releases = releasesUrl(ctx.target.env);
48973
+ const name = executableName();
48974
+ if (!releases || !name) {
48975
+ throw new CliError(
48976
+ EXIT.usage,
48977
+ "no_releases",
48978
+ releases ? `There's no pch executable for ${process.platform} ${process.arch}.` : `${ctx.target.env} has no releases of pch to update from.`
48979
+ );
48980
+ }
48981
+ ctx.out.step("Looking for a newer pch");
48982
+ const latest2 = await latestVersion2(releases, AbortSignal.timeout(15e3)).catch(
48983
+ (error62) => {
48984
+ throw new CliError(
48985
+ EXIT.error,
48986
+ "download_failed",
48987
+ `Couldn't check for a newer pch: ${error62.message}`
48988
+ );
48989
+ }
48990
+ );
48991
+ if (!import_semver3.default.gt(latest2, VERSION)) {
48992
+ ctx.out.result(
48993
+ { version: VERSION, latest: latest2, install: kind, updated: false },
48994
+ () => ctx.out.line(`pch ${VERSION} is the newest.`)
48995
+ );
48996
+ return EXIT.ok;
48997
+ }
48998
+ const file2 = `${name}.gz`;
48999
+ ctx.out.step(`Downloading pch ${latest2}`);
49000
+ const [sums, packed] = await Promise.all([
49001
+ download2(`${releases}/${latest2}/SHA256SUMS`),
49002
+ download2(`${releases}/${latest2}/${file2}`)
49003
+ ]);
49004
+ const expected = parseSums(sums.toString("utf8")).get(file2);
49005
+ const actual = createHash4("sha256").update(packed).digest("hex");
49006
+ if (!expected || expected !== actual) {
49007
+ throw new CliError(
49008
+ EXIT.error,
49009
+ "checksum_mismatch",
49010
+ `pch ${latest2} for this machine doesn't match its SHA-256, so it wasn't installed.`,
49011
+ { file: file2, expected: expected ?? null, actual }
49012
+ );
49013
+ }
49014
+ const target = process.execPath;
49015
+ const fresh = `${target}.new`;
49016
+ await writeFile13(fresh, gunzipSync(packed), { mode: 493 });
49017
+ await chmod3(fresh, 493);
49018
+ const said2 = spawnSync2(fresh, ["--version"], { encoding: "utf8", timeout: 6e4 });
49019
+ if (said2.status !== 0 || said2.stdout.trim() !== latest2) {
49020
+ await rm3(fresh, { force: true });
49021
+ throw new CliError(
49022
+ EXIT.error,
49023
+ "update_failed",
49024
+ `pch ${latest2} didn't start on this machine, so pch ${VERSION} stays.`,
49025
+ { stderr: (said2.stderr ?? said2.error?.message ?? "").trim().slice(-2e3) }
49026
+ );
49027
+ }
49028
+ await replace(target, fresh);
49029
+ ctx.out.result(
49030
+ { version: latest2, previous: VERSION, install: kind, updated: true },
49031
+ () => ctx.out.line(`Updated pch from ${VERSION} to ${latest2}.`)
49032
+ );
49033
+ return EXIT.ok;
49034
+ }
49035
+ async function replace(target, fresh) {
49036
+ if (process.platform !== "win32") return rename2(fresh, target);
49037
+ const old = `${target}.${process.pid}.old`;
49038
+ await rename2(target, old);
49039
+ try {
49040
+ await rename2(fresh, target);
49041
+ } catch (error62) {
49042
+ await rename2(old, target);
49043
+ throw error62;
49044
+ }
49045
+ }
49046
+ function removeOldExecutables(target = process.execPath) {
49047
+ if (!isExecutable || process.platform !== "win32") return;
49048
+ const folder = dirname7(target);
49049
+ const old = new RegExp(`^${basename8(target).replace(/\./g, "\\.")}\\.\\d+\\.old$`, "i");
49050
+ try {
49051
+ for (const name of readdirSync(folder)) {
49052
+ if (old.test(name)) rmSync(join19(folder, name), { force: true });
49053
+ }
49054
+ } catch {
49055
+ }
49056
+ }
49057
+ function startUpdateNotice(ctx, command, now = Date.now()) {
49058
+ const how = updateCommand(installKind());
49059
+ const releases = releasesUrl(ctx.target.env);
49060
+ if (!how || !releases || ctx.out.json || QUIET.has(command)) return null;
49061
+ if (!process.stderr.isTTY || process.env.CI) return null;
49062
+ const stamp = join19(dirname7(configPath()), "update-check.json");
49063
+ try {
49064
+ const { checked_at } = JSON.parse(readFileSync(stamp, "utf8"));
49065
+ if (checked_at && now - Date.parse(checked_at) < DAY) return null;
49066
+ } catch {
49067
+ }
49068
+ try {
49069
+ mkdirSync(dirname7(stamp), { recursive: true, mode: 448 });
49070
+ writeFileSync(stamp, `${JSON.stringify({ checked_at: new Date(now).toISOString() })}
49071
+ `);
49072
+ } catch {
49073
+ return null;
49074
+ }
49075
+ const stop = new AbortController();
49076
+ const giveUp = setTimeout(() => stop.abort(), 2e3);
49077
+ giveUp.unref();
49078
+ const latest2 = latestVersion2(releases, stop.signal).catch(() => null).finally(() => clearTimeout(giveUp));
49079
+ return {
49080
+ async show() {
49081
+ let timer;
49082
+ const late = new Promise((done) => {
49083
+ timer = setTimeout(() => done(null), 500);
49084
+ });
49085
+ const version2 = await Promise.race([latest2, late]);
49086
+ clearTimeout(timer);
49087
+ stop.abort();
49088
+ if (version2 && import_semver3.default.gt(version2, VERSION)) {
49089
+ process.stderr.write(
49090
+ `
49091
+ ${import_picocolors22.default.dim("\u203A")} pch ${version2} is out (you have ${VERSION}). To update, run: ${how}
49092
+ `
49093
+ );
49094
+ }
49095
+ }
49096
+ };
49097
+ }
49098
+
48655
49099
  // src/commands/validate.ts
48656
- var import_picocolors21 = __toESM(require_picocolors(), 1);
49100
+ var import_picocolors23 = __toESM(require_picocolors(), 1);
48657
49101
  init_errors();
48658
49102
  async function validate2(ctx, dir) {
48659
49103
  const project2 = await loadProject(dir);
@@ -48712,12 +49156,12 @@ async function validate2(ctx, dir) {
48712
49156
  notes
48713
49157
  };
48714
49158
  ctx.out.result(result, () => {
48715
- const mark = result.ok ? import_picocolors21.default.green("\u2713") : import_picocolors21.default.red("\u2717");
49159
+ const mark = result.ok ? import_picocolors23.default.green("\u2713") : import_picocolors23.default.red("\u2717");
48716
49160
  ctx.out.line(
48717
49161
  `${mark} ${file2.project}@${file2.version} \xB7 ${project2.kind} \xB7 ${result.trust_tier} \xB7 ${project2.files.length} files, ${formatSize(total)}`
48718
49162
  );
48719
- for (const issue2 of issues) ctx.out.line(` ${import_picocolors21.default.red(issue2.path || "\xB7")} ${issue2.message}`);
48720
- for (const note2 of notes) ctx.out.line(` ${import_picocolors21.default.dim(note2)}`);
49163
+ for (const issue2 of issues) ctx.out.line(` ${import_picocolors23.default.red(issue2.path || "\xB7")} ${issue2.message}`);
49164
+ for (const note2 of notes) ctx.out.line(` ${import_picocolors23.default.dim(note2)}`);
48721
49165
  });
48722
49166
  return result.ok ? EXIT.ok : EXIT.usage;
48723
49167
  }
@@ -48757,32 +49201,52 @@ Start here:
48757
49201
  pch media add, pch project update the cover and the listing
48758
49202
 
48759
49203
  Add --json to any command for results an agent can read. Exit codes: 0 fine, 1 error, 2 bad usage,
48760
- 3 blocked, 4 sign-in or quota, 5 conflict, 6 needs a person.`
49204
+ 3 blocked, 4 sign-in or quota, 5 conflict, 6 needs a person.
49205
+
49206
+ Whenever Patch gets in your way, tell its team: pch feedback send --kind <failed|bug|call_error|unhelpful|idea>
49207
+ "<what got in your way>", with --tool, --error-code, --expected, --actual and --would-help.
49208
+ pch guide has an example. Then carry on.`
48761
49209
  ).showHelpAfterError(!wantsJson).configureOutput({
48762
49210
  outputError: (text, write) => write(
48763
49211
  wantsJson ? `${JSON.stringify({ error: { code: "bad_usage", message: text.replace(/^error: /, "").trim() } })}
48764
49212
  ` : text
48765
49213
  )
48766
49214
  }).exitOverride();
49215
+ function commandPath(command) {
49216
+ const names = [];
49217
+ for (let at = command; at; at = at.parent) names.unshift(at.name());
49218
+ return names.join(" ");
49219
+ }
48767
49220
  function action(run3) {
48768
49221
  return async (...args2) => {
48769
49222
  const command = args2.at(-1);
48770
49223
  const globals = command.optsWithGlobals();
49224
+ let notice = null;
48771
49225
  try {
48772
49226
  const ctx = await createContext(globals);
49227
+ notice = startUpdateNotice(ctx, topLevel(command).name());
48773
49228
  const code = await run3(ctx, ...args2.slice(0, -1));
48774
49229
  process.exitCode = typeof code === "number" ? code : EXIT.ok;
48775
49230
  } catch (error62) {
48776
- process.exitCode = new Output(Boolean(globals.json)).error(error62);
49231
+ process.exitCode = new Output(Boolean(globals.json)).error(error62, commandPath(command));
48777
49232
  }
49233
+ await notice?.show();
48778
49234
  };
48779
49235
  }
49236
+ function topLevel(command) {
49237
+ let top = command;
49238
+ while (top.parent?.parent) top = top.parent;
49239
+ return top;
49240
+ }
48780
49241
  var collect = (value, previous = []) => [...previous, value];
48781
49242
  program2.command("login").description(
48782
49243
  "Sign in through the browser (device code), and sign git in with it. It waits for a person, which can take a few minutes, so don't run it under a short timeout"
48783
49244
  ).option("--scope <scope>", "API key scope: read, publish, admin (repeatable)", collect).option("--no-browser", "print the link instead of opening a browser").action(action((ctx, options) => login(ctx, options)));
48784
49245
  program2.command("logout").description("Revoke and forget the active login, and sign git out").action(action((ctx) => logout(ctx)));
48785
49246
  program2.command("whoami").description("Who you're signed in as, and where").action(action((ctx) => whoami(ctx)));
49247
+ program2.command("update").description(
49248
+ "Update pch to the newest version, when the installer put it here; installed another way, it says how to update"
49249
+ ).action(action((ctx) => update(ctx)));
48786
49250
  program2.command("guide").description("How to make, test and publish on Patch, for you or your agent").action(action(async (ctx) => (await Promise.resolve().then(() => (init_mcp(), mcp_exports))).guide(ctx)));
48787
49251
  program2.command("mcp").description(
48788
49252
  "Patch's MCP server for your agent, signed in as pch is: add `pch mcp` to its servers"
@@ -49215,10 +49679,25 @@ note.command("suggest <about> <fact>").description(
49215
49679
  (ctx, about, fact, options) => noteSuggest(ctx, about, fact, options)
49216
49680
  )
49217
49681
  );
49682
+ var feedback = program2.command("feedback").description("Tell Patch's team what got in your way, and see their answers");
49683
+ feedback.command("send <summary>").description(
49684
+ "Tell Patch's team what got in your way, in one specific line, then carry on: you couldn't do what was asked (failed), Patch did something wrong (bug), a call failed and its error didn't say what to do (call_error), a command or its answer didn't help (unhelpful), or something would have made it easier (idea). Write it for the engineer who'll fix it. A person at Patch reads every one, and anything that looks like a key is taken out. It may ask back the one thing that would help most: answer with --add-to"
49685
+ ).addOption(
49686
+ new Option("--kind <kind>", "failed, bug, call_error, unhelpful or idea").choices(FEEDBACK_KINDS).makeOptionMandatory()
49687
+ ).option("--goal <text>", "what you were trying to do for the person").option("--expected <text>", "what you expected Patch to do or say").option("--actual <text>", "what it did or said instead, in its own words").option("--tried <text>", "what you tried, in order").option("--workaround <text>", "what worked around it in the end, if anything did").option(
49688
+ "--would-help <text>",
49689
+ "exactly what would have made it easy: the words, the command, the flag"
49690
+ ).option("--cost <text>", "what it cost: extra calls, retries, minutes, or a step given up").addOption(
49691
+ new Option("--severity <severity>", "blocked, slowed or annoyed").choices(FEEDBACK_SEVERITIES)
49692
+ ).option("--details <text>", "anything else").option("--add-to <id>", "a report of yours to add this to, as when answering its follow-up").option("--tool <command>", "the command or MCP tool it's about, e.g. pch publish").option("--error-code <code>", "the error's code, for one that named no failed call").option("--project <owner/slug>", "the project it's about").option("--agent <name>", "which coding agent and model you are").action(
49693
+ action((ctx, summary, options) => feedbackSend(ctx, summary, options))
49694
+ );
49695
+ feedback.command("list").description("Your reports, newest first, with what Patch's team answered").action(action((ctx) => feedbackList(ctx)));
49218
49696
  var words = process.argv.slice(2);
49219
49697
  if (!program2.commands.some((one) => [one.name(), ...one.aliases()].some((n) => words.includes(n)))) {
49220
49698
  program2.version(VERSION);
49221
49699
  }
49700
+ removeOldExecutables();
49222
49701
  try {
49223
49702
  await program2.parseAsync(process.argv);
49224
49703
  } catch (error62) {