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