@patchgg/cli 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +715 -442
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -130,11 +130,17 @@ function exitCodeFor(status2) {
130
130
  return EXIT.error;
131
131
  }
132
132
  function notSignedIn() {
133
- return new CliError(
133
+ const failure2 = new CliError(
134
134
  EXIT.auth,
135
135
  "not_signed_in",
136
136
  "You're not signed in. Run pch login, or set PCH_TOKEN to an API key."
137
137
  );
138
+ failure2.forPeople = {
139
+ title: "You're not signed in",
140
+ next: [["pch login", "sign in, in your browser"]],
141
+ also: "For an agent or CI, set PCH_TOKEN to an API key instead."
142
+ };
143
+ return failure2;
138
144
  }
139
145
  var EXIT, CliError;
140
146
  var init_errors = __esm({
@@ -167,6 +173,11 @@ var init_errors = __esm({
167
173
  details;
168
174
  /** Patch's API said it, rather than pch itself. */
169
175
  fromApi;
176
+ /**
177
+ * What a person at a terminal sees instead of the message, when that's shorter: a line, and a
178
+ * command or two that fix it. Agents and --json always get the whole message.
179
+ */
180
+ forPeople;
170
181
  };
171
182
  }
172
183
  });
@@ -176,17 +187,17 @@ var VERSION;
176
187
  var init_version = __esm({
177
188
  "src/version.ts"() {
178
189
  "use strict";
179
- VERSION = true ? "0.1.2" : JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
190
+ VERSION = true ? "0.2.0" : JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
180
191
  }
181
192
  });
182
193
 
183
194
  // src/guide-words.ts
184
- function openCodeWords(name, repo, release2) {
195
+ function openCodeWords(name, repo, release3) {
185
196
  if (repo?.visibility !== "open") return null;
186
- if (release2.status === "yanked" || release2.status === "quarantined") return null;
187
- const seen = release2.channel === "private" ? "private" : release2.status === "published" ? release2.channel : "private until it's published";
197
+ if (release3.status === "yanked" || release3.status === "quarantined") return null;
198
+ const seen = release3.channel === "private" ? "private" : release3.status === "published" ? release3.channel : "private until it's published";
188
199
  if (seen === "public") return null;
189
- const what = release2.kind === "mod" ? "mod" : release2.kind === "block" ? "block" : "game";
200
+ const what = release3.kind === "mod" ? "mod" : release3.kind === "block" ? "block" : "game";
190
201
  const start = `The ${what} is ${seen}, but its code isn't:`;
191
202
  const pass = "Say so when you tell anyone who can see it.";
192
203
  if (repo.home === "github") {
@@ -654,8 +665,8 @@ var require_semver = __commonJS({
654
665
  }
655
666
  // preminor will bump the version up to the next minor release, and immediately
656
667
  // down to pre-release. premajor and prepatch work the same way.
657
- inc(release2, identifier, identifierBase) {
658
- if (release2.startsWith("pre")) {
668
+ inc(release3, identifier, identifierBase) {
669
+ if (release3.startsWith("pre")) {
659
670
  if (!identifier && identifierBase === false) {
660
671
  throw new Error("invalid increment argument: identifier is empty");
661
672
  }
@@ -666,7 +677,7 @@ var require_semver = __commonJS({
666
677
  }
667
678
  }
668
679
  }
669
- switch (release2) {
680
+ switch (release3) {
670
681
  case "premajor":
671
682
  this.prerelease.length = 0;
672
683
  this.patch = 0;
@@ -758,7 +769,7 @@ var require_semver = __commonJS({
758
769
  break;
759
770
  }
760
771
  default:
761
- throw new Error(`invalid increment argument: ${release2}`);
772
+ throw new Error(`invalid increment argument: ${release3}`);
762
773
  }
763
774
  this.raw = this.format();
764
775
  if (this.build.length) {
@@ -824,7 +835,7 @@ var require_inc = __commonJS({
824
835
  "node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/inc.js"(exports, module) {
825
836
  "use strict";
826
837
  var SemVer = require_semver();
827
- var inc = (version2, release2, options, identifier, identifierBase) => {
838
+ var inc = (version2, release3, options, identifier, identifierBase) => {
828
839
  if (typeof options === "string") {
829
840
  identifierBase = identifier;
830
841
  identifier = options;
@@ -834,7 +845,7 @@ var require_inc = __commonJS({
834
845
  return new SemVer(
835
846
  version2 instanceof SemVer ? version2.version : version2,
836
847
  options
837
- ).inc(release2, identifier, identifierBase).version;
848
+ ).inc(release3, identifier, identifierBase).version;
838
849
  } catch (er) {
839
850
  return null;
840
851
  }
@@ -35755,7 +35766,9 @@ var init_mcp = __esm({
35755
35766
  });
35756
35767
 
35757
35768
  // src/plain-when-piped.ts
35758
- if (!process.stdout.isTTY && !process.env.FORCE_COLOR) process.env.NO_COLOR = "1";
35769
+ if ((!process.stdout.isTTY || process.env.TERM === "dumb") && !process.env.FORCE_COLOR) {
35770
+ process.env.NO_COLOR = "1";
35771
+ }
35759
35772
 
35760
35773
  // node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/error.js
35761
35774
  var CommanderError = class extends Error {
@@ -36330,13 +36343,13 @@ var Help = class {
36330
36343
  (sub) => sub.helpGroup() || "Commands:"
36331
36344
  );
36332
36345
  commandGroups.forEach((commands, group) => {
36333
- const commandList = commands.map((sub) => {
36346
+ const commandList2 = commands.map((sub) => {
36334
36347
  return callFormatItem(
36335
36348
  helper.styleSubcommandTerm(helper.subcommandTerm(sub)),
36336
36349
  helper.styleSubcommandDescription(helper.subcommandDescription(sub))
36337
36350
  );
36338
36351
  });
36339
- output2 = output2.concat(this.formatItemList(group, commandList, helper));
36352
+ output2 = output2.concat(this.formatItemList(group, commandList2, helper));
36340
36353
  });
36341
36354
  return output2.join("\n");
36342
36355
  }
@@ -36490,10 +36503,10 @@ ${itemIndentStr}`);
36490
36503
  let sumChunks = [chunks.shift()];
36491
36504
  let sumWidth = this.displayWidth(sumChunks[0]);
36492
36505
  chunks.forEach((chunk) => {
36493
- const visibleWidth = this.displayWidth(chunk);
36494
- if (sumWidth + visibleWidth <= width) {
36506
+ const visibleWidth2 = this.displayWidth(chunk);
36507
+ if (sumWidth + visibleWidth2 <= width) {
36495
36508
  sumChunks.push(chunk);
36496
- sumWidth += visibleWidth;
36509
+ sumWidth += visibleWidth2;
36497
36510
  return;
36498
36511
  }
36499
36512
  wrappedLines.push(sumChunks.join(""));
@@ -39126,7 +39139,7 @@ function useColor() {
39126
39139
  var program = new Command();
39127
39140
 
39128
39141
  // src/commands/account.ts
39129
- var import_picocolors3 = __toESM(require_picocolors(), 1);
39142
+ var import_picocolors4 = __toESM(require_picocolors(), 1);
39130
39143
 
39131
39144
  // node_modules/.pnpm/openapi-fetch@0.17.0/node_modules/openapi-fetch/dist/index.mjs
39132
39145
  var PATH_PARAM_RE = /\{[^{}]+\}/g;
@@ -39970,20 +39983,188 @@ var LFS_FLAGS = [
39970
39983
  ];
39971
39984
  var NO_LFS = "Git LFS isn't installed, so big files stay small pointer files. Install it from https://git-lfs.com, then run git lfs install.";
39972
39985
 
39986
+ // src/commands/account.ts
39987
+ init_guide_words();
39988
+
39989
+ // src/terminal.ts
39990
+ var import_picocolors = __toESM(require_picocolors(), 1);
39991
+ import { spawnSync } from "node:child_process";
39992
+ import { release } from "node:os";
39993
+ function canRedraw() {
39994
+ return Boolean(process.stderr.isTTY) && process.env.TERM !== "dumb";
39995
+ }
39996
+ function unicode(vars = process.env, platform = process.platform) {
39997
+ if (platform !== "win32") return vars.TERM !== "linux";
39998
+ return Boolean(
39999
+ vars.WT_SESSION || vars.TERMINUS_SUBLIME || vars.ConEmuTask === "{cmd::Cmder}" || vars.TERM_PROGRAM === "vscode" || vars.TERM_PROGRAM === "mintty" || vars.TERM === "xterm-256color" || vars.TERM === "alacritty" || vars.TERMINAL_EMULATOR === "JetBrains-JediTerm"
40000
+ );
40001
+ }
40002
+ function colourDepth(vars = process.env, platform = process.platform) {
40003
+ if (!import_picocolors.default.isColorSupported || vars.TERM === "dumb") return 0;
40004
+ if (vars.COLORTERM === "truecolor" || vars.COLORTERM === "24bit") return 3;
40005
+ if (vars.WT_SESSION || vars.TERM_PROGRAM === "mintty" || vars.TERM_PROGRAM === "vscode") return 3;
40006
+ if (platform === "win32" && Number(release().split(".")[2]) >= 14931) return 3;
40007
+ if (vars.TERM?.includes("256")) return 2;
40008
+ return 1;
40009
+ }
40010
+ function pink(text, depth = colourDepth()) {
40011
+ if (depth === 3) return `\x1B[38;2;245;60;209m${text}\x1B[39m`;
40012
+ if (depth === 2) return `\x1B[38;5;206m${text}\x1B[39m`;
40013
+ return depth === 1 ? import_picocolors.default.magenta(text) : text;
40014
+ }
40015
+ function copyText(text, platform = process.platform) {
40016
+ const tools = platform === "win32" ? [["clip", []]] : platform === "darwin" ? [["pbcopy", []]] : [
40017
+ ["wl-copy", []],
40018
+ ["xclip", ["-selection", "clipboard"]],
40019
+ ["xsel", ["--clipboard", "--input"]]
40020
+ ];
40021
+ for (const [command, args2] of tools) {
40022
+ try {
40023
+ const done = spawnSync(command, args2, {
40024
+ input: text,
40025
+ stdio: ["pipe", "ignore", "ignore"],
40026
+ timeout: 2e3
40027
+ });
40028
+ if (done.status === 0) return true;
40029
+ } catch {
40030
+ }
40031
+ }
40032
+ return false;
40033
+ }
40034
+ var symbols = () => unicode() ? { done: "\u2713", failed: "\xD7", note: "!" } : { done: "\u221A", failed: "\xD7", note: "!" };
40035
+ var FRAMES = {
40036
+ unicode: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
40037
+ plain: ["|", "/", "-", "\\"]
40038
+ };
40039
+ function spinner(text, hint = "") {
40040
+ if (!canRedraw()) {
40041
+ process.stderr.write(` ${text}
40042
+ `);
40043
+ return { stop() {
40044
+ }, say(_) {
40045
+ } };
40046
+ }
40047
+ const frames = unicode() ? FRAMES.unicode : FRAMES.plain;
40048
+ let frame = 0;
40049
+ const draw = () => {
40050
+ const room = (process.stderr.columns || 80) - 5;
40051
+ const said2 = text.length > room ? `${text.slice(0, Math.max(0, room - 1))}\u2026` : text;
40052
+ const more = hint && said2.length + 2 + hint.length <= room ? ` ${import_picocolors.default.dim(hint)}` : "";
40053
+ const spin = frames[frame++ % frames.length];
40054
+ process.stderr.write(`\r\x1B[K ${spin} ${said2}${more}`);
40055
+ };
40056
+ const clear = () => process.stderr.write("\r\x1B[K\x1B[?25h");
40057
+ const cancel = () => {
40058
+ clear();
40059
+ process.stderr.write(` ${import_picocolors.default.dim("Cancelled.")}
40060
+ `);
40061
+ process.exit(130);
40062
+ };
40063
+ process.stderr.write("\x1B[?25l");
40064
+ draw();
40065
+ const timer = setInterval(draw, 80);
40066
+ process.once("SIGINT", cancel);
40067
+ let stopped = false;
40068
+ return {
40069
+ stop() {
40070
+ if (stopped) return;
40071
+ stopped = true;
40072
+ clearInterval(timer);
40073
+ process.off("SIGINT", cancel);
40074
+ clear();
40075
+ },
40076
+ /** Says something else from now on, such as the next thing it's waiting for. */
40077
+ say(next) {
40078
+ text = next;
40079
+ hint = "";
40080
+ draw();
40081
+ }
40082
+ };
40083
+ }
40084
+ var Block = class {
40085
+ lines = [];
40086
+ write(line = "") {
40087
+ this.lines.push(line);
40088
+ process.stderr.write(`${line}
40089
+ `);
40090
+ }
40091
+ /** Takes the lines back off the screen, counting the rows the window wrapped them onto. */
40092
+ erase() {
40093
+ const columns = process.stderr.columns;
40094
+ if (!canRedraw() || !columns || !this.lines.length) return;
40095
+ const rows = this.lines.reduce(
40096
+ (sum, line) => sum + Math.max(1, Math.ceil(visibleWidth(line) / columns)),
40097
+ 0
40098
+ );
40099
+ process.stderr.write(`\x1B[${rows}A\r\x1B[J`);
40100
+ this.lines = [];
40101
+ }
40102
+ };
40103
+ function commandList(rows, indent = " ", columns = process.stdout.columns ?? 80) {
40104
+ const wide = Math.max(...rows.map(([command]) => command.length));
40105
+ const fits = indent.length + wide + 3 + Math.max(...rows.map(([, words2]) => words2.length));
40106
+ return rows.map(
40107
+ ([command, words2]) => fits <= columns ? `${indent}${import_picocolors.default.bold(command.padEnd(wide))} ${import_picocolors.default.dim(words2)}` : `${indent}${import_picocolors.default.bold(command)}
40108
+ ${indent} ${import_picocolors.default.dim(words2)}`
40109
+ );
40110
+ }
40111
+ function placeOf(origin) {
40112
+ try {
40113
+ return new URL(origin).host;
40114
+ } catch {
40115
+ return origin;
40116
+ }
40117
+ }
40118
+ var ESCAPES = /\x1b\[[0-9;?]*[A-Za-z]/g;
40119
+ function unstyled(text) {
40120
+ return text.replace(ESCAPES, "");
40121
+ }
40122
+ function visibleWidth(text) {
40123
+ return [...unstyled(text)].length;
40124
+ }
40125
+
39973
40126
  // src/commands/login.ts
39974
- var import_picocolors2 = __toESM(require_picocolors(), 1);
40127
+ var import_picocolors3 = __toESM(require_picocolors(), 1);
39975
40128
  import { spawn as spawn2 } from "node:child_process";
39976
40129
  import { hostname } from "node:os";
39977
40130
  import { setTimeout as sleep } from "node:timers/promises";
39978
40131
  init_errors();
39979
40132
  init_guide_words();
39980
40133
 
40134
+ // src/logo.ts
40135
+ var ROWS = [
40136
+ ".....................#.........#....",
40137
+ "####.................#.........#....",
40138
+ "#...#...####..####..####.#####.####.",
40139
+ "#.+.#...#...#.....#..#...#.....#...#",
40140
+ "#...#...#...#.#####..#...#.....#...#",
40141
+ "#####...#...#.#...#..#...#.....#...#",
40142
+ "........#####.#####..###.#####.#...#",
40143
+ "........#...........................",
40144
+ "........#..........................."
40145
+ ];
40146
+ var WIDTH = ROWS[0]?.length ?? 0;
40147
+ function logo(columns = process.stdout.columns ?? 80, depth = colourDepth()) {
40148
+ if (!canRedraw() || !unicode() || columns < WIDTH + 4) return null;
40149
+ const lines = [];
40150
+ for (let y = 0; y < ROWS.length; y += 2) {
40151
+ const top = ROWS[y] ?? "";
40152
+ const bottom = ROWS[y + 1] ?? "";
40153
+ let line = "";
40154
+ for (let x = 0; x < WIDTH; x++) {
40155
+ const up = top[x] ?? ".";
40156
+ const down = bottom[x] ?? ".";
40157
+ const glyph = up !== "." ? down !== "." ? "\u2588" : "\u2580" : down !== "." ? "\u2584" : " ";
40158
+ line += up === "+" || down === "+" ? pink(glyph, depth) : glyph;
40159
+ }
40160
+ lines.push(line.trimEnd());
40161
+ }
40162
+ return lines;
40163
+ }
40164
+
39981
40165
  // src/output.ts
39982
- var import_picocolors = __toESM(require_picocolors(), 1);
40166
+ var import_picocolors2 = __toESM(require_picocolors(), 1);
39983
40167
  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
40168
  function forAPerson(json2, agent2) {
39988
40169
  return !json2 && Boolean(process.stdout.isTTY && process.stderr.isTTY) && !agent2;
39989
40170
  }
@@ -40004,24 +40185,9 @@ var Output = class {
40004
40185
  }
40005
40186
  /** A progress line, e.g. "Uploading 2 files". */
40006
40187
  step(text) {
40007
- if (!this.json) process.stderr.write(`${import_picocolors.default.dim("\u203A")} ${text}
40188
+ if (!this.json) process.stderr.write(`${import_picocolors2.default.dim("\u203A")} ${text}
40008
40189
  `);
40009
40190
  }
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
- }
40025
40191
  /**
40026
40192
  * How far a long job has got. With --json it's a line of JSON on stderr, so whoever's waiting can
40027
40193
  * see it's moving while stdout stays the one result.
@@ -40045,16 +40211,40 @@ var Output = class {
40045
40211
  };
40046
40212
  process.stderr.write(`${JSON.stringify(body)}
40047
40213
  `);
40214
+ } else if (process.stderr.isTTY) {
40215
+ const failed3 = import_picocolors2.default.red(symbols().failed);
40216
+ const short = failure2.forPeople;
40217
+ if (short) process.stderr.write(`
40218
+ ${failed3} ${short.title}
40219
+
40220
+ `);
40221
+ else
40222
+ process.stderr.write(`
40223
+ ${failed3} ${failure2.message} ${import_picocolors2.default.dim(`(${failure2.code})`)}
40224
+ `);
40225
+ for (const issue2 of issuesOf(failure2.details)) {
40226
+ process.stderr.write(` ${import_picocolors2.default.dim(issue2.path || "\xB7")} ${issue2.message}
40227
+ `);
40228
+ }
40229
+ if (short) process.stderr.write(`${commandList(short.next, " ").join("\n")}
40230
+ `);
40231
+ if (short?.also) process.stderr.write(`
40232
+ ${import_picocolors2.default.dim(short.also)}
40233
+ `);
40234
+ if (feedback2) process.stderr.write(`
40235
+ ${import_picocolors2.default.dim(feedback2.say)}
40236
+ `);
40237
+ process.stderr.write("\n");
40048
40238
  } else {
40049
40239
  process.stderr.write(
40050
- `${import_picocolors.default.red("Error:")} ${failure2.message} ${import_picocolors.default.dim(`(${failure2.code})`)}
40240
+ `${import_picocolors2.default.red("Error:")} ${failure2.message} ${import_picocolors2.default.dim(`(${failure2.code})`)}
40051
40241
  `
40052
40242
  );
40053
40243
  for (const issue2 of issuesOf(failure2.details)) {
40054
- process.stderr.write(` ${import_picocolors.default.dim(issue2.path || "\xB7")} ${issue2.message}
40244
+ process.stderr.write(` ${import_picocolors2.default.dim(issue2.path || "\xB7")} ${issue2.message}
40055
40245
  `);
40056
40246
  }
40057
- if (feedback2) process.stderr.write(`${import_picocolors.default.dim(feedback2.say)}
40247
+ if (feedback2) process.stderr.write(`${import_picocolors2.default.dim(feedback2.say)}
40058
40248
  `);
40059
40249
  }
40060
40250
  return failure2.exitCode;
@@ -40105,29 +40295,33 @@ async function login(ctx, options) {
40105
40295
  const { out, target } = ctx;
40106
40296
  if (process.env.PCH_TOKEN) return loginWithKey(ctx);
40107
40297
  const scopes = options.scope?.length ? options.scope : ["read", "publish"];
40108
- const { me, key } = await withDeviceSession(ctx, options, async (session) => ({
40109
- me: await unwrap(session.GET("/me")),
40110
- key: await unwrap(
40298
+ const name = ctx.target.accountName ?? (target.env === "production" ? "default" : target.env);
40299
+ const isDefault = name === (target.env === "production" ? "default" : target.env);
40300
+ const people = forAPerson(out.json, runningIn());
40301
+ const notes = [];
40302
+ const signIn = { ...options, logo: true };
40303
+ const { me, key, git, gitIdentity } = await withDeviceSession(ctx, signIn, async (session) => {
40304
+ const me2 = await unwrap(session.GET("/me"));
40305
+ const key2 = await unwrap(
40111
40306
  session.POST("/tokens", {
40112
40307
  body: { name: `${runningIn() ?? "pch"} on ${hostname()}`, scopes }
40113
40308
  })
40114
- )
40115
- }));
40116
- const name = ctx.target.accountName ?? defaultAccount(target.env);
40117
- const account2 = {
40118
- env: target.env,
40119
- token: key.key,
40120
- token_id: key.id,
40121
- ...me.username ? { username: me.username } : {},
40122
- ...target.origin !== defaultOrigin(target.env) ? { api_url: target.origin } : {},
40123
- ...target.gitUrl && target.gitUrl !== defaultGitUrl(target.env) ? { git_url: target.gitUrl } : {}
40124
- };
40125
- const config2 = ctx.config;
40126
- config2.accounts = { ...config2.accounts, [name]: account2 };
40127
- config2.active_account = name;
40128
- await writeConfig(config2);
40129
- const git = await setUpGit(ctx);
40130
- const gitIdentity = await identityAdvice(ctx);
40309
+ );
40310
+ const account2 = {
40311
+ env: target.env,
40312
+ token: key2.key,
40313
+ token_id: key2.id,
40314
+ ...me2.username ? { username: me2.username } : {},
40315
+ ...target.origin !== defaultOrigin(target.env) ? { api_url: target.origin } : {},
40316
+ ...target.gitUrl && target.gitUrl !== defaultGitUrl(target.env) ? { git_url: target.gitUrl } : {}
40317
+ };
40318
+ const config2 = ctx.config;
40319
+ config2.accounts = { ...config2.accounts, [name]: account2 };
40320
+ config2.active_account = name;
40321
+ await writeConfig(config2);
40322
+ const git2 = await setUpGit(ctx, people ? notes : void 0);
40323
+ return { me: me2, key: key2, git: git2, gitIdentity: await identityAdvice(ctx) };
40324
+ });
40131
40325
  out.result(
40132
40326
  {
40133
40327
  account: name,
@@ -40139,13 +40333,21 @@ async function login(ctx, options) {
40139
40333
  look_first: LOOK_FIRST
40140
40334
  },
40141
40335
  () => {
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);
40336
+ if (people) {
40337
+ const who = import_picocolors3.default.bold(me.username ? `@${me.username}` : me.email);
40338
+ const which = isDefault ? "" : ` ${import_picocolors3.default.dim(`(account "${name}")`)}`;
40339
+ return signedIn(ctx, `Signed in as ${who} on ${placeOf(target.origin)}${which}`, {
40340
+ git,
40341
+ notes: [
40342
+ ...me.username ? [] : [`Choose a username at ${target.origin}/welcome to publish.`],
40343
+ ...gitIdentity ? [SHORT_IDENTITY] : [],
40344
+ ...notes
40345
+ ]
40346
+ });
40147
40347
  }
40148
- out.line(`Signed in as ${who} on ${target.env} (account "${name}").`);
40348
+ out.line(
40349
+ `Signed in as ${import_picocolors3.default.bold(me.username ? `@${me.username}` : me.email)} on ${target.env} (account "${name}").`
40350
+ );
40149
40351
  if (!me.username) out.line(`Choose a username at ${target.origin}/welcome to publish.`);
40150
40352
  if (git.helper) out.line(`Git is signed in too: Patch repos at ${git.url} just work.`);
40151
40353
  if (gitIdentity) out.line(gitIdentity);
@@ -40159,7 +40361,9 @@ async function login(ctx, options) {
40159
40361
  async function loginWithKey(ctx) {
40160
40362
  const { out, target } = ctx;
40161
40363
  const me = await unwrap(ctx.api.GET("/me"));
40162
- const git = await setUpGit(ctx);
40364
+ const people = forAPerson(out.json, runningIn());
40365
+ const notes = [];
40366
+ const git = await setUpGit(ctx, people ? notes : void 0);
40163
40367
  const gitIdentity = await identityAdvice(ctx);
40164
40368
  out.result(
40165
40369
  {
@@ -40172,63 +40376,57 @@ async function loginWithKey(ctx) {
40172
40376
  look_first: LOOK_FIRST
40173
40377
  },
40174
40378
  () => {
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);
40379
+ if (people) {
40380
+ const who = import_picocolors3.default.bold(me.username ? `@${me.username}` : me.email);
40381
+ return signedIn(ctx, `PCH_TOKEN works: ${who} on ${placeOf(target.origin)}`, {
40382
+ git,
40383
+ notes: [...gitIdentity ? [SHORT_IDENTITY] : [], ...notes]
40384
+ });
40179
40385
  }
40180
- out.line(`PCH_TOKEN works: ${who} on ${target.env}.`);
40386
+ out.line(
40387
+ `PCH_TOKEN works: ${import_picocolors3.default.bold(me.username ? `@${me.username}` : me.email)} on ${target.env}.`
40388
+ );
40181
40389
  if (git.helper) out.line(`Git is signed in too: Patch repos at ${git.url} just work.`);
40182
40390
  if (gitIdentity) out.line(gitIdentity);
40183
40391
  out.line(LOOK_FIRST);
40184
40392
  }
40185
40393
  );
40186
40394
  }
40187
- function defaultAccount(env) {
40188
- return env === "production" ? "default" : env;
40189
- }
40190
- function signedIn(ctx, headline, username, git, gitIdentity) {
40395
+ var SHORT_IDENTITY = "Git doesn't know who you are on this PC yet: pch new and pch clone set that up for each repo.";
40396
+ function signedIn(ctx, headline, { git, notes }) {
40191
40397
  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
40398
  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));
40399
+ const mark = symbols();
40203
40400
  out.line();
40204
- for (const [command, what] of next) {
40205
- out.line(` ${import_picocolors2.default.cyan(command.padEnd(width))} ${import_picocolors2.default.dim(what)}`);
40401
+ out.line(` ${import_picocolors3.default.bold(mark.done)} ${headline}`);
40402
+ if (git.helper && git.url) {
40403
+ out.line(import_picocolors3.default.dim(` Git is signed in too, for repos at ${placeOf(git.url)}.`));
40206
40404
  }
40405
+ for (const note2 of notes) out.line(` ${import_picocolors3.default.bold(mark.note)} ${note2}`);
40406
+ out.line();
40407
+ out.line(` ${import_picocolors3.default.dim("Next")}`);
40408
+ const next = commandList(
40409
+ [
40410
+ [`${pch} search "<your idea>"`, "find a block or game to start from"],
40411
+ [`${pch} agent install`, "give your coding agent Patch's tools"],
40412
+ [`${pch} guide`, "how to make and publish a game"]
40413
+ ],
40414
+ " "
40415
+ );
40416
+ for (const row of next) out.line(row);
40207
40417
  out.line();
40208
40418
  }
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
- }
40222
- async function setUpGit(ctx) {
40419
+ async function setUpGit(ctx, notes) {
40223
40420
  const { gitUrl: url2 } = ctx.target;
40421
+ const say = (text) => notes ? notes.push(text) : ctx.out.step(text);
40224
40422
  if (!url2) return { url: url2, helper: false, lfs: false };
40225
40423
  try {
40226
40424
  await setUpGitHelper(ctx);
40227
40425
  const lfs = await setUpLfs();
40228
- if (!lfs) ctx.out.step(NO_LFS);
40426
+ if (!lfs) say(NO_LFS);
40229
40427
  return { url: url2, helper: true, lfs };
40230
40428
  } catch (error62) {
40231
- ctx.out.step(`Git isn't set up: ${error62.message}`);
40429
+ say(`Git isn't set up: ${error62.message}`);
40232
40430
  return { url: url2, helper: false, lfs: false };
40233
40431
  }
40234
40432
  }
@@ -40239,55 +40437,68 @@ async function withDeviceSession(ctx, options, use) {
40239
40437
  if (!started.response.ok) throw authError(started.body, "Couldn't start a login");
40240
40438
  const device = started.body;
40241
40439
  const code = formatUserCode(device.user_code);
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");
40440
+ const opening = options.browser !== false && Boolean(process.stdout.isTTY);
40441
+ const people = forAPerson(out.json, runningIn());
40442
+ const block = new Block();
40443
+ const drawn = people && options.logo ? logo() : null;
40444
+ if (drawn) process.stderr.write(`
40445
+ ${drawn.map((line) => ` ${line}`).join("\n")}
40446
+ `);
40447
+ if (people) {
40448
+ block.write();
40449
+ block.write(" Sign in to Patch with this code");
40450
+ block.write();
40451
+ const copied = copyText(device.user_code) ? ` ${import_picocolors3.default.dim("copied")}` : "";
40452
+ block.write(`${import_picocolors3.default.bold(pink(device.user_code))}${copied}`);
40453
+ block.write();
40454
+ if (opening) {
40455
+ const page = import_picocolors3.default.bold(`${placeOf(target.origin)}/device`);
40456
+ block.write(` Opening ${page} in your browser: check the code matches, then approve.`);
40457
+ block.write(import_picocolors3.default.dim(` Didn't open? Go to ${device.verification_uri} and enter the code.`));
40458
+ } else {
40459
+ block.write(` Go to ${import_picocolors3.default.bold(device.verification_uri)} and enter the code.`);
40460
+ }
40461
+ block.write();
40258
40462
  } else {
40259
40463
  process.stderr.write(
40260
40464
  `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.
40261
40465
 
40262
- To sign in, open ${import_picocolors2.default.bold(device.verification_uri)} and enter ${import_picocolors2.default.bold(code)}
40466
+ To sign in, open ${import_picocolors3.default.bold(device.verification_uri)} and enter ${import_picocolors3.default.bold(code)}
40263
40467
 
40264
40468
  `
40265
40469
  );
40266
- out.step("Waiting for you to approve in the browser\u2026");
40267
40470
  }
40268
- let sessionToken = null;
40471
+ if (opening) openBrowser(device.verification_uri_complete);
40472
+ const waiting = people ? spinner("Waiting for you to approve", "Ctrl+C to cancel") : { stop() {
40473
+ }, say() {
40474
+ } };
40475
+ if (!people) out.step("Waiting for you to approve in the browser\u2026");
40269
40476
  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 }));
40477
+ const sessionToken = await pollForSession(ctx, device, options);
40478
+ waiting.say("Approved. Finishing up");
40479
+ let value;
40480
+ try {
40481
+ value = await use(createApi(target, { token: sessionToken }));
40482
+ } finally {
40483
+ await fetch(`${auth2}/sign-out`, {
40484
+ method: "POST",
40485
+ headers: { authorization: `Bearer ${sessionToken}`, "content-type": "application/json" },
40486
+ body: "{}"
40487
+ }).catch(() => void 0);
40488
+ }
40489
+ waiting.stop();
40490
+ block.erase();
40491
+ return value;
40279
40492
  } 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);
40493
+ waiting.stop();
40285
40494
  }
40286
40495
  }
40287
- async function approval(ctx, device, options) {
40496
+ var TRY_AGAIN = [["pch login", "get a new code and try again"]];
40497
+ async function pollForSession(ctx, device, options) {
40288
40498
  const auth2 = `${ctx.target.origin}/api/auth`;
40289
40499
  let interval = (options.interval ?? device.interval) * 1e3;
40290
40500
  const deadline = Date.now() + device.expires_in * 1e3;
40501
+ let sessionToken = null;
40291
40502
  while (Date.now() < deadline) {
40292
40503
  await sleep(interval);
40293
40504
  const polled = await postJson(`${auth2}/device/token`, {
@@ -40295,7 +40506,10 @@ async function approval(ctx, device, options) {
40295
40506
  device_code: device.device_code,
40296
40507
  client_id: CLIENT_ID
40297
40508
  });
40298
- if (polled.response.ok) return polled.body.access_token;
40509
+ if (polled.response.ok) {
40510
+ sessionToken = polled.body.access_token;
40511
+ break;
40512
+ }
40299
40513
  const error62 = polled.body.error;
40300
40514
  if (error62 === "authorization_pending") continue;
40301
40515
  if (error62 === "slow_down") {
@@ -40303,12 +40517,27 @@ async function approval(ctx, device, options) {
40303
40517
  continue;
40304
40518
  }
40305
40519
  if (error62 === "access_denied") {
40306
- throw new CliError(EXIT.auth, "access_denied", "The login was denied in the browser.");
40520
+ const denied = new CliError(
40521
+ EXIT.auth,
40522
+ "access_denied",
40523
+ "The login was denied in the browser."
40524
+ );
40525
+ denied.forPeople = { title: "The sign-in was denied in the browser", next: TRY_AGAIN };
40526
+ throw denied;
40307
40527
  }
40308
40528
  if (error62 === "expired_token") break;
40309
40529
  throw authError(polled.body, "The login failed");
40310
40530
  }
40311
- return null;
40531
+ if (!sessionToken) {
40532
+ const expired = new CliError(
40533
+ EXIT.auth,
40534
+ "expired_token",
40535
+ "The code expired. Run pch login again."
40536
+ );
40537
+ expired.forPeople = { title: "The code expired before it was approved", next: TRY_AGAIN };
40538
+ throw expired;
40539
+ }
40540
+ return sessionToken;
40312
40541
  }
40313
40542
  function runningIn(vars = process.env) {
40314
40543
  if (vars.CLAUDECODE === "1") return "Claude Code";
@@ -40347,12 +40576,25 @@ async function whoami(ctx) {
40347
40576
  requireToken(ctx.target);
40348
40577
  const me = await unwrap(ctx.api.GET("/me"));
40349
40578
  ctx.out.result({ ...me, env: ctx.target.env, account: ctx.target.accountName }, () => {
40579
+ const { out, target } = ctx;
40350
40580
  const who = me.username ? `@${me.username}` : me.email;
40351
- const role = me.role === "staff" ? import_picocolors3.default.yellow(" (staff)") : "";
40352
- const via = me.auth.via === "api_key" ? `API key [${(me.auth.scopes ?? []).join(", ")}]` : "session";
40353
- ctx.out.line(`${import_picocolors3.default.bold(who)}${role} on ${ctx.target.env} via ${via}`);
40354
- if (ctx.target.accountName) ctx.out.line(import_picocolors3.default.dim(`account "${ctx.target.accountName}"`));
40355
- for (const org of me.orgs) ctx.out.line(` ${org.slug} ${import_picocolors3.default.dim(`${org.kind}, ${org.role}`)}`);
40581
+ const role = me.role === "staff" ? import_picocolors4.default.dim(" \xB7 staff") : "";
40582
+ const via = me.auth.via === "api_key" ? `an API key (${(me.auth.scopes ?? []).join(", ") || "no scopes"})` : "a session";
40583
+ const saved = target.accountName ? `, saved as account "${target.accountName}"` : "";
40584
+ out.line();
40585
+ out.line(
40586
+ ` ${pink(symbols().done)} ${import_picocolors4.default.bold(who)} on ${placeOf(target.origin)} ${import_picocolors4.default.dim(`(${target.env})`)}${role}`
40587
+ );
40588
+ out.line(import_picocolors4.default.dim(` Signed in with ${via}${saved}.`));
40589
+ if (me.orgs.length) {
40590
+ out.line();
40591
+ out.line(` ${import_picocolors4.default.dim("Orgs")}`);
40592
+ const wide = Math.max(...me.orgs.map((org) => org.slug.length));
40593
+ for (const org of me.orgs) {
40594
+ out.line(` ${org.slug.padEnd(wide)} ${import_picocolors4.default.dim(`${org.kind}, ${org.role}`)}`);
40595
+ }
40596
+ }
40597
+ out.line();
40356
40598
  });
40357
40599
  }
40358
40600
  async function logout(ctx) {
@@ -40376,11 +40618,28 @@ async function logout(ctx) {
40376
40618
  }
40377
40619
  const git = await signOutOfGit(ctx);
40378
40620
  ctx.out.result({ account: name, revoked, git }, () => {
40379
- ctx.out.line(name ? `Signed out of "${name}".` : "PCH_TOKEN stays set; pch keeps no copy.");
40380
- if (name && !revoked) {
40381
- ctx.out.line(import_picocolors3.default.dim("The key wasn't revoked; revoke it on patch.gg if needed."));
40621
+ const { out, target } = ctx;
40622
+ const mark = symbols();
40623
+ out.line();
40624
+ if (name) {
40625
+ const which = import_picocolors4.default.dim(`(account "${name}")`);
40626
+ out.line(` ${pink(mark.done)} Signed out of ${placeOf(target.origin)} ${which}`);
40627
+ if (revoked) out.line(import_picocolors4.default.dim(" Its key is revoked, and pch keeps no copy."));
40628
+ else {
40629
+ out.line(
40630
+ ` ${import_picocolors4.default.bold(mark.note)} The key wasn't revoked: revoke it in your settings on ${placeOf(target.origin)} if you need to.`
40631
+ );
40632
+ }
40633
+ } else {
40634
+ out.line(
40635
+ ` ${pink(mark.done)} Nothing saved to sign out of: PCH_TOKEN stays set, and pch keeps no copy.`
40636
+ );
40382
40637
  }
40383
- if (git.url && !git.helper) ctx.out.line(import_picocolors3.default.dim(`Git is signed out of ${git.url}.`));
40638
+ if (git.url && !git.helper) out.line(import_picocolors4.default.dim(` Git is signed out of ${placeOf(git.url)}.`));
40639
+ out.line();
40640
+ if (name)
40641
+ out.line(` ${import_picocolors4.default.dim("Sign in again with")} ${import_picocolors4.default.bold(`${pchOn(target.env)} login`)}`);
40642
+ if (name) out.line();
40384
40643
  });
40385
40644
  }
40386
40645
  async function signOutOfGit(ctx) {
@@ -40400,7 +40659,7 @@ async function accountList(ctx) {
40400
40659
  ctx.out.result({ data: accounts }, () => {
40401
40660
  if (!accounts.length) ctx.out.line("No saved logins. Run pch login.");
40402
40661
  for (const account2 of accounts) {
40403
- const marker = account2.active ? import_picocolors3.default.green("*") : " ";
40662
+ const marker = account2.active ? import_picocolors4.default.green("*") : " ";
40404
40663
  const who = account2.username ? `@${account2.username}` : "";
40405
40664
  ctx.out.line(`${marker} ${account2.name.padEnd(12)} ${account2.env.padEnd(10)} ${who}`);
40406
40665
  }
@@ -40439,7 +40698,7 @@ async function tokenCreate(ctx, options) {
40439
40698
  ctx.out.result(created, () => {
40440
40699
  ctx.out.line(created.key);
40441
40700
  process.stderr.write(
40442
- import_picocolors3.default.dim("Shown once. Store it as PCH_TOKEN where your agent or CI runs.\n")
40701
+ import_picocolors4.default.dim("Shown once. Store it as PCH_TOKEN where your agent or CI runs.\n")
40443
40702
  );
40444
40703
  });
40445
40704
  }
@@ -40450,7 +40709,7 @@ async function tokenList(ctx) {
40450
40709
  if (!tokens.data.length) ctx.out.line("No API keys.");
40451
40710
  for (const token2 of tokens.data) {
40452
40711
  ctx.out.line(
40453
- `${token2.id} ${(token2.name ?? "").padEnd(24)} ${token2.scopes.join(",").padEnd(18)} ${import_picocolors3.default.dim(token2.start ? `${token2.start}\u2026` : "")}`
40712
+ `${token2.id} ${(token2.name ?? "").padEnd(24)} ${token2.scopes.join(",").padEnd(18)} ${import_picocolors4.default.dim(token2.start ? `${token2.start}\u2026` : "")}`
40454
40713
  );
40455
40714
  }
40456
40715
  });
@@ -40462,7 +40721,7 @@ async function tokenRevoke(ctx, id) {
40462
40721
  }
40463
40722
 
40464
40723
  // src/commands/agent-brief.ts
40465
- var import_picocolors5 = __toESM(require_picocolors(), 1);
40724
+ var import_picocolors6 = __toESM(require_picocolors(), 1);
40466
40725
  import { constants } from "node:fs";
40467
40726
  import { access as access2, copyFile, lstat, mkdir as mkdir2, readdir as readdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
40468
40727
  import { basename, dirname as dirname3, extname, join as join5, resolve as resolve4 } from "node:path";
@@ -41216,7 +41475,8 @@ function parseInstallPath(value) {
41216
41475
  if (rest && !isReleasePath(rest)) return null;
41217
41476
  return { root, game: game ?? null, path: rest };
41218
41477
  }
41219
- var installPath = external_exports.string().refine(
41478
+ var PATH_MAX = 512;
41479
+ var installPath = external_exports.string().max(PATH_MAX, `must be at most ${PATH_MAX} characters`).refine(
41220
41480
  (value) => parseInstallPath(value) !== null,
41221
41481
  `must start with a folder like {game} or {profile} (${INSTALL_ROOTS.map((root) => `{${root}}`).join(", ")}, or {game:slug} for another listed game) and never use ..`
41222
41482
  );
@@ -41228,19 +41488,19 @@ var gameEntry = external_exports.object({
41228
41488
  ),
41229
41489
  version: shortLine(80).optional().describe('The game versions it works with, in words or as a range, e.g. ">=1.6".'),
41230
41490
  stores: external_exports.array(store).min(1).optional(),
41231
- builds: external_exports.partialRecord(store, external_exports.array(external_exports.string().min(1)).min(1)).optional().describe("Exact builds per store; leave out to accept any build.")
41491
+ builds: external_exports.partialRecord(store, external_exports.array(external_exports.string().min(1).max(80)).min(1).max(32)).optional().describe("Exact builds per store; leave out to accept any build.")
41232
41492
  });
41233
41493
  var component = external_exports.object({
41234
41494
  id: external_exports.string().regex(componentId, "must be lowercase letters, digits, . _ or -"),
41235
- file: external_exports.string().refine((value) => value === "" || isReleasePath(value), "must be a path inside the release").describe('One uploaded file, a folder of them ending in "/", or "" for every file.'),
41495
+ file: external_exports.string().max(PATH_MAX, `must be at most ${PATH_MAX} characters`).refine((value) => value === "" || isReleasePath(value), "must be a path inside the release").describe('One uploaded file, a folder of them ending in "/", or "" for every file.'),
41236
41496
  label: shortLine(80).optional().describe('What it is, e.g. "Main files".'),
41237
41497
  kind: external_exports.enum(["main", "optional", "extra"]).default("main").describe("main: what players need. optional: players can turn it on. extra: not installed."),
41238
41498
  required: external_exports.boolean().optional(),
41239
41499
  extract: external_exports.boolean().optional().describe("The file is a zip to unpack when it's installed.")
41240
41500
  });
41241
41501
  var mapping = external_exports.object({
41242
- component: external_exports.string(),
41243
- from: external_exports.string().refine((value) => value === "" || isReleasePath(value), "must be a path inside the component").default("").describe('"" for all of the component, "folder/" for a folder of it, or one file.'),
41502
+ component: external_exports.string().max(64),
41503
+ from: external_exports.string().max(PATH_MAX, `must be at most ${PATH_MAX} characters`).refine((value) => value === "" || isReleasePath(value), "must be a path inside the component").default("").describe('"" for all of the component, "folder/" for a folder of it, or one file.'),
41244
41504
  to: installPath
41245
41505
  });
41246
41506
  var requirement = external_exports.discriminatedUnion(
@@ -41399,10 +41659,11 @@ var recipeSchema = external_exports.object({
41399
41659
  mode: external_exports.enum(["browser", "installed", "standalone", "workshop", "manual"]).optional().describe(
41400
41660
  "browser: plays in Patch's game window. installed: into a game on the player's PC. standalone: a program of its own. workshop: a Steam Workshop item. manual: players install it by hand. Left out: browser, or installed when there's a host game."
41401
41661
  ),
41402
- games: external_exports.array(gameEntry).default([]),
41403
- components: external_exports.array(component).min(1).optional().describe('Left out: one main component, "main", holding every file.'),
41404
- mappings: external_exports.array(mapping).default([]).describe("Where each component's files go."),
41405
- requirements: external_exports.array(requirement).default([]),
41662
+ // Limits far past any real recipe, so checking one stays cheap.
41663
+ games: external_exports.array(gameEntry).max(16, "can have at most 16 games").default([]),
41664
+ components: external_exports.array(component).min(1).max(64, "can have at most 64 components").optional().describe('Left out: one main component, "main", holding every file.'),
41665
+ mappings: external_exports.array(mapping).max(64, "can have at most 64 mappings").default([]).describe("Where each component's files go."),
41666
+ requirements: external_exports.array(requirement).max(32, "can have at most 32 requirements").default([]),
41406
41667
  launch: launch.optional().describe("Left out: index.html for a browser game, the game itself otherwise."),
41407
41668
  settings: external_exports.array(
41408
41669
  external_exports.object({
@@ -41455,12 +41716,12 @@ var recipeSchema = external_exports.object({
41455
41716
  workshop: external_exports.object({ steam_app_id: external_exports.string().regex(/^\d+$/), file_id: external_exports.string().regex(/^\d+$/) }).optional(),
41456
41717
  credits: external_exports.array(
41457
41718
  external_exports.object({
41458
- files: external_exports.string().min(1),
41459
- license: external_exports.string().min(1).describe("SPDX id, e.g. CC0-1.0 or MIT."),
41460
- source: external_exports.string().url().optional(),
41461
- author: external_exports.string().optional()
41719
+ files: external_exports.string().min(1).max(PATH_MAX),
41720
+ license: external_exports.string().min(1).max(200).describe("SPDX id, e.g. CC0-1.0 or MIT."),
41721
+ source: external_exports.string().url().max(2e3).optional(),
41722
+ author: external_exports.string().max(200).optional()
41462
41723
  })
41463
- ).default([])
41724
+ ).max(500, "can have at most 500 credits").default([])
41464
41725
  }).strict();
41465
41726
 
41466
41727
  // src/manifest/safety.ts
@@ -42359,7 +42620,7 @@ function hashFile(path2) {
42359
42620
  }
42360
42621
 
42361
42622
  // src/commands/agent-guide.ts
42362
- var import_picocolors4 = __toESM(require_picocolors(), 1);
42623
+ var import_picocolors5 = __toESM(require_picocolors(), 1);
42363
42624
  import { access, writeFile as writeFile2 } from "node:fs/promises";
42364
42625
  import { join as join4, resolve as resolve3 } from "node:path";
42365
42626
 
@@ -42514,12 +42775,12 @@ async function agentGuide(ctx, dir) {
42514
42775
  const { name, written } = await writeAgentGuide(root);
42515
42776
  ctx.out.result({ path: join4(root, name), written }, () => {
42516
42777
  if (written) {
42517
- ctx.out.line(`Wrote ${import_picocolors4.default.bold(join4(root, name))}, for coding agents working in this folder.`);
42518
- ctx.out.line(import_picocolors4.default.dim("Commit it: in a clone, pch publish refuses files that aren't."));
42778
+ ctx.out.line(`Wrote ${import_picocolors5.default.bold(join4(root, name))}, for coding agents working in this folder.`);
42779
+ ctx.out.line(import_picocolors5.default.dim("Commit it: in a clone, pch publish refuses files that aren't."));
42519
42780
  } else {
42520
42781
  ctx.out.line(`${join4(root, name)} is this folder's guide for agents, so it's left as it is.`);
42521
42782
  ctx.out.line(
42522
- import_picocolors4.default.dim("pch guide has how to run, check and publish, to add to it if it helps.")
42783
+ import_picocolors5.default.dim("pch guide has how to run, check and publish, to add to it if it helps.")
42523
42784
  );
42524
42785
  }
42525
42786
  });
@@ -42545,10 +42806,10 @@ async function askForBrief(lines, say) {
42545
42806
  };
42546
42807
  const answers = [];
42547
42808
  for (const { ask } of QUESTIONS) {
42548
- say(import_picocolors5.default.bold(ask));
42809
+ say(import_picocolors6.default.bold(ask));
42549
42810
  answers.push((await untilBlank()).join("\n"));
42550
42811
  }
42551
- say(import_picocolors5.default.bold(PICTURES_ASK));
42812
+ say(import_picocolors6.default.bold(PICTURES_ASK));
42552
42813
  return { answers, pictures: await untilBlank() };
42553
42814
  }
42554
42815
  function briefText(answers, pictures) {
@@ -42638,7 +42899,7 @@ async function agentBrief(ctx, dir) {
42638
42899
  ctx.out.step(
42639
42900
  `A brief stays with this game, and every agent that works on it reads it first. ${PUBLIC}`
42640
42901
  );
42641
- ctx.out.step(import_picocolors5.default.dim("A blank line ends an answer, or skips the question."));
42902
+ ctx.out.step(import_picocolors6.default.dim("A blank line ends an answer, or skips the question."));
42642
42903
  const said2 = await fromTerminal(ctx);
42643
42904
  const pictures = await gatherPictures(root, said2.pictures, (line) => ctx.out.step(line));
42644
42905
  const text = briefText(said2.answers, pictures);
@@ -42652,13 +42913,13 @@ async function agentBrief(ctx, dir) {
42652
42913
  await writeAgentGuide(root);
42653
42914
  const silent = await guidesNotNaming(root);
42654
42915
  ctx.out.result({ path: path2, written: true, pictures, guides_without_it: silent }, () => {
42655
- ctx.out.line(`Wrote ${import_picocolors5.default.bold(path2)}. Change it whenever you like: it's yours.`);
42916
+ ctx.out.line(`Wrote ${import_picocolors6.default.bold(path2)}. Change it whenever you like: it's yours.`);
42656
42917
  for (const name of silent) {
42657
42918
  ctx.out.line(
42658
42919
  `${name} doesn't mention it, so agents may not read it. Add this to it: Read ${BRIEF_FILE} first: it's my idea and rules for this game.`
42659
42920
  );
42660
42921
  }
42661
- ctx.out.line(import_picocolors5.default.dim("Commit it, and every agent that clones this repo starts with it."));
42922
+ ctx.out.line(import_picocolors6.default.dim("Commit it, and every agent that clones this repo starts with it."));
42662
42923
  });
42663
42924
  }
42664
42925
  async function guidesNotNaming(root) {
@@ -42671,9 +42932,9 @@ async function guidesNotNaming(root) {
42671
42932
  }
42672
42933
 
42673
42934
  // src/commands/agent-install.ts
42674
- var import_picocolors6 = __toESM(require_picocolors(), 1);
42935
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
42675
42936
  init_errors();
42676
- import { spawnSync } from "node:child_process";
42937
+ import { spawnSync as spawnSync2 } from "node:child_process";
42677
42938
  import { existsSync as existsSync2 } from "node:fs";
42678
42939
  import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
42679
42940
  import { homedir as homedir2 } from "node:os";
@@ -42703,14 +42964,14 @@ async function agentInstall(ctx, named, options = {}) {
42703
42964
  for (const entry of agents) {
42704
42965
  const name = AGENT_NAMES[entry.agent];
42705
42966
  if (entry.state === "manual") {
42706
- ctx.out.line(`${import_picocolors6.default.bold(name)}: pch can't do this here. ${entry.how}`);
42967
+ ctx.out.line(`${import_picocolors7.default.bold(name)}: pch can't do this here. ${entry.how}`);
42707
42968
  } else {
42708
42969
  ctx.out.line(
42709
- `${import_picocolors6.default.green(`${name}:`)} ${entry.state} the ${import_picocolors6.default.bold(server.name)} MCP server, in ${entry.where}.`
42970
+ `${import_picocolors7.default.green(`${name}:`)} ${entry.state} the ${import_picocolors7.default.bold(server.name)} MCP server, in ${entry.where}.`
42710
42971
  );
42711
42972
  }
42712
42973
  }
42713
- ctx.out.line(import_picocolors6.default.dim(`It runs ${commandLine(server)}, and holds no key.`));
42974
+ ctx.out.line(import_picocolors7.default.dim(`It runs ${commandLine(server)}, and holds no key.`));
42714
42975
  if (!signedIn2) ctx.out.line(`Sign in first, or its tools won't work: ${pchLogin(ctx)}.`);
42715
42976
  ctx.out.line(
42716
42977
  "Restart your agent to load its tools. Until then, pch guide prints the same guide."
@@ -42828,11 +43089,11 @@ function onPath(bin) {
42828
43089
  function run(bin, args2) {
42829
43090
  const shell = process.platform === "win32";
42830
43091
  const quoted = shell ? args2.map((arg) => /\s/.test(arg) ? `"${arg}"` : arg) : args2;
42831
- return spawnSync(bin, quoted, { encoding: "utf8", shell, timeout: 6e4 });
43092
+ return spawnSync2(bin, quoted, { encoding: "utf8", shell, timeout: 6e4 });
42832
43093
  }
42833
43094
 
42834
43095
  // src/commands/assets.ts
42835
- var import_picocolors7 = __toESM(require_picocolors(), 1);
43096
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
42836
43097
  import { createHash as createHash2 } from "node:crypto";
42837
43098
  import { mkdir as mkdir4, readFile as readFile5, stat as stat2, writeFile as writeFile5 } from "node:fs/promises";
42838
43099
  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";
@@ -42873,17 +43134,17 @@ async function assetsSearch(ctx, q, options) {
42873
43134
  for (const hit of found.hits) {
42874
43135
  const size = hit.fit?.mb ? ` \xB7 ${hit.fit.mb} MB` : "";
42875
43136
  ctx.out.line(
42876
- `${import_picocolors7.default.bold(plain(hit.id))} ${import_picocolors7.default.dim(`${hit.type} \xB7 ${hit.license}${size}${hit.ai ? " \xB7 AI-generated" : ""}`)}`
43137
+ `${import_picocolors8.default.bold(plain(hit.id))} ${import_picocolors8.default.dim(`${hit.type} \xB7 ${hit.license}${size}${hit.ai ? " \xB7 AI-generated" : ""}`)}`
42877
43138
  );
42878
43139
  ctx.out.line(` ${plain(hit.title)}`);
42879
- if (hit.thumbnail_url) ctx.out.line(import_picocolors7.default.dim(` ${plain(hit.thumbnail_url)}`));
43140
+ if (hit.thumbnail_url) ctx.out.line(import_picocolors8.default.dim(` ${plain(hit.thumbnail_url)}`));
42880
43141
  }
42881
43142
  ctx.out.line(
42882
- import_picocolors7.default.dim(
43143
+ import_picocolors8.default.dim(
42883
43144
  `${found.total} in all. Look at the pictures, as titles don't say how an asset looks, then pch assets add <id>\u2026`
42884
43145
  )
42885
43146
  );
42886
- if (found.next_cursor) ctx.out.line(import_picocolors7.default.dim(`More: --cursor ${found.next_cursor}`));
43147
+ if (found.next_cursor) ctx.out.line(import_picocolors8.default.dim(`More: --cursor ${found.next_cursor}`));
42887
43148
  });
42888
43149
  }
42889
43150
  async function assetsFacets(ctx, q, options) {
@@ -42895,11 +43156,11 @@ async function assetsFacets(ctx, q, options) {
42895
43156
  async function assetsView(ctx, id) {
42896
43157
  const asset = await unwrap(ctx.api.GET("/assets/{id}", { params: { path: { id } } }));
42897
43158
  ctx.out.result(asset, () => {
42898
- ctx.out.line(`${import_picocolors7.default.bold(plain(asset.title))} ${import_picocolors7.default.dim(asset.id)}`);
43159
+ ctx.out.line(`${import_picocolors8.default.bold(plain(asset.title))} ${import_picocolors8.default.dim(asset.id)}`);
42899
43160
  ctx.out.line(`${asset.type_label} \xB7 ${asset.license.spdx} \xB7 ${plain(asset.source.name)}`);
42900
43161
  if (asset.description) ctx.out.line(plain(asset.description));
42901
43162
  ctx.out.line(`Credit: ${plain(asset.credit)}`);
42902
- ctx.out.line(import_picocolors7.default.dim(`Take it with pch assets add ${asset.id}`));
43163
+ ctx.out.line(import_picocolors8.default.dim(`Take it with pch assets add ${asset.id}`));
42903
43164
  });
42904
43165
  }
42905
43166
  async function assetsAdd(ctx, ids, options) {
@@ -42980,13 +43241,13 @@ async function assetsAdd(ctx, ids, options) {
42980
43241
  },
42981
43242
  () => {
42982
43243
  ctx.out.line(
42983
- import_picocolors7.default.green(`Copied ${copied.length} ${copied.length === 1 ? "file" : "files"} to ${root}.`)
43244
+ import_picocolors8.default.green(`Copied ${copied.length} ${copied.length === 1 ? "file" : "files"} to ${root}.`)
42984
43245
  );
42985
43246
  for (const entry of usage3) {
42986
- ctx.out.line(`${import_picocolors7.default.bold(entry.id)} is at ${entry.path}`);
43247
+ ctx.out.line(`${import_picocolors8.default.bold(entry.id)} is at ${entry.path}`);
42987
43248
  ctx.out.line(` ${plain(entry.usage)}`);
42988
43249
  }
42989
- for (const warning of plan.warnings) ctx.out.line(import_picocolors7.default.dim(plain(warning.msg)));
43250
+ for (const warning of plan.warnings) ctx.out.line(import_picocolors8.default.dim(plain(warning.msg)));
42990
43251
  ctx.out.line("Load them by relative path, and commit them: the release carries its files.");
42991
43252
  ctx.out.line(
42992
43253
  `Their credits are in ${fromHere(creditsFile)}. Keep it in the release, with the licences, and show them on a credits screen in the game.`
@@ -43113,7 +43374,7 @@ async function saveChecked(url2, path2, sha256) {
43113
43374
  }
43114
43375
 
43115
43376
  // src/commands/catalog.ts
43116
- var import_picocolors8 = __toESM(require_picocolors(), 1);
43377
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
43117
43378
  import { randomUUID } from "node:crypto";
43118
43379
  import { readFile as readFile6 } from "node:fs/promises";
43119
43380
  init_errors();
@@ -43216,18 +43477,18 @@ async function projectCreate(ctx, ref, options) {
43216
43477
  ) : (await ctx.api.GET("/projects/{owner}/{slug}/repo", path2)).data;
43217
43478
  const whoCanSee = repo?.visibility === "private" ? { who_can_see: PRIVATE_REPO } : {};
43218
43479
  ctx.out.result({ ...project2, repo_visibility: repo?.visibility ?? null, ...whoCanSee }, () => {
43219
- ctx.out.line(`Created ${import_picocolors8.default.bold(project2.handle)}`);
43480
+ ctx.out.line(`Created ${import_picocolors9.default.bold(project2.handle)}`);
43220
43481
  ctx.out.line(
43221
43482
  ` Source: ${SOURCE_LABELS[project2.source_visibility]} \xB7 code ${project2.code_license} \xB7 art ${project2.art_license}`
43222
43483
  );
43223
43484
  if (repo) ctx.out.line(` Repo: ${VISIBILITY_LABELS[repo.visibility]}`);
43224
43485
  if (repo?.visibility === "private") ctx.out.line(` ${PRIVATE_REPO_NOT_LOCKED}`);
43225
43486
  ctx.out.line(
43226
- import_picocolors8.default.dim(
43487
+ import_picocolors9.default.dim(
43227
43488
  `Change these with pch project update ${project2.handle}${repo ? ` and pch set ${project2.handle} --visibility \u2026` : ""}.`
43228
43489
  )
43229
43490
  );
43230
- ctx.out.line(import_picocolors8.default.dim(projectUrl(ctx.target.origin, { owner, slug: slug2 })));
43491
+ ctx.out.line(import_picocolors9.default.dim(projectUrl(ctx.target.origin, { owner, slug: slug2 })));
43231
43492
  });
43232
43493
  }
43233
43494
  async function projectView(ctx, ref) {
@@ -43250,7 +43511,7 @@ async function projectView(ctx, ref) {
43250
43511
  openCode
43251
43512
  ].filter(Boolean).join(" ");
43252
43513
  ctx.out.result({ ...project2, ...whoCanSee ? { who_can_see: whoCanSee } : {} }, () => {
43253
- ctx.out.line(`${import_picocolors8.default.bold(project2.name)} ${import_picocolors8.default.dim(project2.handle)}`);
43514
+ ctx.out.line(`${import_picocolors9.default.bold(project2.name)} ${import_picocolors9.default.dim(project2.handle)}`);
43254
43515
  if (project2.summary) ctx.out.line(project2.summary);
43255
43516
  const kind = project2.kind ? KIND_LABELS[project2.kind] : "not published yet";
43256
43517
  const games = project2.host_games.map((game) => `${game.name} (${game.role})`).join(", ");
@@ -43281,7 +43542,7 @@ async function projectView(ctx, ref) {
43281
43542
  `Source: ${repo ? VISIBILITY_LABELS[repo.visibility] : SOURCE_LABELS[project2.source_visibility]} \xB7 code ${project2.code_license} \xB7 art ${project2.art_license}`
43282
43543
  );
43283
43544
  if (repo?.visibility === "private" && !notLocked) ctx.out.line(PRIVATE_REPO_NOT_LOCKED);
43284
- ctx.out.line(import_picocolors8.default.dim(projectUrl(ctx.target.origin, { owner, slug: slug2 })));
43545
+ ctx.out.line(import_picocolors9.default.dim(projectUrl(ctx.target.origin, { owner, slug: slug2 })));
43285
43546
  });
43286
43547
  }
43287
43548
  var LINK_NAMES = ["discord", "x", "telegram", "youtube", "twitch", "github"];
@@ -43317,8 +43578,8 @@ async function projectUpdate(ctx, ref, options) {
43317
43578
  })
43318
43579
  );
43319
43580
  ctx.out.result(updated, () => {
43320
- ctx.out.line(`Updated ${import_picocolors8.default.bold(updated.handle)}`);
43321
- ctx.out.line(import_picocolors8.default.dim(updated.urls?.page ?? projectUrl(ctx.target.origin, updated)));
43581
+ ctx.out.line(`Updated ${import_picocolors9.default.bold(updated.handle)}`);
43582
+ ctx.out.line(import_picocolors9.default.dim(updated.urls?.page ?? projectUrl(ctx.target.origin, updated)));
43322
43583
  });
43323
43584
  }
43324
43585
  function parseLinks(values) {
@@ -43352,7 +43613,7 @@ async function myProjects(ctx) {
43352
43613
  if (!data.length) ctx.out.line("No projects yet. Start one with pch new <you>/<slug>.");
43353
43614
  for (const project2 of data) {
43354
43615
  ctx.out.line(
43355
- `${project2.handle.padEnd(32)} ${(project2.kind ?? "").padEnd(14)} ${project2.latest_version?.version ?? import_picocolors8.default.dim("nothing published")}`
43616
+ `${project2.handle.padEnd(32)} ${(project2.kind ?? "").padEnd(14)} ${project2.latest_version?.version ?? import_picocolors9.default.dim("nothing published")}`
43356
43617
  );
43357
43618
  }
43358
43619
  });
@@ -43392,41 +43653,41 @@ async function releaseList(ctx, ref) {
43392
43653
  } while (cursor);
43393
43654
  ctx.out.result({ data }, () => {
43394
43655
  if (!data.length) ctx.out.line("No releases.");
43395
- for (const release2 of data) {
43656
+ for (const release3 of data) {
43396
43657
  ctx.out.line(
43397
- `${release2.version.padEnd(12)} ${release2.channel.padEnd(9)} ${release2.status.padEnd(10)} ${import_picocolors8.default.dim(release2.published_at ?? release2.created_at)}`
43658
+ `${release3.version.padEnd(12)} ${release3.channel.padEnd(9)} ${release3.status.padEnd(10)} ${import_picocolors9.default.dim(release3.published_at ?? release3.created_at)}`
43398
43659
  );
43399
43660
  }
43400
43661
  });
43401
43662
  }
43402
43663
  async function releaseView(ctx, ref) {
43403
43664
  const { owner, slug: slug2, version: version2 } = parseReleaseRef(ref);
43404
- const release2 = await unwrap(
43665
+ const release3 = await unwrap(
43405
43666
  ctx.api.GET("/projects/{owner}/{slug}/releases/{version}", {
43406
43667
  params: { path: { owner, slug: slug2, version: version2 } }
43407
43668
  })
43408
43669
  );
43409
- const repo = release2.channel === "private" ? await ctx.api.GET("/projects/{owner}/{slug}/repo", { params: { path: { owner, slug: slug2 } } }).catch(() => null) : null;
43410
- const openCode = openCodeWords(`${owner}/${slug2}`, repo?.data, release2);
43411
- ctx.out.result({ ...release2, ...openCode ? { who_can_see: openCode } : {} }, () => {
43412
- ctx.out.line(`${import_picocolors8.default.bold(`${release2.project}@${release2.version}`)} ${release2.kind ?? ""}`);
43670
+ const repo = release3.channel === "private" ? await ctx.api.GET("/projects/{owner}/{slug}/repo", { params: { path: { owner, slug: slug2 } } }).catch(() => null) : null;
43671
+ const openCode = openCodeWords(`${owner}/${slug2}`, repo?.data, release3);
43672
+ ctx.out.result({ ...release3, ...openCode ? { who_can_see: openCode } : {} }, () => {
43673
+ ctx.out.line(`${import_picocolors9.default.bold(`${release3.project}@${release3.version}`)} ${release3.kind ?? ""}`);
43413
43674
  ctx.out.line(
43414
- `Channel ${release2.channel} \xB7 ${release2.status} \xB7 ${TRUST_LABELS[release2.trust_tier]}`
43675
+ `Channel ${release3.channel} \xB7 ${release3.status} \xB7 ${TRUST_LABELS[release3.trust_tier]}`
43415
43676
  );
43416
43677
  if (openCode) ctx.out.line(openCode);
43417
- if (release2.commit) {
43418
- ctx.out.line(`From commit ${release2.commit}${release2.tag ? `, tag ${release2.tag}` : ""}`);
43678
+ if (release3.commit) {
43679
+ ctx.out.line(`From commit ${release3.commit}${release3.tag ? `, tag ${release3.tag}` : ""}`);
43419
43680
  }
43420
- for (const requirement2 of release2.requirements) {
43681
+ for (const requirement2 of release3.requirements) {
43421
43682
  ctx.out.line(`Needs ${requirement2.name} (${requirement2.role})`);
43422
43683
  }
43423
- if (release2.changelog) ctx.out.line(plain(release2.changelog));
43424
- for (const file2 of release2.files) {
43684
+ if (release3.changelog) ctx.out.line(plain(release3.changelog));
43685
+ for (const file2 of release3.files) {
43425
43686
  ctx.out.line(
43426
- ` ${file2.path.padEnd(40)} ${formatSize(file2.size).padStart(8)} ${import_picocolors8.default.dim(file2.sha256)}`
43687
+ ` ${file2.path.padEnd(40)} ${formatSize(file2.size).padStart(8)} ${import_picocolors9.default.dim(file2.sha256)}`
43427
43688
  );
43428
43689
  if (file2.url) ctx.out.line(` ${file2.url}`);
43429
- else if (file2.url === null) ctx.out.line(import_picocolors8.default.dim(" can't be had right now"));
43690
+ else if (file2.url === null) ctx.out.line(import_picocolors9.default.dim(" can't be had right now"));
43430
43691
  }
43431
43692
  });
43432
43693
  }
@@ -43435,7 +43696,7 @@ async function hostGameSearch(ctx, q, limit2) {
43435
43696
  ctx.out.result(games, () => {
43436
43697
  if (!games.data.length) ctx.out.line("No host games found.");
43437
43698
  for (const game of games.data) ctx.out.line(`${game.slug.padEnd(28)} ${game.name}`);
43438
- if (games.next_cursor) ctx.out.line(import_picocolors8.default.dim("More match; narrow the search or raise --limit."));
43699
+ if (games.next_cursor) ctx.out.line(import_picocolors9.default.dim("More match; narrow the search or raise --limit."));
43439
43700
  });
43440
43701
  }
43441
43702
  var WIKI_PAGE = /^(engines|methods)\/([a-z0-9][a-z0-9-]*)(?:\.md)?$/;
@@ -43449,7 +43710,7 @@ function showWikiPage(ctx, page) {
43449
43710
  page.build_seen && `build: ${page.build_seen}`
43450
43711
  ].filter(Boolean).join(" \xB7 ");
43451
43712
  ctx.out.line();
43452
- ctx.out.line(`${import_picocolors8.default.bold(page.path)} ${import_picocolors8.default.dim(plain(about))}`);
43713
+ ctx.out.line(`${import_picocolors9.default.bold(page.path)} ${import_picocolors9.default.dim(plain(about))}`);
43453
43714
  ctx.out.line(plain(page.text));
43454
43715
  }
43455
43716
  async function hostGameView(ctx, slug2) {
@@ -43477,13 +43738,13 @@ async function hostGameView(ctx, slug2) {
43477
43738
  ...unavailable ? { wiki_pages_unavailable: unavailable } : {}
43478
43739
  };
43479
43740
  ctx.out.result(result, () => {
43480
- ctx.out.line(`${import_picocolors8.default.bold(game.name)} ${import_picocolors8.default.dim(game.slug)}`);
43741
+ ctx.out.line(`${import_picocolors9.default.bold(game.name)} ${import_picocolors9.default.dim(game.slug)}`);
43481
43742
  for (const store2 of game.stores) ctx.out.line(` ${store2.store} ${store2.ref}`);
43482
43743
  for (const build of game.builds) {
43483
43744
  ctx.out.line(` build ${build.store} ${build.build_ref} ${build.version_label ?? ""}`);
43484
43745
  }
43485
- if (!ctx.target.token) ctx.out.line(import_picocolors8.default.dim("Sign in with pch login for Patch's wiki pages."));
43486
- if (unavailable) ctx.out.line(import_picocolors8.default.dim(`No wiki pages this time: ${plain(unavailable)}`));
43746
+ if (!ctx.target.token) ctx.out.line(import_picocolors9.default.dim("Sign in with pch login for Patch's wiki pages."));
43747
+ if (unavailable) ctx.out.line(import_picocolors9.default.dim(`No wiki pages this time: ${plain(unavailable)}`));
43487
43748
  for (const page of pages) showWikiPage(ctx, page);
43488
43749
  });
43489
43750
  }
@@ -43506,11 +43767,11 @@ async function noteSuggest(ctx, about, fact, options) {
43506
43767
  }
43507
43768
  const note2 = data;
43508
43769
  ctx.out.result(note2, () => {
43509
- ctx.out.line(`Suggested for ${import_picocolors8.default.bold(plain(note2.page))}. ${plain(note2.next)}`);
43770
+ ctx.out.line(`Suggested for ${import_picocolors9.default.bold(plain(note2.page))}. ${plain(note2.next)}`);
43510
43771
  for (const finding of note2.findings) {
43511
43772
  ctx.out.line(` ${finding.rule_id}: ${plain(finding.message)}`);
43512
- if (finding.line) ctx.out.line(import_picocolors8.default.dim(` ${plain(finding.line)}`));
43513
- if (finding.suggestion) ctx.out.line(import_picocolors8.default.dim(` ${plain(finding.suggestion)}`));
43773
+ if (finding.line) ctx.out.line(import_picocolors9.default.dim(` ${plain(finding.line)}`));
43774
+ if (finding.suggestion) ctx.out.line(import_picocolors9.default.dim(` ${plain(finding.suggestion)}`));
43514
43775
  }
43515
43776
  });
43516
43777
  }
@@ -43519,7 +43780,7 @@ function titleCase(slug2) {
43519
43780
  }
43520
43781
 
43521
43782
  // src/commands/change-requests.ts
43522
- var import_picocolors9 = __toESM(require_picocolors(), 1);
43783
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
43523
43784
  init_errors();
43524
43785
  var handle = (ref) => `${ref.owner}/${ref.slug}`;
43525
43786
  var counted = (count, thing) => `${count} ${thing}${count === 1 ? "" : "s"}`;
@@ -43561,7 +43822,7 @@ async function changeRequestOpen(ctx, ref, options) {
43561
43822
  const name = `${handle(project2)}#${request.number}`;
43562
43823
  out.result({ project: handle(project2), ...request, ...ahead.fields }, () => {
43563
43824
  const shown = plain(request);
43564
- out.line(`Opened ${import_picocolors9.default.bold(name)}: ${shown.title}`);
43825
+ out.line(`Opened ${import_picocolors10.default.bold(name)}: ${shown.title}`);
43565
43826
  out.line(`${handle(from)}'s ${shown.branch} into ${shown.into}`);
43566
43827
  showStanding(ctx, shown, ahead);
43567
43828
  });
@@ -43637,19 +43898,19 @@ function updateAdvice(request, steps) {
43637
43898
  ];
43638
43899
  }
43639
43900
  function showStanding({ out }, request, { fields }) {
43640
- if (fields.up_to_date === true) out.line(import_picocolors9.default.green("Up to date: it can merge."));
43901
+ if (fields.up_to_date === true) out.line(import_picocolors10.default.green("Up to date: it can merge."));
43641
43902
  if (fields.up_to_date === false) {
43642
- out.line(import_picocolors9.default.yellow(`Out of date: ${outOfDate(request)}`));
43903
+ out.line(import_picocolors10.default.yellow(`Out of date: ${outOfDate(request)}`));
43643
43904
  if (fields.update) for (const line of updateAdvice(request, fields.update)) out.line(line);
43644
43905
  }
43645
43906
  if (fields.up_to_date === null && request.state === "open") {
43646
43907
  out.line(
43647
- import_picocolors9.default.yellow("Patch can't tell whether it's up to date just now. Try again in a moment.")
43908
+ import_picocolors10.default.yellow("Patch can't tell whether it's up to date just now. Try again in a moment.")
43648
43909
  );
43649
43910
  }
43650
43911
  if (request.problems.length) {
43651
43912
  out.line(
43652
- import_picocolors9.default.yellow(
43913
+ import_picocolors10.default.yellow(
43653
43914
  "The latest push to its branch hasn't reached this request, so it stays where it was:"
43654
43915
  )
43655
43916
  );
@@ -43684,20 +43945,20 @@ async function changeRequestList(ctx, ref, options) {
43684
43945
  }
43685
43946
  for (const request of plain(data)) {
43686
43947
  const open3 = request.state === "open";
43687
- const state = request.up_to_date === false ? import_picocolors9.default.yellow("out of date".padEnd(12)) : open3 && request.up_to_date === null ? import_picocolors9.default.yellow("not checked".padEnd(12)) : request.state.padEnd(12);
43948
+ const state = request.up_to_date === false ? import_picocolors10.default.yellow("out of date".padEnd(12)) : open3 && request.up_to_date === null ? import_picocolors10.default.yellow("not checked".padEnd(12)) : request.state.padEnd(12);
43688
43949
  const from = request.from ? `${request.from} ${request.branch}` : request.branch;
43689
43950
  out.line(
43690
- `#${String(request.number).padEnd(4)} ${state} ${request.title} ${import_picocolors9.default.dim(`(${from} \u2192 ${request.into})`)}`
43951
+ `#${String(request.number).padEnd(4)} ${state} ${request.title} ${import_picocolors10.default.dim(`(${from} \u2192 ${request.into})`)}`
43691
43952
  );
43692
43953
  }
43693
43954
  if (data.some((request) => request.up_to_date === false)) {
43694
43955
  out.line(
43695
- import_picocolors9.default.dim(
43956
+ import_picocolors10.default.dim(
43696
43957
  `Out of date ones can't merge yet. pch change-request view ${handle(project2)}#<number> says how to update one.`
43697
43958
  )
43698
43959
  );
43699
43960
  }
43700
- if (cursor) out.line(import_picocolors9.default.dim("There are more; raise --limit."));
43961
+ if (cursor) out.line(import_picocolors10.default.dim("There are more; raise --limit."));
43701
43962
  });
43702
43963
  }
43703
43964
  async function mapInTurn(items, size, run3) {
@@ -43743,7 +44004,7 @@ async function readRequest(ctx, ref) {
43743
44004
  function showHeader(ctx, project2, request, diff, fetch2) {
43744
44005
  const { out } = ctx;
43745
44006
  out.line(
43746
- `${import_picocolors9.default.bold(`${handle(project2)}#${request.number}`)} ${request.title} ${import_picocolors9.default.dim(request.state)}`
44007
+ `${import_picocolors10.default.bold(`${handle(project2)}#${request.number}`)} ${request.title} ${import_picocolors10.default.dim(request.state)}`
43747
44008
  );
43748
44009
  const parts = [
43749
44010
  `${request.from ? `${request.from}'s ` : ""}${request.branch} into ${request.into}`,
@@ -43762,15 +44023,15 @@ function showHeader(ctx, project2, request, diff, fetch2) {
43762
44023
  } else if (request.closed_by) {
43763
44024
  out.line(`${request.state === "declined" ? "Declined" : "Withdrawn"} by ${request.closed_by}`);
43764
44025
  }
43765
- if (fetch2) out.line(import_picocolors9.default.dim(`To get its commits: ${fetch2}`));
44026
+ if (fetch2) out.line(import_picocolors10.default.dim(`To get its commits: ${fetch2}`));
43766
44027
  }
43767
44028
  function showTalk({ out }, talk) {
43768
44029
  if (!talk.length) return;
43769
44030
  out.line();
43770
- out.line(import_picocolors9.default.bold("Talk"));
44031
+ out.line(import_picocolors10.default.bold("Talk"));
43771
44032
  for (const post of talk) {
43772
44033
  const who = `${post.author ?? "someone"}${post.via === "api_key" ? " (with a key)" : ""}`;
43773
- out.line(`${import_picocolors9.default.dim(`${who}, ${post.created_at}`)}`);
44034
+ out.line(`${import_picocolors10.default.dim(`${who}, ${post.created_at}`)}`);
43774
44035
  out.line(post.body);
43775
44036
  }
43776
44037
  }
@@ -43803,18 +44064,18 @@ async function changeRequestView(ctx, ref) {
43803
44064
  function showFiles({ out }, diff) {
43804
44065
  if (tooBigToList(diff)) {
43805
44066
  out.line();
43806
- out.line(import_picocolors9.default.yellow("There's more in this request than can show here."));
44067
+ out.line(import_picocolors10.default.yellow("There's more in this request than can show here."));
43807
44068
  }
43808
44069
  if (!diff.files.length) return;
43809
44070
  out.line();
43810
44071
  for (const file2 of diff.files.slice(0, FILES_SHOWN)) {
43811
44072
  out.line(
43812
- `${file2.status.padEnd(9)} ${file2.path} ${import_picocolors9.default.dim(`+${file2.additions} -${file2.deletions}`)}`
44073
+ `${file2.status.padEnd(9)} ${file2.path} ${import_picocolors10.default.dim(`+${file2.additions} -${file2.deletions}`)}`
43813
44074
  );
43814
44075
  }
43815
44076
  if (diff.files.length > FILES_SHOWN) {
43816
44077
  out.line(
43817
- import_picocolors9.default.dim(
44078
+ import_picocolors10.default.dim(
43818
44079
  `And ${diff.files.length - FILES_SHOWN} more: pch change-request review has them all.`
43819
44080
  )
43820
44081
  );
@@ -43834,7 +44095,7 @@ async function changeRequestReview(ctx, ref) {
43834
44095
  }
43835
44096
  if (shown.diff) showDiff(ctx, shown.diff, shown.fetch);
43836
44097
  else
43837
- out.line(import_picocolors9.default.yellow("Patch couldn't read what it changes just now. Try again in a moment."));
44098
+ out.line(import_picocolors10.default.yellow("Patch couldn't read what it changes just now. Try again in a moment."));
43838
44099
  showTalk(ctx, shown.talk);
43839
44100
  });
43840
44101
  }
@@ -43846,30 +44107,30 @@ var NO_PATCH = {
43846
44107
  };
43847
44108
  function showDiff({ out }, diff, fetch2) {
43848
44109
  out.line();
43849
- out.line(import_picocolors9.default.bold("Commits"));
44110
+ out.line(import_picocolors10.default.bold("Commits"));
43850
44111
  for (const commit of diff.commits) {
43851
- out.line(`${import_picocolors9.default.dim(commit.id.slice(0, 7))} ${commit.subject} ${import_picocolors9.default.dim(commit.author)}`);
44112
+ out.line(`${import_picocolors10.default.dim(commit.id.slice(0, 7))} ${commit.subject} ${import_picocolors10.default.dim(commit.author)}`);
43852
44113
  }
43853
44114
  if (diff.commit_count > diff.commits.length) {
43854
- out.line(import_picocolors9.default.dim(`The newest ${diff.commits.length} of ${diff.commit_count}.`));
44115
+ out.line(import_picocolors10.default.dim(`The newest ${diff.commits.length} of ${diff.commit_count}.`));
43855
44116
  }
43856
44117
  for (const file2 of diff.files) {
43857
44118
  const moved2 = file2.old_path ? `${file2.old_path} \u2192 ` : "";
43858
44119
  out.line();
43859
44120
  out.line(
43860
- `${import_picocolors9.default.bold(`${moved2}${file2.path}`)} ${import_picocolors9.default.dim(`${file2.status}, +${file2.additions} -${file2.deletions}`)}`
44121
+ `${import_picocolors10.default.bold(`${moved2}${file2.path}`)} ${import_picocolors10.default.dim(`${file2.status}, +${file2.additions} -${file2.deletions}`)}`
43861
44122
  );
43862
44123
  if (file2.big_file) {
43863
44124
  const size = (side) => side ? formatSize(side.size) : "nothing";
43864
44125
  out.line(`Big file: ${size(file2.big_file.old)} \u2192 ${size(file2.big_file.new)}`);
43865
44126
  }
43866
44127
  if (file2.patch) out.line(file2.patch.trimEnd());
43867
- else if (file2.no_patch) out.line(import_picocolors9.default.dim(NO_PATCH[file2.no_patch]));
44128
+ else if (file2.no_patch) out.line(import_picocolors10.default.dim(NO_PATCH[file2.no_patch]));
43868
44129
  }
43869
44130
  if (diff.truncated) {
43870
44131
  out.line();
43871
44132
  out.line(
43872
- import_picocolors9.default.yellow(
44133
+ import_picocolors10.default.yellow(
43873
44134
  tooBigToList(diff) ? `None of it can be shown here: there's more in this request than can show.${fetch2 ? ` To read it yourself: ${fetch2}` : ""}` : "Some of it isn't shown: patches stop at 300 files or 2 MiB, and the list at 3,000."
43874
44135
  )
43875
44136
  );
@@ -43911,7 +44172,7 @@ async function changeRequestMerge(ctx, ref, options) {
43911
44172
  const request = merged.data;
43912
44173
  out.result({ project: handle(project2), ...request }, () => {
43913
44174
  const at = request.merge_commit ? ` at ${request.merge_commit.slice(0, 7)}` : "";
43914
- out.line(`Merged ${import_picocolors9.default.bold(`${handle(project2)}#${number4}`)} into ${request.into}${at}.`);
44175
+ out.line(`Merged ${import_picocolors10.default.bold(`${handle(project2)}#${number4}`)} into ${request.into}${at}.`);
43915
44176
  });
43916
44177
  }
43917
44178
  async function cantMergeYet(ctx, project2, number4) {
@@ -43941,7 +44202,7 @@ async function changeRequestDecline(ctx, ref) {
43941
44202
  })
43942
44203
  );
43943
44204
  out.result({ project: handle(project2), ...request }, () => {
43944
- out.line(`Declined ${import_picocolors9.default.bold(`${handle(project2)}#${number4}`)}.`);
44205
+ out.line(`Declined ${import_picocolors10.default.bold(`${handle(project2)}#${number4}`)}.`);
43945
44206
  });
43946
44207
  }
43947
44208
  async function changeRequestWithdraw(ctx, ref) {
@@ -43954,12 +44215,12 @@ async function changeRequestWithdraw(ctx, ref) {
43954
44215
  })
43955
44216
  );
43956
44217
  out.result({ project: handle(project2), ...request }, () => {
43957
- out.line(`Withdrew ${import_picocolors9.default.bold(`${handle(project2)}#${number4}`)}.`);
44218
+ out.line(`Withdrew ${import_picocolors10.default.bold(`${handle(project2)}#${number4}`)}.`);
43958
44219
  });
43959
44220
  }
43960
44221
 
43961
44222
  // src/commands/check.ts
43962
- var import_picocolors10 = __toESM(require_picocolors(), 1);
44223
+ var import_picocolors11 = __toESM(require_picocolors(), 1);
43963
44224
  import { existsSync as existsSync4 } from "node:fs";
43964
44225
  import { mkdir as mkdir6, mkdtemp as mkdtemp2 } from "node:fs/promises";
43965
44226
  import { tmpdir as tmpdir2 } from "node:os";
@@ -45170,10 +45431,10 @@ function gpuName(renderer) {
45170
45431
  init_errors();
45171
45432
  var usage2 = (code, message) => new CliError(EXIT.usage, code, message);
45172
45433
  var SAYS = {
45173
- pass: (text) => import_picocolors10.default.green(text),
45174
- fail: (text) => import_picocolors10.default.red(text),
45175
- warn: (text) => import_picocolors10.default.yellow(text),
45176
- cant_check: (text) => import_picocolors10.default.yellow(text)
45434
+ pass: (text) => import_picocolors11.default.green(text),
45435
+ fail: (text) => import_picocolors11.default.red(text),
45436
+ warn: (text) => import_picocolors11.default.yellow(text),
45437
+ cant_check: (text) => import_picocolors11.default.yellow(text)
45177
45438
  };
45178
45439
  var LABELS = {
45179
45440
  pass: "pass ",
@@ -45292,23 +45553,23 @@ async function check2(ctx, dir, options) {
45292
45553
  },
45293
45554
  () => {
45294
45555
  const on = rendering.software ? "no GPU, in software" : rendering.webgl ? gpuName(rendering.webgl) : "no WebGL";
45295
- out.line(`Checked ${import_picocolors10.default.bold(game.entry)} in ${version2.product} with ${on}.`);
45556
+ out.line(`Checked ${import_picocolors11.default.bold(game.entry)} in ${version2.product} with ${on}.`);
45296
45557
  out.line();
45297
45558
  for (const entry of checks) {
45298
45559
  out.line(` ${SAYS[entry.result](LABELS[entry.result])} ${entry.detail}`);
45299
45560
  }
45300
45561
  if (frames) {
45301
45562
  out.line(
45302
- ` ${import_picocolors10.default.dim("note ")} ${frames.perSecond} frames a second over ${frames.seconds} s, the longest ${frames.longestMs} ms, ${frames.slow} over 100 ms. That's only for you: no frame rate fails anything.`
45563
+ ` ${import_picocolors11.default.dim("note ")} ${frames.perSecond} frames a second over ${frames.seconds} s, the longest ${frames.longestMs} ms, ${frames.slow} over 100 ms. That's only for you: no frame rate fails anything.`
45303
45564
  );
45304
45565
  }
45305
45566
  if (observed.changedWithInput !== null && observed.changedWithInput < 1e-3) {
45306
45567
  out.line(
45307
- ` ${import_picocolors10.default.dim("note ")} The picture didn't change between the first look and the second, with input going in. It may be waiting for something scripted input doesn't do.`
45568
+ ` ${import_picocolors11.default.dim("note ")} The picture didn't change between the first look and the second, with input going in. It may be waiting for something scripted input doesn't do.`
45308
45569
  );
45309
45570
  }
45310
45571
  for (const entry of observed.errors.slice(1, 5)) {
45311
- out.line(import_picocolors10.default.dim(` also: ${entry.message}`));
45572
+ out.line(import_picocolors11.default.dim(` also: ${entry.message}`));
45312
45573
  }
45313
45574
  out.line();
45314
45575
  out.line("Look at these, as a look is what can tell you it's right:");
@@ -45321,7 +45582,7 @@ async function check2(ctx, dir, options) {
45321
45582
  printModels(ctx, models);
45322
45583
  }
45323
45584
  out.line();
45324
- out.line(import_picocolors10.default.dim(ADVICE));
45585
+ out.line(import_picocolors11.default.dim(ADVICE));
45325
45586
  }
45326
45587
  );
45327
45588
  return failed3.length || brokenModels.length ? EXIT.blocked : EXIT.ok;
@@ -45334,8 +45595,8 @@ async function serve(ctx, game) {
45334
45595
  const server = await serveGame(game.root, game.files);
45335
45596
  const url2 = `${server.origin}/${game.entry.split("/").map(encodeURIComponent).join("/")}`;
45336
45597
  ctx.out.result({ url: url2, folder: game.root }, () => {
45337
- ctx.out.line(`Serving ${game.root} as Patch's game host does, at ${import_picocolors10.default.bold(url2)}`);
45338
- ctx.out.line(import_picocolors10.default.dim("Paths have to match exactly here, as they do on Patch. Ctrl+C stops it."));
45598
+ ctx.out.line(`Serving ${game.root} as Patch's game host does, at ${import_picocolors11.default.bold(url2)}`);
45599
+ ctx.out.line(import_picocolors11.default.dim("Paths have to match exactly here, as they do on Patch. Ctrl+C stops it."));
45339
45600
  });
45340
45601
  return EXIT.ok;
45341
45602
  }
@@ -45356,23 +45617,23 @@ function showModels(ctx, models) {
45356
45617
  function printModels(ctx, models) {
45357
45618
  for (const model of models) {
45358
45619
  if (model.error) {
45359
- ctx.out.line(` ${import_picocolors10.default.red("FAIL ")} ${model.file}: ${model.error}`);
45620
+ ctx.out.line(` ${import_picocolors11.default.red("FAIL ")} ${model.file}: ${model.error}`);
45360
45621
  continue;
45361
45622
  }
45362
45623
  const textures = model.textures ?? [];
45363
45624
  const size = textures.reduce((sum, texture) => sum + texture.size, 0);
45364
45625
  ctx.out.line(
45365
- ` ${import_picocolors10.default.green("read ")} ${model.file}: ${formatSize(model.size)}, ${model.triangles} triangles, ${model.meshes} meshes (${model.skinned} skinned), ${model.materials} materials, ${textures.length} textures (${formatSize(size)}), ${model.animations?.length} animations`
45626
+ ` ${import_picocolors11.default.green("read ")} ${model.file}: ${formatSize(model.size)}, ${model.triangles} triangles, ${model.meshes} meshes (${model.skinned} skinned), ${model.materials} materials, ${textures.length} textures (${formatSize(size)}), ${model.animations?.length} animations`
45366
45627
  );
45367
45628
  if (model.needs?.length) {
45368
- ctx.out.line(import_picocolors10.default.dim(` needs a loader with ${model.needs.join(", ")}`));
45629
+ ctx.out.line(import_picocolors11.default.dim(` needs a loader with ${model.needs.join(", ")}`));
45369
45630
  }
45370
45631
  }
45371
- ctx.out.line(import_picocolors10.default.dim(" Read, not drawn: what they look like takes a look in the game."));
45632
+ ctx.out.line(import_picocolors11.default.dim(" Read, not drawn: what they look like takes a look in the game."));
45372
45633
  }
45373
45634
 
45374
45635
  // src/commands/connect.ts
45375
- var import_picocolors14 = __toESM(require_picocolors(), 1);
45636
+ var import_picocolors15 = __toESM(require_picocolors(), 1);
45376
45637
  import { readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
45377
45638
  import { join as join15, resolve as resolve11 } from "node:path";
45378
45639
  import { setTimeout as sleep5 } from "node:timers/promises";
@@ -45419,7 +45680,7 @@ function sameName(a, b) {
45419
45680
  }
45420
45681
 
45421
45682
  // src/commands/repos.ts
45422
- var import_picocolors13 = __toESM(require_picocolors(), 1);
45683
+ var import_picocolors14 = __toESM(require_picocolors(), 1);
45423
45684
  import { randomUUID as randomUUID2 } from "node:crypto";
45424
45685
  import { mkdir as mkdir7, readdir as readdir3, readFile as readFile11, rm as rm2, writeFile as writeFile9 } from "node:fs/promises";
45425
45686
  import { join as join14, resolve as resolve10 } from "node:path";
@@ -45489,9 +45750,9 @@ function reviewing(progress) {
45489
45750
  const usual = `usually about ${minutes(progress.usual_minutes)}, ${soFar}`;
45490
45751
  return progress.slow ? `Patch is reviewing it: ${step}. It's taking longer than usual: ${usual}. It finishes by itself.` : `Patch is reviewing it: ${step}. ${capital(usual)}.`;
45491
45752
  }
45492
- function nextStep(release2, status2, test2, cloud) {
45493
- const follow4 = `pch test view ${release2} --wait`;
45494
- const startOne2 = `pch test start ${release2}`;
45753
+ function nextStep(release3, status2, test2, cloud) {
45754
+ const follow4 = `pch test view ${release3} --wait`;
45755
+ const startOne2 = `pch test start ${release3}`;
45495
45756
  switch (status2) {
45496
45757
  case "draft":
45497
45758
  return {
@@ -45513,12 +45774,12 @@ function nextStep(release2, status2, test2, cloud) {
45513
45774
  case "recorded":
45514
45775
  return {
45515
45776
  say: "It has been played and recorded. Look at the footage, then send your review.",
45516
- command: `pch test footage ${release2}`
45777
+ command: `pch test footage ${release3}`
45517
45778
  };
45518
45779
  case "passed":
45519
45780
  return {
45520
45781
  say: "It passed its test, and it's still private. Publish it once you're happy it's ready.",
45521
- command: `pch release submit ${release2}`
45782
+ command: `pch release submit ${release3}`
45522
45783
  };
45523
45784
  default:
45524
45785
  return { say: "It needs a test that passes.", command: startOne2 };
@@ -45532,12 +45793,12 @@ function nextStep(release2, status2, test2, cloud) {
45532
45793
  case "cloud_review":
45533
45794
  return {
45534
45795
  say: reviewing(cloud) ?? "Patch is reviewing it, which can take several minutes, and nothing tells you when it's done: pch status says.",
45535
- command: `pch status ${release2} --wait 50`
45796
+ command: `pch status ${release3} --wait 50`
45536
45797
  };
45537
45798
  case "in_review":
45538
45799
  return {
45539
45800
  say: "It's waiting for a staff member at Patch, and nothing tells you when that's done: pch status says.",
45540
- command: `pch status ${release2} --wait 50`
45801
+ command: `pch status ${release3} --wait 50`
45541
45802
  };
45542
45803
  case "published":
45543
45804
  return { say: "It's published.", command: null };
@@ -45548,11 +45809,11 @@ function nextStep(release2, status2, test2, cloud) {
45548
45809
  var PLAY_FIRST = "This would be its first public release, and a passed test shows it starts and runs, not that it plays well, so play it yourself first: pch guide says how.";
45549
45810
  async function playFirst(ctx, ref) {
45550
45811
  const { owner, slug: slug2 } = ref;
45551
- const [release2, project2] = await Promise.all([
45812
+ const [release3, project2] = await Promise.all([
45552
45813
  ctx.api.GET("/projects/{owner}/{slug}/releases/{version}", { params: { path: ref } }),
45553
45814
  ctx.api.GET("/projects/{owner}/{slug}", { params: { path: { owner, slug: slug2 } } })
45554
45815
  ]);
45555
- const first = release2.data?.channel === "public" && release2.data.kind !== "block" && project2.data && !project2.data.latest_release;
45816
+ const first = release3.data?.channel === "public" && release3.data.kind !== "block" && project2.data && !project2.data.latest_release;
45556
45817
  return first ? PLAY_FIRST : null;
45557
45818
  }
45558
45819
  function sayNext(next) {
@@ -45597,7 +45858,7 @@ async function versionTaken(ctx, ref, failure2) {
45597
45858
  }
45598
45859
 
45599
45860
  // src/commands/init.ts
45600
- var import_picocolors11 = __toESM(require_picocolors(), 1);
45861
+ var import_picocolors12 = __toESM(require_picocolors(), 1);
45601
45862
  import { readFile as readFile10, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
45602
45863
  import { basename as basename4, join as join13, resolve as resolve9 } from "node:path";
45603
45864
  init_errors();
@@ -45645,17 +45906,17 @@ async function init(ctx, dir, options) {
45645
45906
  const agentGuide2 = { path: join13(root, guide2.name), written: guide2.written };
45646
45907
  const result = { path: path2, ...contents, on_patch: onPatch2, agent_guide: agentGuide2 };
45647
45908
  ctx.out.result({ ...result, next, look_first: LOOK_FIRST }, () => {
45648
- ctx.out.line(`Wrote ${import_picocolors11.default.bold(path2)} for ${import_picocolors11.default.bold(name)}.`);
45909
+ ctx.out.line(`Wrote ${import_picocolors12.default.bold(path2)} for ${import_picocolors12.default.bold(name)}.`);
45649
45910
  ctx.out.line(
45650
45911
  guide2.written ? `Wrote ${guide2.name}, for coding agents working in this folder.` : `Left ${guide2.name} as it is: it's this folder's guide for agents.`
45651
45912
  );
45652
- ctx.out.line(import_picocolors11.default.dim(BRIEF_WORDS));
45653
- if (files) ctx.out.line(import_picocolors11.default.dim(`The release is what's in ${files}/.`));
45913
+ ctx.out.line(import_picocolors12.default.dim(BRIEF_WORDS));
45914
+ if (files) ctx.out.line(import_picocolors12.default.dim(`The release is what's in ${files}/.`));
45654
45915
  ctx.out.line(LOOK_FIRST);
45655
45916
  ctx.out.line("Then:");
45656
45917
  for (const step of next) ctx.out.line(` ${step}`);
45657
45918
  ctx.out.line(
45658
- import_picocolors11.default.dim(
45919
+ import_picocolors12.default.dim(
45659
45920
  `A browser game whose page is index.html needs no recipe. A test's length and your credits go in one, in patch.json: ${RECIPE_EXAMPLE}. For anything else: pch guide`
45660
45921
  )
45661
45922
  );
@@ -45698,7 +45959,7 @@ async function existsOnPatch(ctx, name) {
45698
45959
  }
45699
45960
 
45700
45961
  // src/commands/scan.ts
45701
- var import_picocolors12 = __toESM(require_picocolors(), 1);
45962
+ var import_picocolors13 = __toESM(require_picocolors(), 1);
45702
45963
  import { setTimeout as sleep3 } from "node:timers/promises";
45703
45964
  init_errors();
45704
45965
  var handle2 = (ref) => `${ref.owner}/${ref.slug}`;
@@ -45761,19 +46022,19 @@ function show(ctx, project2, scan2) {
45761
46022
  out.result({ project: name, ...scan2 }, () => {
45762
46023
  if (scan2.status === "running") {
45763
46024
  out.line(
45764
- `${import_picocolors12.default.bold(name)}: still reading its history. Follow it: pch scan view ${name} --wait`
46025
+ `${import_picocolors13.default.bold(name)}: still reading its history. Follow it: pch scan view ${name} --wait`
45765
46026
  );
45766
46027
  return;
45767
46028
  }
45768
46029
  if (!scan2.up_to_date) {
45769
46030
  out.line(
45770
- import_picocolors12.default.yellow(
45771
- `${import_picocolors12.default.bold(name)}: a push has landed since this scan started, so it says nothing about what the repo holds now. Start a new one: ${start}`
46031
+ import_picocolors13.default.yellow(
46032
+ `${import_picocolors13.default.bold(name)}: a push has landed since this scan started, so it says nothing about what the repo holds now. Start a new one: ${start}`
45772
46033
  )
45773
46034
  );
45774
46035
  }
45775
46036
  if (scan2.status === "failed") {
45776
- out.line(import_picocolors12.default.yellow(`${import_picocolors12.default.bold(name)}: the scan didn't finish, so it can't go open yet.`));
46037
+ out.line(import_picocolors13.default.yellow(`${import_picocolors13.default.bold(name)}: the scan didn't finish, so it can't go open yet.`));
45777
46038
  if (scan2.failure) out.line(plain(scan2.failure));
45778
46039
  out.line(`Start a new one: ${start}`);
45779
46040
  return;
@@ -45790,7 +46051,7 @@ function show(ctx, project2, scan2) {
45790
46051
  function showClean({ out }, name, scan2) {
45791
46052
  const big = scan2.will_become_public?.big_files;
45792
46053
  const where = !big?.count || big.scanned ? "in its whole history" : "in what it read";
45793
- out.line(`${import_picocolors12.default.bold(name)}: ${import_picocolors12.default.green("no secrets found")} ${where}.`);
46054
+ out.line(`${import_picocolors13.default.bold(name)}: ${import_picocolors13.default.green("no secrets found")} ${where}.`);
45794
46055
  if (!scan2.up_to_date) return;
45795
46056
  const open3 = `pch set ${name} --visibility open`;
45796
46057
  const unread = big?.not_read?.count;
@@ -45800,18 +46061,18 @@ function showClean({ out }, name, scan2) {
45800
46061
  }
45801
46062
  function showSecrets({ out }, name, scan2) {
45802
46063
  out.line(
45803
- `${import_picocolors12.default.bold(name)}: ${import_picocolors12.default.red(`${counted2(scan2.secret_count, "secret")} found`)}, which going open would publish.`
46064
+ `${import_picocolors13.default.bold(name)}: ${import_picocolors13.default.red(`${counted2(scan2.secret_count, "secret")} found`)}, which going open would publish.`
45804
46065
  );
45805
46066
  for (const secret of plain(scan2.secrets)) {
45806
46067
  const where = secret.path ? `${secret.path}:${secret.line}` : `its message, line ${secret.line}`;
45807
46068
  const commit = secret.commit ? `${secret.commit.slice(0, 7)} ` : "";
45808
- out.line(` ${import_picocolors12.default.dim(commit)}${where}: ${secret.rule} (${secret.masked})`);
46069
+ out.line(` ${import_picocolors13.default.dim(commit)}${where}: ${secret.rule} (${secret.masked})`);
45809
46070
  }
45810
46071
  if (scan2.secrets.length < scan2.secret_count) {
45811
- out.line(import_picocolors12.default.dim(` The first ${scan2.secrets.length} of them.`));
46072
+ out.line(import_picocolors13.default.dim(` The first ${scan2.secrets.length} of them.`));
45812
46073
  }
45813
46074
  out.line();
45814
- out.line(import_picocolors12.default.bold("How to take them out of its history:"));
46075
+ out.line(import_picocolors13.default.bold("How to take them out of its history:"));
45815
46076
  for (const step of plain(scan2.fix)) {
45816
46077
  out.line(step.step);
45817
46078
  for (const command of step.commands) out.line(` ${command}`);
@@ -45823,7 +46084,7 @@ function showWhatsPublished({ out }, scan2) {
45823
46084
  scan2.will_become_public
45824
46085
  );
45825
46086
  out.line();
45826
- out.line(import_picocolors12.default.bold("Going open publishes:"));
46087
+ out.line(import_picocolors13.default.bold("Going open publishes:"));
45827
46088
  const unread = big_files.not_read?.count;
45828
46089
  const bigFiles = big_files.count ? [
45829
46090
  `${counted2(big_files.count, "big file")} (${formatSize(big_files.total_size)}${unread ? `, ${unread} not read` : ""})`
@@ -45839,14 +46100,14 @@ function showWhatsPublished({ out }, scan2) {
45839
46100
  );
45840
46101
  if (big_files.count) {
45841
46102
  const said2 = big_files.coverage ?? "This answer doesn't say whether they were read for secrets.";
45842
- out.line(unread ? import_picocolors12.default.yellow(said2) : said2);
46103
+ out.line(unread ? import_picocolors13.default.yellow(said2) : said2);
45843
46104
  }
45844
46105
  out.line("Everyone in its history, with the emails git history publishes:");
45845
46106
  for (const person of authors.people.slice(0, PEOPLE_SHOWN)) {
45846
46107
  out.line(` ${person.name} <${person.email}>`);
45847
46108
  }
45848
46109
  if (authors.count > PEOPLE_SHOWN) {
45849
- out.line(import_picocolors12.default.dim(` And ${authors.count - PEOPLE_SHOWN} more: --json has them all.`));
46110
+ out.line(import_picocolors13.default.dim(` And ${authors.count - PEOPLE_SHOWN} more: --json has them all.`));
45850
46111
  }
45851
46112
  }
45852
46113
 
@@ -45937,17 +46198,17 @@ async function newRepo(ctx, ref, dir, options) {
45937
46198
  ...options.visibility === "private" ? { who_can_see: PRIVATE_REPO } : {}
45938
46199
  },
45939
46200
  () => {
45940
- out.line(`Made ${import_picocolors13.default.bold(handle3(project2))} in ${folder}, under the ${licenceName(licence)}.`);
46201
+ out.line(`Made ${import_picocolors14.default.bold(handle3(project2))} in ${folder}, under the ${licenceName(licence)}.`);
45941
46202
  out.line(
45942
46203
  options.visibility === "private" ? `Its repo is private: only your team can find it or read its source. ${PRIVATE_REPO_NOT_LOCKED}` : options.visibility && options.visibility !== "open" ? `Its repo is ${options.visibility}.` : "Its repo opens by itself once that scan is clean: then anyone can read, clone and remix it, with what you push. To keep it to your team: pch set --visibility private."
45943
46204
  );
45944
46205
  if (commitsAs) out.line(saidCommitsAs(commitsAs));
45945
46206
  out.line(`${AGENT_GUIDE_FILE}, in its first commit, tells coding agents how to work here.`);
45946
- out.line(import_picocolors13.default.dim(BRIEF_WORDS));
46207
+ out.line(import_picocolors14.default.dim(BRIEF_WORDS));
45947
46208
  out.line(LOOK_FIRST);
45948
46209
  out.line("Fill in README.md, add your work, then commit and push with plain git.");
45949
46210
  out.line(`To release it: pch validate, then pch publish, in ${folder}.`);
45950
- out.line(import_picocolors13.default.dim(url2));
46211
+ out.line(import_picocolors14.default.dim(url2));
45951
46212
  }
45952
46213
  );
45953
46214
  }
@@ -46018,7 +46279,7 @@ async function remix(ctx, ref, options) {
46018
46279
  ...address ? { address } : {}
46019
46280
  };
46020
46281
  out.result(said2, () => {
46021
- out.line(`Remixed ${import_picocolors13.default.bold(handle3(from))} to ${import_picocolors13.default.bold(name)}.`);
46282
+ out.line(`Remixed ${import_picocolors14.default.bold(handle3(from))} to ${import_picocolors14.default.bold(name)}.`);
46022
46283
  if (address) out.line(address);
46023
46284
  out.line(words2 ?? `Its repo is ${repo.visibility}.`);
46024
46285
  if (ready) out.line(`Clone it with pch clone ${name}`);
@@ -46026,7 +46287,7 @@ async function remix(ctx, ref, options) {
46026
46287
  out.line(
46027
46288
  `A patch.json in it still names ${handle3(from)}: change "project" to ${name} before you publish.`
46028
46289
  );
46029
- out.line(import_picocolors13.default.dim(repo.clone_url));
46290
+ out.line(import_picocolors14.default.dim(repo.clone_url));
46030
46291
  });
46031
46292
  }
46032
46293
  async function yourOrg(ctx, what) {
@@ -46068,23 +46329,23 @@ async function status(ctx, ref, options) {
46068
46329
  const repoAnswer = await api.GET("/projects/{owner}/{slug}/repo", { params: { path: project2 } });
46069
46330
  const repo = repoAnswer.response.ok ? repoAnswer.data : null;
46070
46331
  const latest2 = version2 ?? await latestVersion(ctx, project2, repo);
46071
- const release2 = latest2 ? await releaseStanding(ctx, project2, latest2, options.wait) : null;
46072
- const next = release2 && "next" in release2 ? nextStep(`${handle3(project2)}@${release2.version}`, release2.status, release2.test).command : null;
46073
- const standing2 = repo?.visibility === "open" && release2 && "next" in release2 ? await api.GET("/projects/{owner}/{slug}/releases/{version}", {
46074
- params: { path: { ...project2, version: release2.version } }
46332
+ const release3 = latest2 ? await releaseStanding(ctx, project2, latest2, options.wait) : null;
46333
+ const next = release3 && "next" in release3 ? nextStep(`${handle3(project2)}@${release3.version}`, release3.status, release3.test).command : null;
46334
+ const standing2 = repo?.visibility === "open" && release3 && "next" in release3 ? await api.GET("/projects/{owner}/{slug}/releases/{version}", {
46335
+ params: { path: { ...project2, version: release3.version } }
46075
46336
  }) : null;
46076
46337
  const openCode = standing2?.data ? openCodeWords(handle3(project2), repo, standing2.data) : null;
46077
46338
  const whoCanSee = repo?.visibility === "private" ? { who_can_see: PRIVATE_REPO } : openCode ? { who_can_see: openCode } : {};
46078
- const play2 = release2 && "next" in release2 && release2.status === "testing" && release2.test?.state === "passed" ? await playFirst(ctx, { ...project2, version: release2.version }) : null;
46339
+ const play2 = release3 && "next" in release3 && release3.status === "testing" && release3.test?.state === "passed" ? await playFirst(ctx, { ...project2, version: release3.version }) : null;
46079
46340
  const ask = play2 ? { ask: play2 } : {};
46080
- out.result({ project: handle3(project2), repo, release: release2, next, ...whoCanSee, ...ask }, () => {
46081
- out.line(import_picocolors13.default.bold(handle3(project2)));
46341
+ out.result({ project: handle3(project2), repo, release: release3, next, ...whoCanSee, ...ask }, () => {
46342
+ out.line(import_picocolors14.default.bold(handle3(project2)));
46082
46343
  if (repo) out.line(repoLine(repo));
46083
46344
  if (repo?.visibility === "private") out.line(PRIVATE_REPO);
46084
46345
  if (repo?.home === "github") out.line(homeLine(repo.github));
46085
- if (!release2) out.line("No releases yet. Make one with pch publish.");
46086
- else if ("next" in release2) showReview(ctx, project2, release2);
46087
- else out.line(`Release ${release2.version}: ${import_picocolors13.default.bold(release2.status)}`);
46346
+ if (!release3) out.line("No releases yet. Make one with pch publish.");
46347
+ else if ("next" in release3) showReview(ctx, project2, release3);
46348
+ else out.line(`Release ${release3.version}: ${import_picocolors14.default.bold(release3.status)}`);
46088
46349
  if (openCode) out.line(openCode);
46089
46350
  if (play2) out.line(` ${play2}`);
46090
46351
  if (next) out.line(` Next: ${next}`);
@@ -46163,10 +46424,10 @@ function repoLine(repo) {
46163
46424
  function homeLine(github) {
46164
46425
  if (!github) return "Home: GitHub";
46165
46426
  const seen = github.private ? " (private)" : "";
46166
- return `Home: GitHub, ${plain(github.full_name)}${seen} \xB7 ${import_picocolors13.default.dim(plain(github.html_url))}`;
46427
+ return `Home: GitHub, ${plain(github.full_name)}${seen} \xB7 ${import_picocolors14.default.dim(plain(github.html_url))}`;
46167
46428
  }
46168
46429
  function showReview({ out }, project2, review) {
46169
- out.line(`Release ${review.version}: ${import_picocolors13.default.bold(review.status)}`);
46430
+ out.line(`Release ${review.version}: ${import_picocolors14.default.bold(review.status)}`);
46170
46431
  const ours = ["draft", "testing", "rejected", "checking", "cloud_review", "in_review"].includes(
46171
46432
  review.status
46172
46433
  );
@@ -46185,7 +46446,7 @@ function showReview({ out }, project2, review) {
46185
46446
  for (const finding of run3.findings) {
46186
46447
  const where = finding.path ? ` ${finding.path}:` : "";
46187
46448
  out.line(` ${finding.severity} ${finding.rule_id}${where} ${finding.message}`);
46188
- if (finding.suggestion) out.line(` ${import_picocolors13.default.dim(finding.suggestion)}`);
46449
+ if (finding.suggestion) out.line(` ${import_picocolors14.default.dim(finding.suggestion)}`);
46189
46450
  }
46190
46451
  }
46191
46452
  if (review.test) out.line(` Test: ${review.test.state}`);
@@ -46252,7 +46513,7 @@ async function set2(ctx, ref, options) {
46252
46513
  const whoCanSee = repo.visibility === "private" ? { who_can_see: PRIVATE_REPO } : {};
46253
46514
  const said2 = { project: handle3(project2), ...repo, ...licence ? { licence } : {}, ...whoCanSee };
46254
46515
  out.result(said2, () => {
46255
- out.line(`Updated ${import_picocolors13.default.bold(handle3(project2))}.`);
46516
+ out.line(`Updated ${import_picocolors14.default.bold(handle3(project2))}.`);
46256
46517
  out.line(` Visibility: ${repo.visibility}`);
46257
46518
  if (repo.visibility === "private") out.line(` ${PRIVATE_REPO}`);
46258
46519
  out.line(` Training: ${repo.training_opt_out ? "off" : "on"}`);
@@ -46260,7 +46521,7 @@ async function set2(ctx, ref, options) {
46260
46521
  if (licence) {
46261
46522
  out.line(` Licence: ${licenceName(licence)}`);
46262
46523
  out.line(
46263
- import_picocolors13.default.dim(" Patch lists it now. Commit a LICENSE file to match: that's in your hands.")
46524
+ import_picocolors14.default.dim(" Patch lists it now. Commit a LICENSE file to match: that's in your hands.")
46264
46525
  );
46265
46526
  }
46266
46527
  });
@@ -46328,7 +46589,7 @@ async function connect(ctx, ref, options) {
46328
46589
  out.result(
46329
46590
  { project: handle4(project2), github: shown, already: !posted, patch_json: written, url: url2 },
46330
46591
  () => {
46331
- const name = import_picocolors14.default.bold(handle4(project2));
46592
+ const name = import_picocolors15.default.bold(handle4(project2));
46332
46593
  const repoName = plain(repo.full_name);
46333
46594
  out.line(
46334
46595
  posted ? `Connected ${repoName} on GitHub to ${name}. Its home is GitHub: Patch keeps none of its files until you release one.` : `${repoName} on GitHub is connected to ${name} already.`
@@ -46338,7 +46599,7 @@ async function connect(ctx, ref, options) {
46338
46599
  out.line(
46339
46600
  "Next: pch publish tags a commit on GitHub with your own git login, and releases it."
46340
46601
  );
46341
- out.line(import_picocolors14.default.dim(url2));
46602
+ out.line(import_picocolors15.default.dim(url2));
46342
46603
  }
46343
46604
  );
46344
46605
  }
@@ -46756,7 +47017,7 @@ async function nameInManifest(manifest, project2) {
46756
47017
  }
46757
47018
 
46758
47019
  // src/commands/covers.ts
46759
- var import_picocolors15 = __toESM(require_picocolors(), 1);
47020
+ var import_picocolors16 = __toESM(require_picocolors(), 1);
46760
47021
  import { mkdir as mkdir8, readFile as readFile13, writeFile as writeFile11 } from "node:fs/promises";
46761
47022
  import { basename as basename5, extname as extname3, join as join16, resolve as resolve12 } from "node:path";
46762
47023
  import { setTimeout as sleep7 } from "node:timers/promises";
@@ -46924,7 +47185,7 @@ async function call(ctx, method, path2, body) {
46924
47185
  var coversPath = (project2) => `/projects/${project2.owner}/${project2.slug}/covers`;
46925
47186
  function freeLine(allowance) {
46926
47187
  const left = `${allowance.takes_left} of ${allowance.takes_per_game} free takes and ${allowance.fixes_left} of ${allowance.fixes_per_game} fixes left for this game`;
46927
- return `${import_picocolors15.default.green("Free")}: Patch pays for covers, and they cost you nothing. ${left}.`;
47188
+ return `${import_picocolors16.default.green("Free")}: Patch pays for covers, and they cost you nothing. ${left}.`;
46928
47189
  }
46929
47190
  async function uploadStill(ctx, project2, file2) {
46930
47191
  const absolute = resolve12(file2);
@@ -47003,16 +47264,16 @@ async function save(ctx, job, folder) {
47003
47264
  }
47004
47265
  function show2(ctx, job, saved) {
47005
47266
  ctx.out.result({ ...job, saved }, () => {
47006
- ctx.out.line(`${import_picocolors15.default.bold(job.project)}: ${job.kind === "fix" ? "a fix" : "covers"} (${job.id})`);
47267
+ ctx.out.line(`${import_picocolors16.default.bold(job.project)}: ${job.kind === "fix" ? "a fix" : "covers"} (${job.id})`);
47007
47268
  for (const take of job.takes) {
47008
47269
  const file2 = saved.find((entry) => entry.take === take.id)?.file;
47009
- const state = take.state === "ready" ? import_picocolors15.default.green("ready") : take.state === "failed" ? import_picocolors15.default.red(`failed: ${take.error ?? "no reason given"}`) : import_picocolors15.default.yellow("making");
47010
- const set3 = take.set_as ? import_picocolors15.default.cyan(` set as the ${take.set_as.replace("_", " ")}`) : "";
47270
+ const state = take.state === "ready" ? import_picocolors16.default.green("ready") : take.state === "failed" ? import_picocolors16.default.red(`failed: ${take.error ?? "no reason given"}`) : import_picocolors16.default.yellow("making");
47271
+ const set3 = take.set_as ? import_picocolors16.default.cyan(` set as the ${take.set_as.replace("_", " ")}`) : "";
47011
47272
  ctx.out.line(
47012
- ` ${take.shape.padEnd(4)} ${take.id} ${state}${set3}${file2 ? import_picocolors15.default.dim(` ${file2}`) : ""}`
47273
+ ` ${take.shape.padEnd(4)} ${take.id} ${state}${set3}${file2 ? import_picocolors16.default.dim(` ${file2}`) : ""}`
47013
47274
  );
47014
47275
  }
47015
- if (job.next) ctx.out.line(import_picocolors15.default.dim(job.next));
47276
+ if (job.next) ctx.out.line(import_picocolors16.default.dim(job.next));
47016
47277
  ctx.out.line(freeLine(job.allowance));
47017
47278
  });
47018
47279
  if (job.takes.length && job.takes.every((take) => take.state === "failed")) {
@@ -47087,9 +47348,9 @@ async function coversSet(ctx, ref, take, options) {
47087
47348
  const answer = await call(ctx, "POST", `${coversPath(project2)}/takes/${take}/set`, { as: options.as });
47088
47349
  ctx.out.result(answer, () => {
47089
47350
  ctx.out.line(
47090
- `${import_picocolors15.default.green(`Set the ${answer.media.kind.replace("_", " ")}`)} of ${import_picocolors15.default.bold(ref)}: ${answer.media.status}.`
47351
+ `${import_picocolors16.default.green(`Set the ${answer.media.kind.replace("_", " ")}`)} of ${import_picocolors16.default.bold(ref)}: ${answer.media.status}.`
47091
47352
  );
47092
- ctx.out.line(import_picocolors15.default.dim("It shows on the game's page once it's ready, in a few seconds."));
47353
+ ctx.out.line(import_picocolors16.default.dim("It shows on the game's page once it's ready, in a few seconds."));
47093
47354
  ctx.out.line(freeLine(answer.allowance));
47094
47355
  });
47095
47356
  return EXIT.ok;
@@ -47101,7 +47362,7 @@ function outFolder(out, byDefault) {
47101
47362
  }
47102
47363
 
47103
47364
  // src/commands/feedback.ts
47104
- var import_picocolors16 = __toESM(require_picocolors(), 1);
47365
+ var import_picocolors17 = __toESM(require_picocolors(), 1);
47105
47366
  var FEEDBACK_KINDS = ["failed", "bug", "call_error", "unhelpful", "idea"];
47106
47367
  var FEEDBACK_SEVERITIES = ["blocked", "slowed", "annoyed"];
47107
47368
  async function feedbackSend(ctx, summary, options) {
@@ -47131,7 +47392,7 @@ async function feedbackSend(ctx, summary, options) {
47131
47392
  const shown = { ...sent, next: sent.next.replaceAll("send_feedback", "pch feedback send") };
47132
47393
  ctx.out.result(shown, () => {
47133
47394
  ctx.out.line(
47134
- `${options.addTo ? "Added to your report" : "Told Patch's team"}: ${import_picocolors16.default.bold(plain(sent.summary))}`
47395
+ `${options.addTo ? "Added to your report" : "Told Patch's team"}: ${import_picocolors17.default.bold(plain(sent.summary))}`
47135
47396
  );
47136
47397
  if (sent.known) {
47137
47398
  ctx.out.line(`Patch's team already answered a report like it: ${plain(sent.known.answer)}`);
@@ -47140,12 +47401,12 @@ async function feedbackSend(ctx, summary, options) {
47140
47401
  const flag = sent.follow_up.field.replaceAll("_", "-");
47141
47402
  ctx.out.line(`One more thing would help most: ${plain(sent.follow_up.question)}`);
47142
47403
  ctx.out.line(
47143
- import_picocolors16.default.dim(
47404
+ import_picocolors17.default.dim(
47144
47405
  ` pch feedback send --kind ${sent.kind} --add-to ${sent.id} --${flag} "..." "${plain(sent.summary)}"`
47145
47406
  )
47146
47407
  );
47147
47408
  } else {
47148
- ctx.out.line(import_picocolors16.default.dim(plain(shown.next)));
47409
+ ctx.out.line(import_picocolors17.default.dim(plain(shown.next)));
47149
47410
  }
47150
47411
  });
47151
47412
  }
@@ -47159,24 +47420,24 @@ async function feedbackList(ctx) {
47159
47420
  }
47160
47421
  for (const report of data) {
47161
47422
  ctx.out.line(
47162
- `${import_picocolors16.default.dim(report.created_at.slice(0, 10))} ${statusWord(report.status)} ${plain(report.summary)}`
47423
+ `${import_picocolors17.default.dim(report.created_at.slice(0, 10))} ${statusWord(report.status)} ${plain(report.summary)}`
47163
47424
  );
47164
- if (report.answer) ctx.out.line(` ${import_picocolors16.default.dim("Patch:")} ${plain(report.answer)}`);
47425
+ if (report.answer) ctx.out.line(` ${import_picocolors17.default.dim("Patch:")} ${plain(report.answer)}`);
47165
47426
  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} ...`));
47427
+ ctx.out.line(import_picocolors17.default.dim(` Add to it: pch feedback send --add-to ${report.id} ...`));
47167
47428
  }
47168
47429
  }
47169
47430
  });
47170
47431
  }
47171
47432
  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 ");
47433
+ if (status2 === "fixed") return import_picocolors17.default.green("fixed ");
47434
+ if (status2 === "planned") return import_picocolors17.default.cyan("planned ");
47435
+ if (status2 === "declined") return import_picocolors17.default.dim("declined");
47436
+ return import_picocolors17.default.yellow("open ");
47176
47437
  }
47177
47438
 
47178
47439
  // src/commands/git.ts
47179
- var import_picocolors17 = __toESM(require_picocolors(), 1);
47440
+ var import_picocolors18 = __toESM(require_picocolors(), 1);
47180
47441
  import { stat as stat5 } from "node:fs/promises";
47181
47442
  import { basename as basename6, join as join17, resolve as resolve13 } from "node:path";
47182
47443
 
@@ -47911,8 +48172,8 @@ async function push(ctx, given) {
47911
48172
  },
47912
48173
  () => {
47913
48174
  const list = sent.map(({ ref, status: status2 }) => `${shortRef(ref)}${status2 === "deleted" ? " (deleted)" : ""}`).join(", ");
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)}.`);
48175
+ if (list) out.line(`${dryRun ? "Would push" : "Pushed"} ${list} to ${import_picocolors18.default.bold(repo)}.`);
48176
+ else if (!quiet) out.line(`Nothing new to push to ${import_picocolors18.default.bold(repo)}.`);
47916
48177
  if (pushed.notice) out.line(pushed.notice);
47917
48178
  }
47918
48179
  );
@@ -47959,10 +48220,10 @@ async function clone2(ctx, ref, dir) {
47959
48220
  brief: brief ? BRIEF_FILE : null
47960
48221
  },
47961
48222
  () => {
47962
- out.line(`Cloned ${import_picocolors17.default.bold(`${project2.owner}/${project2.slug}`)} into ${basename6(into)}`);
48223
+ out.line(`Cloned ${import_picocolors18.default.bold(`${project2.owner}/${project2.slug}`)} into ${basename6(into)}`);
47963
48224
  if (commitsAs) out.line(saidCommitsAs(commitsAs));
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."));
48225
+ out.line(import_picocolors18.default.dim("Commit and push with plain git."));
48226
+ if (!guide2) out.line(import_picocolors18.default.dim("It has no guide for coding agents: pch agent guide adds one."));
47966
48227
  if (brief) {
47967
48228
  out.line(
47968
48229
  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.`
@@ -47973,7 +48234,7 @@ async function clone2(ctx, ref, dir) {
47973
48234
  }
47974
48235
 
47975
48236
  // src/commands/media.ts
47976
- var import_picocolors18 = __toESM(require_picocolors(), 1);
48237
+ var import_picocolors19 = __toESM(require_picocolors(), 1);
47977
48238
  import { readFile as readFile14 } from "node:fs/promises";
47978
48239
  import { basename as basename7, extname as extname4, resolve as resolve14 } from "node:path";
47979
48240
  import { setTimeout as sleep8 } from "node:timers/promises";
@@ -48069,11 +48330,11 @@ function showAdded(ctx, ref, media2) {
48069
48330
  );
48070
48331
  }
48071
48332
  ctx.out.result(media2, () => {
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));
48333
+ ctx.out.line(`${import_picocolors19.default.green(`Added the ${media2.kind}`)} to ${import_picocolors19.default.bold(ref)}: ${media2.status}.`);
48334
+ if (media2.status === "ready") ctx.out.line(import_picocolors19.default.dim(media2.url));
48074
48335
  else {
48075
48336
  ctx.out.line(
48076
- import_picocolors18.default.dim(
48337
+ import_picocolors19.default.dim(
48077
48338
  `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}.`
48078
48339
  )
48079
48340
  );
@@ -48093,7 +48354,7 @@ async function mediaList(ctx, ref) {
48093
48354
  for (const entry of media2.data) {
48094
48355
  const failed3 = entry.status === "failed" && entry.error ? `: ${entry.error}` : "";
48095
48356
  ctx.out.line(
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}`) : ""}`
48357
+ `${entry.id} ${entry.kind.padEnd(10)} ${entry.status.padEnd(10)} ${formatSize(entry.size).padStart(8)}${failed3}${entry.alt ? import_picocolors19.default.dim(` ${entry.alt}`) : ""}`
48097
48358
  );
48098
48359
  }
48099
48360
  });
@@ -48121,7 +48382,7 @@ async function mediaOrder(ctx, ref, ids) {
48121
48382
  }
48122
48383
 
48123
48384
  // src/commands/publish.ts
48124
- var import_picocolors19 = __toESM(require_picocolors(), 1);
48385
+ var import_picocolors20 = __toESM(require_picocolors(), 1);
48125
48386
  var import_semver2 = __toESM(require_semver2(), 1);
48126
48387
  import { open as open2, readFile as readFile15 } from "node:fs/promises";
48127
48388
  init_errors();
@@ -48204,21 +48465,21 @@ async function publish(ctx, dir, options) {
48204
48465
  out.step("Submitting");
48205
48466
  const name = releaseName({ ...ref, version: version2 });
48206
48467
  const submitted = await submit(ctx, { ...ref, version: version2 }).catch(draftUntilTested);
48207
- const release2 = await unwrap(
48468
+ const release3 = await unwrap(
48208
48469
  api.GET("/projects/{owner}/{slug}/releases/{version}", { params: { path: path2 } })
48209
48470
  );
48210
48471
  const url2 = projectUrl(ctx.target.origin, ref);
48211
48472
  const branch = await source?.bringBranch();
48212
48473
  const branchSaid = branch ? { branch } : {};
48213
48474
  const next = nextStep(name, submitted?.status ?? "draft", null).command;
48214
- const openCode = await openCodeOf(ctx, ref, release2);
48475
+ const openCode = await openCodeOf(ctx, ref, release3);
48215
48476
  const whoCanSee = whoCanSeeIt(
48216
48477
  // A draft is private whichever channel it's for, as the words for a person say.
48217
- !submitted || release2.channel === "private" ? privateWords(release2.kind, !submitted) : null,
48478
+ !submitted || release3.channel === "private" ? privateWords(release3.kind, !submitted) : null,
48218
48479
  openCode
48219
48480
  );
48220
48481
  const result = {
48221
- release: release2,
48482
+ release: release3,
48222
48483
  uploaded: draft.uploads.length,
48223
48484
  url: url2,
48224
48485
  next,
@@ -48228,25 +48489,25 @@ async function publish(ctx, dir, options) {
48228
48489
  out.result(result, () => {
48229
48490
  if (!submitted) {
48230
48491
  out.line(
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.`
48492
+ `Uploaded ${import_picocolors20.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.`
48232
48493
  );
48233
- out.line(privateWords(release2.kind, true));
48494
+ out.line(privateWords(release3.kind, true));
48234
48495
  if (openCode) out.line(openCode);
48235
48496
  if (source) out.line(fromCommit(source));
48236
48497
  if (branch?.say) out.line(branch.say);
48237
48498
  out.line(`Next: ${next}`);
48238
48499
  return;
48239
48500
  }
48240
- showSubmitted(ctx, { ...ref, version: version2 }, submitted.status, release2);
48501
+ showSubmitted(ctx, { ...ref, version: version2 }, submitted.status, release3);
48241
48502
  if (openCode) out.line(openCode);
48242
48503
  if (source) out.line(fromCommit(source));
48243
48504
  if (branch?.say) out.line(branch.say);
48244
- for (const entry of release2.files) {
48505
+ for (const entry of release3.files) {
48245
48506
  out.line(
48246
48507
  ` ${entry.path.padEnd(40)} ${formatSize(entry.size).padStart(8)} sha256 ${entry.sha256}`
48247
48508
  );
48248
48509
  }
48249
- out.line(import_picocolors19.default.dim(url2));
48510
+ out.line(import_picocolors20.default.dim(url2));
48250
48511
  });
48251
48512
  }
48252
48513
  function fromCommit(source) {
@@ -48255,36 +48516,36 @@ function fromCommit(source) {
48255
48516
  }
48256
48517
  async function releaseSubmit(ctx, ref) {
48257
48518
  requireToken(ctx.target);
48258
- const release2 = parseReleaseRef(ref);
48259
- const submitted = await submit(ctx, release2);
48260
- const next = nextStep(releaseName(release2), submitted.status, null).command;
48261
- const openCode = await openCodeOf(ctx, release2, submitted);
48519
+ const release3 = parseReleaseRef(ref);
48520
+ const submitted = await submit(ctx, release3);
48521
+ const next = nextStep(releaseName(release3), submitted.status, null).command;
48522
+ const openCode = await openCodeOf(ctx, release3, submitted);
48262
48523
  const whoCanSee = whoCanSeeIt(
48263
48524
  submitted.channel === "private" && submitted.status === "published" ? privateWords(submitted.kind, false) : null,
48264
48525
  openCode
48265
48526
  );
48266
48527
  ctx.out.result({ ...submitted, next, ...whoCanSee }, () => {
48267
- showSubmitted(ctx, release2, submitted.status, submitted);
48528
+ showSubmitted(ctx, release3, submitted.status, submitted);
48268
48529
  if (openCode) ctx.out.line(openCode);
48269
- ctx.out.line(import_picocolors19.default.dim(projectUrl(ctx.target.origin, release2)));
48530
+ ctx.out.line(import_picocolors20.default.dim(projectUrl(ctx.target.origin, release3)));
48270
48531
  });
48271
48532
  }
48272
48533
  async function releasePromote(ctx, ref, options) {
48273
48534
  requireToken(ctx.target);
48274
48535
  const from = parseReleaseRef(ref);
48275
48536
  const project2 = { owner: from.owner, slug: from.slug };
48276
- const release2 = await unwrap(
48537
+ const release3 = await unwrap(
48277
48538
  ctx.api.GET("/projects/{owner}/{slug}/releases/{version}", { params: { path: from } })
48278
48539
  );
48279
- if (release2.status !== "published") {
48280
- const next = nextStep(ref, release2.status, null);
48540
+ if (release3.status !== "published") {
48541
+ const next = nextStep(ref, release3.status, null);
48281
48542
  throw new CliError(
48282
48543
  EXIT.conflict,
48283
48544
  "not_published",
48284
- `${ref} is ${release2.status}, and only a published release can move to another channel. ${next.say}${next.command ? ` Next: ${next.command}` : ""} To release it elsewhere instead, set "channel" in patch.json, move the version on, and run pch publish.`
48545
+ `${ref} is ${release3.status}, and only a published release can move to another channel. ${next.say}${next.command ? ` Next: ${next.command}` : ""} To release it elsewhere instead, set "channel" in patch.json, move the version on, and run pch publish.`
48285
48546
  );
48286
48547
  }
48287
- if (release2.channel === options.channel) {
48548
+ if (release3.channel === options.channel) {
48288
48549
  throw new CliError(
48289
48550
  EXIT.conflict,
48290
48551
  "already_on_channel",
@@ -48298,10 +48559,10 @@ async function releasePromote(ctx, ref, options) {
48298
48559
  body: {
48299
48560
  version: version2,
48300
48561
  channel: options.channel,
48301
- changelog: options.changelog ?? release2.changelog ?? void 0,
48302
- ...release2.recipe ? { recipe: release2.recipe } : {},
48303
- ...release2.commit ? { commit: release2.commit } : {},
48304
- files: release2.files.map(({ path: path2, sha256, size }) => ({ path: path2, sha256, size }))
48562
+ changelog: options.changelog ?? release3.changelog ?? void 0,
48563
+ ...release3.recipe ? { recipe: release3.recipe } : {},
48564
+ ...release3.commit ? { commit: release3.commit } : {},
48565
+ files: release3.files.map(({ path: path2, sha256, size }) => ({ path: path2, sha256, size }))
48305
48566
  }
48306
48567
  })
48307
48568
  );
@@ -48320,7 +48581,7 @@ async function releasePromote(ctx, ref, options) {
48320
48581
  const next = nextStep(name, "draft", null).command;
48321
48582
  ctx.out.result({ from: ref, release: { version: version2, status: "draft" }, url: url2, next }, () => {
48322
48583
  ctx.out.line(
48323
- `Made ${import_picocolors19.default.bold(name)} from ${ref}, as a draft for ${options.channel}. It needs a passed test of its own.`
48584
+ `Made ${import_picocolors20.default.bold(name)} from ${ref}, as a draft for ${options.channel}. It needs a passed test of its own.`
48324
48585
  );
48325
48586
  ctx.out.line(`Next: ${next}`);
48326
48587
  });
@@ -48328,7 +48589,7 @@ async function releasePromote(ctx, ref, options) {
48328
48589
  }
48329
48590
  ctx.out.result({ from: ref, release: submitted, url: url2 }, () => {
48330
48591
  showSubmitted(ctx, to, submitted.status, submitted);
48331
- ctx.out.line(import_picocolors19.default.dim(url2));
48592
+ ctx.out.line(import_picocolors20.default.dim(url2));
48332
48593
  });
48333
48594
  }
48334
48595
  function draftUntilTested(error62) {
@@ -48344,7 +48605,7 @@ async function nextVersion(ctx, project2) {
48344
48605
  params: { path: project2, query: { limit: 100, cursor } }
48345
48606
  })
48346
48607
  );
48347
- versions.push(...page.data.map((release2) => release2.version));
48608
+ versions.push(...page.data.map((release3) => release3.version));
48348
48609
  cursor = page.next_cursor ?? void 0;
48349
48610
  } while (cursor);
48350
48611
  const newest2 = import_semver2.default.rsort(versions.filter((version2) => import_semver2.default.valid(version2)))[0];
@@ -48354,9 +48615,9 @@ function privateWords(kind, draft) {
48354
48615
  if (!onGameHost(kind)) return "Only your team can find it.";
48355
48616
  return draft ? PRIVATE_DRAFT : PRIVATE_GAME;
48356
48617
  }
48357
- async function openCodeOf(ctx, { owner, slug: slug2 }, release2) {
48618
+ async function openCodeOf(ctx, { owner, slug: slug2 }, release3) {
48358
48619
  const repo = await ctx.api.GET("/projects/{owner}/{slug}/repo", { params: { path: { owner, slug: slug2 } } }).catch(() => null);
48359
- return openCodeWords(`${owner}/${slug2}`, repo?.data, release2);
48620
+ return openCodeWords(`${owner}/${slug2}`, repo?.data, release3);
48360
48621
  }
48361
48622
  function whoCanSeeIt(...words2) {
48362
48623
  const said2 = words2.filter(Boolean).join(" ");
@@ -48365,7 +48626,7 @@ function whoCanSeeIt(...words2) {
48365
48626
  function showSubmitted(ctx, ref, status2, { channel, kind }) {
48366
48627
  const name = releaseName(ref);
48367
48628
  if (status2 === "published") {
48368
- ctx.out.line(import_picocolors19.default.green(`Published ${name} to ${channel}`));
48629
+ ctx.out.line(import_picocolors20.default.green(`Published ${name} to ${channel}`));
48369
48630
  if (channel === "private") {
48370
48631
  ctx.out.line(privateWords(kind, false));
48371
48632
  ctx.out.line(`pch status ${name} says whether your team can play it yet, and where.`);
@@ -48519,7 +48780,7 @@ async function uploadFailed(file2, upload, response) {
48519
48780
  }
48520
48781
 
48521
48782
  // src/commands/release-test.ts
48522
- var import_picocolors20 = __toESM(require_picocolors(), 1);
48783
+ var import_picocolors21 = __toESM(require_picocolors(), 1);
48523
48784
  import { createHash as createHash3 } from "node:crypto";
48524
48785
  import { mkdir as mkdir9, mkdtemp as mkdtemp3, writeFile as writeFile12 } from "node:fs/promises";
48525
48786
  import { tmpdir as tmpdir3 } from "node:os";
@@ -48531,23 +48792,23 @@ var ASK_EVERY_MS2 = 5e3;
48531
48792
  var OPEN = /* @__PURE__ */ new Set(["waiting", "running", "recorded"]);
48532
48793
  var GOING = /* @__PURE__ */ new Set(["waiting", "running"]);
48533
48794
  var REVIEW_ROUTE = "/projects/{owner}/{slug}/releases/{version}/review";
48534
- var reviewOf = (ctx, release2) => ctx.api.GET(REVIEW_ROUTE, { params: { path: release2 } });
48795
+ var reviewOf = (ctx, release3) => ctx.api.GET(REVIEW_ROUTE, { params: { path: release3 } });
48535
48796
  async function testStart(ctx, ref, options) {
48536
48797
  requireToken(ctx.target);
48537
- const release2 = parseReleaseRef(ref);
48798
+ const release3 = parseReleaseRef(ref);
48538
48799
  const wait2 = waitOf(options.wait);
48539
- const before = await reviewOf(ctx, release2);
48800
+ const before = await reviewOf(ctx, release3);
48540
48801
  const open3 = before.data?.test && OPEN.has(before.data.test.state) ? before.data.test : null;
48541
- const test2 = open3 ?? await startOne(ctx, release2, before.data?.status);
48542
- return show3(ctx, release2, wait2 === null ? test2 : await follow3(ctx, test2, wait2), {
48802
+ const test2 = open3 ?? await startOne(ctx, release3, before.data?.status);
48803
+ return show3(ctx, release3, wait2 === null ? test2 : await follow3(ctx, test2, wait2), {
48543
48804
  started: !open3,
48544
48805
  followed: wait2 !== null
48545
48806
  });
48546
48807
  }
48547
- async function startOne(ctx, release2, status2) {
48548
- const name = releaseName(release2);
48808
+ async function startOne(ctx, release3, status2) {
48809
+ const name = releaseName(release3);
48549
48810
  const answer = await ctx.api.POST("/projects/{owner}/{slug}/releases/{version}/tests", {
48550
- params: { path: release2 }
48811
+ params: { path: release3 }
48551
48812
  });
48552
48813
  if (answer.response.ok) return answer.data;
48553
48814
  const failure2 = fromResponse(answer.response, answer.error);
@@ -48577,17 +48838,17 @@ async function startOne(ctx, release2, status2) {
48577
48838
  }
48578
48839
  async function testView(ctx, ref, options) {
48579
48840
  requireToken(ctx.target);
48580
- const release2 = parseReleaseRef(ref);
48841
+ const release3 = parseReleaseRef(ref);
48581
48842
  const wait2 = waitOf(options.wait);
48582
- const test2 = await newest(ctx, release2);
48583
- return show3(ctx, release2, wait2 === null ? test2 : await follow3(ctx, test2, wait2), {
48843
+ const test2 = await newest(ctx, release3);
48844
+ return show3(ctx, release3, wait2 === null ? test2 : await follow3(ctx, test2, wait2), {
48584
48845
  followed: wait2 !== null
48585
48846
  });
48586
48847
  }
48587
- async function newest(ctx, release2) {
48588
- const review = await unwrap(reviewOf(ctx, release2));
48848
+ async function newest(ctx, release3) {
48849
+ const review = await unwrap(reviewOf(ctx, release3));
48589
48850
  if (review.test) return review.test;
48590
- const name = releaseName(release2);
48851
+ const name = releaseName(release3);
48591
48852
  const next = `pch test start ${name}`;
48592
48853
  throw new CliError(EXIT.error, "not_found", `${name} has no test yet. Start one: ${next}`, {
48593
48854
  next
@@ -48623,12 +48884,12 @@ function whatsHappening(test2) {
48623
48884
  if (test2.needs_person) return plain(test2.needs_person.ask);
48624
48885
  return "Waiting for you to press Start in Patch Desktop";
48625
48886
  }
48626
- async function show3(ctx, release2, test2, { followed, ...extra } = {}) {
48887
+ async function show3(ctx, release3, test2, { followed, ...extra } = {}) {
48627
48888
  const { out } = ctx;
48628
- const name = releaseName(release2);
48889
+ const name = releaseName(release3);
48629
48890
  const next = nextStep(name, test2.state === "failed" ? "rejected" : "testing", test2);
48630
48891
  const command = next.command;
48631
- const play2 = test2.state === "passed" ? await playFirst(ctx, release2) : null;
48892
+ const play2 = test2.state === "passed" ? await playFirst(ctx, release3) : null;
48632
48893
  const ask = test2.state === "waiting" ? askThePerson(test2) : play2;
48633
48894
  const noteAsk = test2.note_ask?.replaceAll("suggest_note", "pch note suggest");
48634
48895
  const feedbackAsk = test2.feedback_ask ? {
@@ -48643,7 +48904,7 @@ async function show3(ctx, release2, test2, { followed, ...extra } = {}) {
48643
48904
  ...feedbackAsk ? { feedback_ask: feedbackAsk } : {}
48644
48905
  };
48645
48906
  out.result({ ...shown, ...ask ? { ask } : {} }, () => {
48646
- const bold = import_picocolors20.default.bold(name);
48907
+ const bold = import_picocolors21.default.bold(name);
48647
48908
  if (test2.state === "waiting") {
48648
48909
  out.line(
48649
48910
  !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.`
@@ -48659,19 +48920,19 @@ async function show3(ctx, release2, test2, { followed, ...extra } = {}) {
48659
48920
  `${bold}: it has been played and recorded. Look at the footage, then send your review.`
48660
48921
  );
48661
48922
  } else if (test2.state === "passed") {
48662
- out.line(`${bold}: ${import_picocolors20.default.green("its test passed.")} It's still private.`);
48923
+ out.line(`${bold}: ${import_picocolors21.default.green("its test passed.")} It's still private.`);
48663
48924
  if (test2.review) out.line(plain(test2.review.summary));
48664
48925
  if (play2) out.line(play2);
48665
48926
  out.line(
48666
48927
  "Publish it once you're happy it's ready: that's your yes, and nothing does it for you."
48667
48928
  );
48668
48929
  } else if (test2.state === "failed") {
48669
- out.line(`${bold}: ${import_picocolors20.default.red("its test failed")}, so this version can't be published.`);
48930
+ out.line(`${bold}: ${import_picocolors21.default.red("its test failed")}, so this version can't be published.`);
48670
48931
  showWhatFailed(ctx, test2);
48671
48932
  out.line(`pch status ${name} says how to fix it.`);
48672
48933
  out.line("Then move the version on in patch.json and publish that.");
48673
48934
  } else if (test2.state === "couldnt_run") {
48674
- out.line(`${bold}: ${import_picocolors20.default.yellow("its test couldn't run.")}`);
48935
+ out.line(`${bold}: ${import_picocolors21.default.yellow("its test couldn't run.")}`);
48675
48936
  if (test2.outcome) out.line(plain(test2.outcome.reason));
48676
48937
  } else {
48677
48938
  out.line(`${bold}: its test was cancelled.`);
@@ -48683,7 +48944,7 @@ async function show3(ctx, release2, test2, { followed, ...extra } = {}) {
48683
48944
  }
48684
48945
  if (command) out.line(`Next: ${command}`);
48685
48946
  if (OPEN.has(test2.state) && !extra.started) {
48686
- out.line(import_picocolors20.default.dim(`To start over: pch test cancel ${name}, then pch test start ${name}`));
48947
+ out.line(import_picocolors21.default.dim(`To start over: pch test cancel ${name}, then pch test start ${name}`));
48687
48948
  }
48688
48949
  });
48689
48950
  if (test2.state === "failed") return EXIT.blocked;
@@ -48706,10 +48967,10 @@ function showWhatFailed({ out }, test2) {
48706
48967
  async function testFootage(ctx, ref, options) {
48707
48968
  requireToken(ctx.target);
48708
48969
  const { out } = ctx;
48709
- const release2 = parseReleaseRef(ref);
48710
- const name = releaseName(release2);
48970
+ const release3 = parseReleaseRef(ref);
48971
+ const name = releaseName(release3);
48711
48972
  const run3 = options.run === void 0 ? void 0 : positive(options.run, "--run");
48712
- const test2 = await newest(ctx, release2);
48973
+ const test2 = await newest(ctx, release3);
48713
48974
  const { data } = await unwrap(
48714
48975
  ctx.api.GET("/tests/{id}/footage", { params: { path: { id: test2.id }, query: { run: run3 } } })
48715
48976
  );
@@ -48720,7 +48981,7 @@ async function testFootage(ctx, ref, options) {
48720
48981
  const next = data.length ? reviewCommand(name) : nextStep(name, "testing", test2).command;
48721
48982
  out.result({ test: test2.id, state: test2.state, data: sheets, next }, () => {
48722
48983
  if (!sheets.length) {
48723
- out.line(`${import_picocolors20.default.bold(name)} has no footage yet: its test is ${test2.state.replace("_", " ")}.`);
48984
+ out.line(`${import_picocolors21.default.bold(name)} has no footage yet: its test is ${test2.state.replace("_", " ")}.`);
48724
48985
  for (const result of test2.results) {
48725
48986
  if (result.footage_error) out.line(plain(result.footage_error));
48726
48987
  }
@@ -48757,9 +49018,9 @@ async function saveSheets(sheets, folder) {
48757
49018
  }
48758
49019
  async function testReview(ctx, ref, options) {
48759
49020
  requireToken(ctx.target);
48760
- const release2 = parseReleaseRef(ref);
49021
+ const release3 = parseReleaseRef(ref);
48761
49022
  const moments = options.moment.map(parseMoment);
48762
- const test2 = await newest(ctx, release2);
49023
+ const test2 = await newest(ctx, release3);
48763
49024
  const answer = await ctx.api.POST("/tests/{id}/review", {
48764
49025
  params: { path: { id: test2.id } },
48765
49026
  body: {
@@ -48770,8 +49031,8 @@ async function testReview(ctx, ref, options) {
48770
49031
  moments
48771
49032
  }
48772
49033
  });
48773
- if (answer.response.ok) return show3(ctx, release2, answer.data);
48774
- throw withNext(fromResponse(answer.response, answer.error), release2, test2);
49034
+ if (answer.response.ok) return show3(ctx, release3, answer.data);
49035
+ throw withNext(fromResponse(answer.response, answer.error), release3, test2);
48775
49036
  }
48776
49037
  function parseMoment(text) {
48777
49038
  const match = /^(\d+)\s+(\d{1,3}:\d{2})\s+(\S.*)$/s.exec(text.trim());
@@ -48786,21 +49047,21 @@ function parseMoment(text) {
48786
49047
  }
48787
49048
  async function testCancel(ctx, ref) {
48788
49049
  requireToken(ctx.target);
48789
- const release2 = parseReleaseRef(ref);
48790
- const name = releaseName(release2);
48791
- const test2 = await newest(ctx, release2);
49050
+ const release3 = parseReleaseRef(ref);
49051
+ const name = releaseName(release3);
49052
+ const test2 = await newest(ctx, release3);
48792
49053
  const answer = await ctx.api.POST("/tests/{id}/cancel", { params: { path: { id: test2.id } } });
48793
49054
  if (!answer.response.ok) {
48794
- throw withNext(fromResponse(answer.response, answer.error), release2, test2);
49055
+ throw withNext(fromResponse(answer.response, answer.error), release3, test2);
48795
49056
  }
48796
49057
  const next = `pch test start ${name}`;
48797
49058
  ctx.out.result({ ...answer.data, next }, () => {
48798
- ctx.out.line(`Cancelled ${import_picocolors20.default.bold(name)}'s test. Start another: ${next}`);
49059
+ ctx.out.line(`Cancelled ${import_picocolors21.default.bold(name)}'s test. Start another: ${next}`);
48799
49060
  });
48800
49061
  }
48801
- function withNext(failure2, release2, test2) {
49062
+ function withNext(failure2, release3, test2) {
48802
49063
  if (failure2.code !== "test_state") return failure2;
48803
- const next = nextStep(releaseName(release2), "testing", test2);
49064
+ const next = nextStep(releaseName(release3), "testing", test2);
48804
49065
  return new CliError(failure2.exitCode, failure2.code, `${failure2.message}. ${sayNext(next)}`, {
48805
49066
  next: next.command
48806
49067
  });
@@ -48825,7 +49086,7 @@ async function testAutostart(ctx, state) {
48825
49086
  }
48826
49087
 
48827
49088
  // src/commands/search.ts
48828
- var import_picocolors21 = __toESM(require_picocolors(), 1);
49089
+ var import_picocolors22 = __toESM(require_picocolors(), 1);
48829
49090
  init_guide_words();
48830
49091
  var filter = (values) => values?.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean).join(",") || void 0;
48831
49092
  var licences = (values) => values.flatMap((value) => value.split(",")).map((value) => licenceId(value.trim()) ?? value.trim());
@@ -48863,13 +49124,13 @@ async function search(ctx, q, options) {
48863
49124
  out.result({ ...answer, ...aboutBlocks }, () => {
48864
49125
  if (!data.length) out.line("Nothing found.");
48865
49126
  for (const result of plain(data)) {
48866
- out.line(`${import_picocolors21.default.bold(result.handle)} ${import_picocolors21.default.dim(facts(result))}`);
49127
+ out.line(`${import_picocolors22.default.bold(result.handle)} ${import_picocolors22.default.dim(facts(result))}`);
48867
49128
  if (result.summary) out.line(` ${result.summary}`);
48868
49129
  if (result.demo) out.line(` Live demo: ${ctx.target.origin}/${result.owner}/${result.slug}`);
48869
- out.line(import_picocolors21.default.dim(` ${result.clone_url}`));
49130
+ out.line(import_picocolors22.default.dim(` ${result.clone_url}`));
48870
49131
  }
48871
49132
  if (blocks) out.line(BLOCK_WORDS);
48872
- if (cursor) out.line(import_picocolors21.default.dim("There are more; raise --limit."));
49133
+ if (cursor) out.line(import_picocolors22.default.dim("There are more; raise --limit."));
48873
49134
  });
48874
49135
  }
48875
49136
  function facts(result) {
@@ -48894,20 +49155,21 @@ async function categories(ctx, options) {
48894
49155
  const width = Math.max(0, ...data.map((category) => category.id.length));
48895
49156
  for (const category of data) {
48896
49157
  ctx.out.line(
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`}`)}`
49158
+ `${import_picocolors22.default.bold(category.id.padEnd(width))} ${category.name} ${import_picocolors22.default.dim(`\xB7 ${category.for.join(", ")} \xB7 ${category.projects === 1 ? "1 project" : `${category.projects} projects`}`)}`
48898
49159
  );
48899
49160
  }
48900
49161
  });
48901
49162
  }
48902
49163
 
48903
49164
  // src/commands/update.ts
48904
- var import_picocolors22 = __toESM(require_picocolors(), 1);
49165
+ var import_picocolors23 = __toESM(require_picocolors(), 1);
48905
49166
  var import_semver3 = __toESM(require_semver2(), 1);
48906
- import { spawnSync as spawnSync2 } from "node:child_process";
49167
+ import { spawnSync as spawnSync3 } from "node:child_process";
48907
49168
  import { createHash as createHash4 } from "node:crypto";
48908
49169
  import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
48909
49170
  import { chmod as chmod3, rename as rename2, rm as rm3, writeFile as writeFile13 } from "node:fs/promises";
48910
49171
  import { basename as basename8, dirname as dirname7, join as join19 } from "node:path";
49172
+ import { setTimeout as sleep10 } from "node:timers/promises";
48911
49173
  import { gunzipSync } from "node:zlib";
48912
49174
  init_errors();
48913
49175
  init_version();
@@ -49015,9 +49277,9 @@ async function update(ctx) {
49015
49277
  const fresh = `${target}.new`;
49016
49278
  await writeFile13(fresh, gunzipSync(packed), { mode: 493 });
49017
49279
  await chmod3(fresh, 493);
49018
- const said2 = spawnSync2(fresh, ["--version"], { encoding: "utf8", timeout: 6e4 });
49280
+ const said2 = spawnSync3(fresh, ["--version"], { encoding: "utf8", timeout: 6e4 });
49019
49281
  if (said2.status !== 0 || said2.stdout.trim() !== latest2) {
49020
- await rm3(fresh, { force: true });
49282
+ await whenFree(() => rm3(fresh, { force: true })).catch(() => void 0);
49021
49283
  throw new CliError(
49022
49284
  EXIT.error,
49023
49285
  "update_failed",
@@ -49037,12 +49299,23 @@ async function replace(target, fresh) {
49037
49299
  const old = `${target}.${process.pid}.old`;
49038
49300
  await rename2(target, old);
49039
49301
  try {
49040
- await rename2(fresh, target);
49302
+ await whenFree(() => rename2(fresh, target));
49041
49303
  } catch (error62) {
49042
49304
  await rename2(old, target);
49043
49305
  throw error62;
49044
49306
  }
49045
49307
  }
49308
+ async function whenFree(step, wait2 = 200) {
49309
+ for (let attempt = 1; ; attempt++) {
49310
+ try {
49311
+ return await step();
49312
+ } catch (error62) {
49313
+ const code = error62.code ?? "";
49314
+ if (attempt >= 10 || !["EBUSY", "EPERM", "EACCES"].includes(code)) throw error62;
49315
+ await sleep10(attempt * wait2);
49316
+ }
49317
+ }
49318
+ }
49046
49319
  function removeOldExecutables(target = process.execPath) {
49047
49320
  if (!isExecutable || process.platform !== "win32") return;
49048
49321
  const folder = dirname7(target);
@@ -49088,7 +49361,7 @@ function startUpdateNotice(ctx, command, now = Date.now()) {
49088
49361
  if (version2 && import_semver3.default.gt(version2, VERSION)) {
49089
49362
  process.stderr.write(
49090
49363
  `
49091
- ${import_picocolors22.default.dim("\u203A")} pch ${version2} is out (you have ${VERSION}). To update, run: ${how}
49364
+ ${import_picocolors23.default.dim("\u203A")} pch ${version2} is out (you have ${VERSION}). To update, run: ${how}
49092
49365
  `
49093
49366
  );
49094
49367
  }
@@ -49097,7 +49370,7 @@ ${import_picocolors22.default.dim("\u203A")} pch ${version2} is out (you have ${
49097
49370
  }
49098
49371
 
49099
49372
  // src/commands/validate.ts
49100
- var import_picocolors23 = __toESM(require_picocolors(), 1);
49373
+ var import_picocolors24 = __toESM(require_picocolors(), 1);
49101
49374
  init_errors();
49102
49375
  async function validate2(ctx, dir) {
49103
49376
  const project2 = await loadProject(dir);
@@ -49156,12 +49429,12 @@ async function validate2(ctx, dir) {
49156
49429
  notes
49157
49430
  };
49158
49431
  ctx.out.result(result, () => {
49159
- const mark = result.ok ? import_picocolors23.default.green("\u2713") : import_picocolors23.default.red("\u2717");
49432
+ const mark = result.ok ? import_picocolors24.default.green("\u2713") : import_picocolors24.default.red("\u2717");
49160
49433
  ctx.out.line(
49161
49434
  `${mark} ${file2.project}@${file2.version} \xB7 ${project2.kind} \xB7 ${result.trust_tier} \xB7 ${project2.files.length} files, ${formatSize(total)}`
49162
49435
  );
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)}`);
49436
+ for (const issue2 of issues) ctx.out.line(` ${import_picocolors24.default.red(issue2.path || "\xB7")} ${issue2.message}`);
49437
+ for (const note2 of notes) ctx.out.line(` ${import_picocolors24.default.dim(note2)}`);
49165
49438
  });
49166
49439
  return result.ok ? EXIT.ok : EXIT.usage;
49167
49440
  }
@@ -49605,10 +49878,10 @@ assets.command("add <ids...>").description(
49605
49878
  (ctx, ids, options) => assetsAdd(ctx, ids, options)
49606
49879
  )
49607
49880
  );
49608
- var release = program2.command("release").description("A project's releases");
49609
- release.command("list <project>").action(action((ctx, ref) => releaseList(ctx, ref)));
49610
- release.command("view <release>").description("owner/slug@version").action(action((ctx, ref) => releaseView(ctx, ref)));
49611
- release.command("submit <release>").description(
49881
+ var release2 = program2.command("release").description("A project's releases");
49882
+ release2.command("list <project>").action(action((ctx, ref) => releaseList(ctx, ref)));
49883
+ release2.command("view <release>").description("owner/slug@version").action(action((ctx, ref) => releaseView(ctx, ref)));
49884
+ release2.command("submit <release>").description(
49612
49885
  "Send a release on to be published, owner/slug@version: this is your yes. A browser game or a mod Patch installs has to have passed a test"
49613
49886
  ).action(action((ctx, ref) => releaseSubmit(ctx, ref)));
49614
49887
  var test = program2.command("test").description(
@@ -49655,7 +49928,7 @@ test.command("review <release>").description("Send your review of a test's foota
49655
49928
  )
49656
49929
  );
49657
49930
  test.command("cancel <release>").description("Cancel a release's test, if it hasn't been decided").action(action((ctx, ref) => testCancel(ctx, ref)));
49658
- release.command("promote <release>").description(
49931
+ release2.command("promote <release>").description(
49659
49932
  "Put owner/slug@version on another channel: a new version with the same files, which uploads nothing"
49660
49933
  ).addOption(
49661
49934
  new Option("--channel <channel>", `where it goes. ${NOT_LOCKED_HELP}`).choices(["private", "unlisted", "public"]).default("public")