@patchgg/cli 0.1.1 → 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 +61 -59
- package/dist/cli.js +293 -79
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,64 +29,66 @@ pch media add your-name/my-game cover.png --kind cover
|
|
|
29
29
|
pch project update your-name/my-game --summary "One line about it"
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
| Command
|
|
33
|
-
|
|
|
34
|
-
| `login [--scope …] [--no-browser]`
|
|
35
|
-
| `logout`
|
|
36
|
-
| `whoami`
|
|
37
|
-
| `guide`
|
|
38
|
-
| `mcp`
|
|
39
|
-
| `agent guide [dir]`
|
|
40
|
-
| `agent brief [dir]`
|
|
41
|
-
| `agent install [claude\|codex\|cursor]`
|
|
42
|
-
| `account list` / `account use <name>`
|
|
43
|
-
| `token create --name <n> [--scope …]` / `list` / `revoke`
|
|
44
|
-
| `search [query] [--kind …] [--category …] [--engine …] [--license …] [--limit <n>]`
|
|
45
|
-
| `categories [--for <kind>]`
|
|
46
|
-
| `assets search [query] [--type …] [--where …] [--license …] [--cap …] [--sort …]` / `assets facets` / `assets view <id>`
|
|
47
|
-
| `assets add <ids…> [--to <folder>]`
|
|
48
|
-
| `clone <owner/slug> [dir]`
|
|
49
|
-
| `push [remote] [refspec…]`
|
|
50
|
-
| `new <owner/slug> [dir] [--licence <id>] [--name …] [--summary …]`
|
|
51
|
-
| `remix <owner/slug> [--to <owner>] [--slug …] [--name …] [--visibility private]`
|
|
52
|
-
| `connect [owner/slug] [--to <owner>] [--slug …] [--name …] [--visibility …] [--wait]`
|
|
53
|
-
| `status [owner/slug[@version]] [--wait <seconds>]`
|
|
54
|
-
| `init [dir] [--project …] [--version …] [--files …] [--channel …] [--force]`
|
|
55
|
-
| `set <owner/slug> [--visibility …] [--licence <id>] [--training on\|off] [--change-requests on\|off]`
|
|
56
|
-
| `scan start <owner/slug> [--wait [seconds]]`
|
|
57
|
-
| `scan view <owner/slug> [--wait [seconds]]`
|
|
58
|
-
| `change-request open [owner/slug] --title … [--description …] [--from <owner/slug>] [--branch …] [--into …]`
|
|
59
|
-
| `change-request list <owner/slug> [--state …] [--limit <n>]`
|
|
60
|
-
| `change-request view <owner/slug#number>`
|
|
61
|
-
| `change-request review <owner/slug#number>`
|
|
62
|
-
| `change-request talk <owner/slug#number> <text>`
|
|
63
|
-
| `change-request merge <owner/slug#number> [--squash] [--message …]`
|
|
64
|
-
| `change-request decline <owner/slug#number>` / `withdraw <owner/slug#number>`
|
|
65
|
-
| `check [dir] [--serve] [--seconds …] [--out …] [--browser …] [--models [files…]]`
|
|
66
|
-
| `validate [dir]`
|
|
67
|
-
| `publish [dir] [--channel …] [--changelog …]`
|
|
68
|
-
| `test start <owner/slug@version> [--wait [seconds]]`
|
|
69
|
-
| `test autostart <on or off>`
|
|
70
|
-
| `test view <owner/slug@version> [--wait [seconds]]`
|
|
71
|
-
| `test footage <owner/slug@version> [--run <n>] [--out <dir>]`
|
|
72
|
-
| `test review <owner/slug@version> --verdict … --plays … --harmful … --summary … --moment …`
|
|
73
|
-
| `test cancel <owner/slug@version>`
|
|
74
|
-
| `project create\|view <owner/slug>` / `project list`
|
|
75
|
-
| `project update <owner/slug> [--name …] [--summary …] [--description …\|--description-file …] [--category …] [--link name=url]`
|
|
76
|
-
| `project list --mine`
|
|
77
|
-
| `media add <owner/slug> <file> --kind cover\|screenshot\|clip [--alt …] [--wait [seconds]]`
|
|
78
|
-
| `media list <owner/slug>` / `media remove <owner/slug> <id>` / `media order <owner/slug> <ids…>`
|
|
79
|
-
| `covers <owner/slug>`
|
|
80
|
-
| `covers make <owner/slug> --subject … [--shape …] [--still <file>…] [--screenshot <id>…] [--wait]`
|
|
81
|
-
| `covers view <owner/slug> <job>` / `covers fix <owner/slug> <take> <instruction>`
|
|
82
|
-
| `covers set <owner/slug> <take> --as cover\|tall_cover`
|
|
83
|
-
| `release list <owner/slug>` / `release view <owner/slug@version>`
|
|
84
|
-
| `release submit <owner/slug@version>`
|
|
85
|
-
| `release promote <owner/slug@version> [--channel …] [--as <version>]`
|
|
86
|
-
| `host-game search [q]` / `host-game view <slug>`
|
|
87
|
-
| `note suggest <about> <fact> [--link <url>] [--build <build>]`
|
|
88
|
-
|
|
89
|
-
|
|
32
|
+
| Command | Does |
|
|
33
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
34
|
+
| `login [--scope …] [--no-browser]` | Device flow, then saves an API key for this machine and [signs git in](#git). With `PCH_TOKEN` set, checks that key and signs git in, and saves nothing |
|
|
35
|
+
| `logout` | Revokes and forgets the active login, and signs git out of its git host unless another saved login is on it |
|
|
36
|
+
| `whoami` | Who you're signed in as, where, and with which scopes |
|
|
37
|
+
| `guide` | How to make, test and publish on Patch, signed in or out |
|
|
38
|
+
| `mcp` | Patch's MCP server for your agent, signed in as pch is (below) |
|
|
39
|
+
| `agent guide [dir]` | Writes `AGENTS.md` in a folder, for coding agents working there, as `new` and `init` do. A folder with its own guide is left alone |
|
|
40
|
+
| `agent brief [dir]` | Starts a game's `BRIEF.md` by asking you ([below](#your-agent)). It never overwrites one |
|
|
41
|
+
| `agent install [claude\|codex\|cursor]` | Adds `pch mcp` to an agent's MCP servers, or to every agent found on this PC ([below](#your-agent)) |
|
|
42
|
+
| `account list` / `account use <name>` | Saved logins |
|
|
43
|
+
| `token create --name <n> [--scope …]` / `list` / `revoke` | API keys for agents and CI (printed once). Making one takes approving a code in the browser, since keys can't make keys. Pushing with one takes the `publish` scope |
|
|
44
|
+
| `search [query] [--kind …] [--category …] [--engine …] [--license …] [--limit <n>]` | [Finds open repos](#search) by words and meaning, each with what it is, its licence, its latest release and its clone URL |
|
|
45
|
+
| `categories [--for <kind>]` | The categories `search --category` takes: games' and mods' by default, or a toolbox shelf's, each with how many projects are in it |
|
|
46
|
+
| `assets search [query] [--type …] [--where …] [--license …] [--cap …] [--sort …]` / `assets facets` / `assets view <id>` | Patch's asset library: 3D models, materials, HDRIs, sounds, music and sprites, each with its licence. Works signed out |
|
|
47
|
+
| `assets add <ids…> [--to <folder>]` | Copies assets into your game, each file checked against its SHA-256, with their licences in `LICENSES/`, and says how to load them. `--to` puts them straight in that folder under their own names. Their credits go in `CREDITS.md` and in the recipe in `patch.json`. Exit 3 if one can't be used, with alternatives |
|
|
48
|
+
| `clone <owner/slug> [dir]` | Clones a repo, with git sign-in and Git LFS set up for it |
|
|
49
|
+
| `push [remote] [refspec…]` | [Pushes to a repo on Patch at any size](#big-pushes), straight to where it's kept, where plain git brings up to 100 MB at a time. It takes git push's flags |
|
|
50
|
+
| `new <owner/slug> [dir] [--licence <id>] [--name …] [--summary …]` | Makes an [Open repo](#new-repos) with a README to fill in, its licence, big-file rules and ignores, and pushes its first commit |
|
|
51
|
+
| `remix <owner/slug> [--to <owner>] [--slug …] [--name …] [--visibility private]` | Makes a project from all of a repo (also `pch fork`), of yours or the org you name, which records its parent. It's as open as what it copies, unless `--visibility private` starts it private |
|
|
52
|
+
| `connect [owner/slug] [--to <owner>] [--slug …] [--name …] [--visibility …] [--wait]` | [Makes GitHub the home](#connecting-a-github-repo) of a new project, or the one you name, for the GitHub repo this folder is a clone of, and names it in `patch.json` |
|
|
53
|
+
| `status [owner/slug[@version]] [--wait <seconds>]` | Where a release is in review and whether it's live, with the repo's state and home: a project's latest release, the one you name, or with nothing named, the project this folder is |
|
|
54
|
+
| `init [dir] [--project …] [--version …] [--files …] [--channel …] [--force]` | Writes `patch.json`: the project named, or this repo's, or your name and this folder's. Finds a build folder with a page in it, and a `recipe.json`. `pch new` writes one too |
|
|
55
|
+
| `set <owner/slug> [--visibility …] [--licence <id>] [--training on\|off] [--change-requests on\|off]` | Changes who can find and read a repo, what Patch lists as its licence, and its switches |
|
|
56
|
+
| `scan start <owner/slug> [--wait [seconds]]` | [Reads a repo's whole history for secrets](#going-open), or joins the scan that's running; `--wait` follows it until it finishes, or for that many seconds |
|
|
57
|
+
| `scan view <owner/slug> [--wait [seconds]]` | The latest scan: what it found and how to take it out, and what going open publishes |
|
|
58
|
+
| `change-request open [owner/slug] --title … [--description …] [--from <owner/slug>] [--branch …] [--into …]` | Offers a remix's branch to the repo it came from. In a clone of the fork, it works out the fork, the branch and the repo. `cr` for short |
|
|
59
|
+
| `change-request list <owner/slug> [--state …] [--limit <n>]` | A repo's requests, newest first, saying which open ones are out of date |
|
|
60
|
+
| `change-request view <owner/slug#number>` | One request: what it is, whether it can merge, what it changes, and its talk |
|
|
61
|
+
| `change-request review <owner/slug#number>` | All of that, with its commits and patches |
|
|
62
|
+
| `change-request talk <owner/slug#number> <text>` | Posts in its talk, to ask for changes or to answer |
|
|
63
|
+
| `change-request merge <owner/slug#number> [--squash] [--message …]` | Merges it, once it's up to date |
|
|
64
|
+
| `change-request decline <owner/slug#number>` / `withdraw <owner/slug#number>` | Turns it down, as the repo's org, or takes it back, as whoever offers it |
|
|
65
|
+
| `check [dir] [--serve] [--seconds …] [--out …] [--browser …] [--models [files…]]` | [Sees a browser game run](#seeing-it-run) as Patch serves it, and says what it saw and couldn't. Advice, not a gate |
|
|
66
|
+
| `validate [dir]` | `patch.json`, its recipe and files locally, then the same recipe checked by Patch. For a [block](#blocks), its controls, presets and triggers, and its option pictures |
|
|
67
|
+
| `publish [dir] [--channel …] [--changelog …]` | Hash, tag the commit in a clone of the project's repo, upload only what Patch doesn't have, submit. [Most releases stop there as a private draft until they've passed a test](#from-upload-to-live) |
|
|
68
|
+
| `test start <owner/slug@version> [--wait [seconds]]` | [Starts a test on your PC](#from-upload-to-live), which waits for you to press Start in Patch Desktop (unless your tests start by themselves), or follows the one that's open |
|
|
69
|
+
| `test autostart <on or off>` | Whether your tests of browser games and blocks start in the person's Patch Desktop by themselves, with a notice each time. Ask them first, and change it only after a clear yes |
|
|
70
|
+
| `test view <owner/slug@version> [--wait [seconds]]` | Where a release's newest test has got to, and what to do next |
|
|
71
|
+
| `test footage <owner/slug@version> [--run <n>] [--out <dir>]` | A test's contact sheets, saved as pictures, in a new temporary folder unless `--out` names one, with their paths printed |
|
|
72
|
+
| `test review <owner/slug@version> --verdict … --plays … --harmful … --summary … --moment …` | Sends your review of the footage, which decides the test |
|
|
73
|
+
| `test cancel <owner/slug@version>` | Cancels a test that hasn't been decided |
|
|
74
|
+
| `project create\|view <owner/slug>` / `project list` | |
|
|
75
|
+
| `project update <owner/slug> [--name …] [--summary …] [--description …\|--description-file …] [--category …] [--link name=url]` | Changes a project's listing. Only what you name changes |
|
|
76
|
+
| `project list --mine` | Your projects, drafts included |
|
|
77
|
+
| `media add <owner/slug> <file> --kind cover\|screenshot\|clip [--alt …] [--wait [seconds]]` | Adds a picture or clip to a listing. It shows once Patch has made its copies, and a new cover takes the old one's place |
|
|
78
|
+
| `media list <owner/slug>` / `media remove <owner/slug> <id>` / `media order <owner/slug> <ids…>` | A listing's media, in order, with where each one stands |
|
|
79
|
+
| `covers <owner/slug>` | Free covers: the takes and fixes left, and the latest covers. Patch pays, up to 12 takes and 4 fixes a game once it's published. Only where Patch has turned them on (staging, for now) |
|
|
80
|
+
| `covers make <owner/slug> --subject … [--shape …] [--still <file>…] [--screenshot <id>…] [--wait]` | Titled covers, wide and tall, from 1 to 3 stills of the game, once the person agreed a direction. Uploads each `--still`; `--wait` follows and saves the takes |
|
|
81
|
+
| `covers view <owner/slug> <job>` / `covers fix <owner/slug> <take> <instruction>` | A job's takes, saved to look at; one new take, changed as you say |
|
|
82
|
+
| `covers set <owner/slug> <take> --as cover\|tall_cover` | Sets the take the person picked as the cover (a wide take) or the tall cover (a tall one) |
|
|
83
|
+
| `release list <owner/slug>` / `release view <owner/slug@version>` | A release's files. A library repo's are Patch's conversions, such as `web/` for browsers, each with a link that takes no login, and a `LICENSE` with their credit |
|
|
84
|
+
| `release submit <owner/slug@version>` | Sends a release on to be published, once it has passed its test: this is your yes |
|
|
85
|
+
| `release promote <owner/slug@version> [--channel …] [--as <version>]` | Puts a published release on another channel, public by default: a new version with the same files, recipe and commit, which uploads nothing |
|
|
86
|
+
| `host-game search [q]` / `host-game view <slug>` | Host games mods run in. Signed in, `view` adds their wiki pages; `view engines/<slug>` or `view methods/<slug>` gives such a page alone |
|
|
87
|
+
| `note suggest <about> <fact> [--link <url>] [--build <build>]` | Suggests one fact for Patch's wiki, about a game, `engines/<slug>` or `methods/<slug>`, with how it's known: a public page or the game build you saw it on. It only goes to a queue, and a person at Patch reads it before any agent does. Facts only, never about your own project, code or PC |
|
|
88
|
+
| `feedback send <summary> --kind <kind> [--goal …] [--expected …] [--actual …] [--tried …] [--workaround …] [--would-help …] [--cost …] [--severity …] [--add-to <id>]` | Tells Patch's team what got in your way, then you carry on: `failed` (you couldn't do what was asked), `bug` (Patch did something wrong), `call_error` (a call failed and its error didn't say what to do), `unhelpful` (a command or its answer didn't help) or `idea`. Write it for the engineer who'll fix it: what you were doing, what you expected, what happened in Patch's own words, what you tried, any workaround, what it cost (`--severity blocked`, `slowed` or `annoyed`) and what would have helped. It asks back the one thing a new report is missing that would help most, which `--add-to` adds. `--tool`, `--error-code`, `--project`, `--agent` and `--details` say more. A person at Patch reads every one, anything that looks like a key is taken out, and if the team already answered a report like it, it says what they said |
|
|
89
|
+
| `feedback list` | Your reports, newest first, with what Patch's team answered |
|
|
90
|
+
|
|
91
|
+
Every command takes `--json`: results on stdout, errors as `{ "error": { "code", "message", "details" } }` on stderr. An error from Patch's API, or one of pch's own that shouldn't happen (exit 1), adds `"feedback": { "say" }`: the `pch feedback send` to run if the error didn't help. Once a test is over or a release is live, `pch test view` and `pch status` ask what in Patch's tools cost you time getting there (`feedback_ask` with `--json`). A long upload also says how far it's got on stderr, as lines of `{ "progress": { … } }`.
|
|
90
92
|
|
|
91
93
|
| Exit code | Meaning |
|
|
92
94
|
| --------- | --------------------------------------------------------------------------------------------- |
|
|
@@ -151,7 +153,7 @@ It never pushes a submodule as well, as git's own push into one would take this
|
|
|
151
153
|
|
|
152
154
|
Patch gives a key for that one command, and it needs the `publish` scope and a place in the repo's org. The key is only in git's environment, as `PCH_PUSH_KEY`, read by a credential helper on the command line, so no key goes in any file. Git LFS may note `locksverify` for its own address in `.git/config`, which holds no key. The remote keeps its address, and git's record of where the remote is moves as it does for any push. Big files still go to Patch's git host, signed in with the login pch is using.
|
|
153
155
|
|
|
154
|
-
A key lasts
|
|
156
|
+
A key lasts 10 minutes, and it's only checked as each of git's requests starts. When big files take longer than that to upload, pch gets one more key and pushes again, and they don't go twice. Git may print `Everything up-to-date` on the way: pch goes by what it says of each ref.
|
|
155
157
|
|
|
156
158
|
What's in a push is never why it's refused: an open repo's new commits are read for secrets after they land, and a secret that's landed takes a rewrite to get out. One ref that can't go refuses the whole push, `--atomic` or not, and nothing in it lands, so pch says the reason once. Once a push lands, pch tells Patch and prints whatever Patch has to say about it, such as a scan it's waiting on. If Patch can't be told, the push has still landed, and Patch finds out by itself a little later.
|
|
157
159
|
|
package/dist/cli.js
CHANGED
|
@@ -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
|
|
|
@@ -39977,6 +39981,12 @@ init_guide_words();
|
|
|
39977
39981
|
// src/output.ts
|
|
39978
39982
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
39979
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
|
+
}
|
|
39980
39990
|
var Output = class {
|
|
39981
39991
|
constructor(json2) {
|
|
39982
39992
|
this.json = json2;
|
|
@@ -39997,6 +40007,21 @@ var Output = class {
|
|
|
39997
40007
|
if (!this.json) process.stderr.write(`${import_picocolors.default.dim("\u203A")} ${text}
|
|
39998
40008
|
`);
|
|
39999
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
|
+
}
|
|
40000
40025
|
/**
|
|
40001
40026
|
* How far a long job has got. With --json it's a line of JSON on stderr, so whoever's waiting can
|
|
40002
40027
|
* see it's moving while stdout stays the one result.
|
|
@@ -40006,15 +40031,17 @@ var Output = class {
|
|
|
40006
40031
|
`);
|
|
40007
40032
|
else this.step(text);
|
|
40008
40033
|
}
|
|
40009
|
-
error(error62) {
|
|
40034
|
+
error(error62, command) {
|
|
40010
40035
|
const failure2 = error62 instanceof CliError ? error62 : new CliError(
|
|
40011
40036
|
EXIT.error,
|
|
40012
40037
|
"unexpected_error",
|
|
40013
40038
|
error62 instanceof Error ? error62.message : String(error62)
|
|
40014
40039
|
);
|
|
40040
|
+
const feedback2 = feedbackFor(failure2, command);
|
|
40015
40041
|
if (this.json) {
|
|
40016
40042
|
const body = {
|
|
40017
|
-
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 } : {}
|
|
40018
40045
|
};
|
|
40019
40046
|
process.stderr.write(`${JSON.stringify(body)}
|
|
40020
40047
|
`);
|
|
@@ -40027,10 +40054,19 @@ var Output = class {
|
|
|
40027
40054
|
process.stderr.write(` ${import_picocolors.default.dim(issue2.path || "\xB7")} ${issue2.message}
|
|
40028
40055
|
`);
|
|
40029
40056
|
}
|
|
40057
|
+
if (feedback2) process.stderr.write(`${import_picocolors.default.dim(feedback2.say)}
|
|
40058
|
+
`);
|
|
40030
40059
|
}
|
|
40031
40060
|
return failure2.exitCode;
|
|
40032
40061
|
}
|
|
40033
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
|
+
}
|
|
40034
40070
|
function issuesOf(details) {
|
|
40035
40071
|
const issues = details?.issues;
|
|
40036
40072
|
return Array.isArray(issues) ? issues : [];
|
|
@@ -40077,7 +40113,7 @@ async function login(ctx, options) {
|
|
|
40077
40113
|
})
|
|
40078
40114
|
)
|
|
40079
40115
|
}));
|
|
40080
|
-
const name = ctx.target.accountName ?? (target.env
|
|
40116
|
+
const name = ctx.target.accountName ?? defaultAccount(target.env);
|
|
40081
40117
|
const account2 = {
|
|
40082
40118
|
env: target.env,
|
|
40083
40119
|
token: key.key,
|
|
@@ -40103,9 +40139,13 @@ async function login(ctx, options) {
|
|
|
40103
40139
|
look_first: LOOK_FIRST
|
|
40104
40140
|
},
|
|
40105
40141
|
() => {
|
|
40106
|
-
|
|
40107
|
-
|
|
40108
|
-
|
|
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}").`);
|
|
40109
40149
|
if (!me.username) out.line(`Choose a username at ${target.origin}/welcome to publish.`);
|
|
40110
40150
|
if (git.helper) out.line(`Git is signed in too: Patch repos at ${git.url} just work.`);
|
|
40111
40151
|
if (gitIdentity) out.line(gitIdentity);
|
|
@@ -40132,15 +40172,53 @@ async function loginWithKey(ctx) {
|
|
|
40132
40172
|
look_first: LOOK_FIRST
|
|
40133
40173
|
},
|
|
40134
40174
|
() => {
|
|
40135
|
-
|
|
40136
|
-
|
|
40137
|
-
|
|
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}.`);
|
|
40138
40181
|
if (git.helper) out.line(`Git is signed in too: Patch repos at ${git.url} just work.`);
|
|
40139
40182
|
if (gitIdentity) out.line(gitIdentity);
|
|
40140
40183
|
out.line(LOOK_FIRST);
|
|
40141
40184
|
}
|
|
40142
40185
|
);
|
|
40143
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
|
+
}
|
|
40144
40222
|
async function setUpGit(ctx) {
|
|
40145
40223
|
const { gitUrl: url2 } = ctx.target;
|
|
40146
40224
|
if (!url2) return { url: url2, helper: false, lfs: false };
|
|
@@ -40161,19 +40239,55 @@ async function withDeviceSession(ctx, options, use) {
|
|
|
40161
40239
|
if (!started.response.ok) throw authError(started.body, "Couldn't start a login");
|
|
40162
40240
|
const device = started.body;
|
|
40163
40241
|
const code = formatUserCode(device.user_code);
|
|
40164
|
-
process.
|
|
40165
|
-
|
|
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.
|
|
40166
40261
|
|
|
40167
40262
|
To sign in, open ${import_picocolors2.default.bold(device.verification_uri)} and enter ${import_picocolors2.default.bold(code)}
|
|
40168
40263
|
|
|
40169
40264
|
`
|
|
40170
|
-
|
|
40171
|
-
|
|
40172
|
-
|
|
40173
|
-
|
|
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`;
|
|
40174
40289
|
let interval = (options.interval ?? device.interval) * 1e3;
|
|
40175
40290
|
const deadline = Date.now() + device.expires_in * 1e3;
|
|
40176
|
-
let sessionToken = null;
|
|
40177
40291
|
while (Date.now() < deadline) {
|
|
40178
40292
|
await sleep(interval);
|
|
40179
40293
|
const polled = await postJson(`${auth2}/device/token`, {
|
|
@@ -40181,10 +40295,7 @@ To sign in, open ${import_picocolors2.default.bold(device.verification_uri)} and
|
|
|
40181
40295
|
device_code: device.device_code,
|
|
40182
40296
|
client_id: CLIENT_ID
|
|
40183
40297
|
});
|
|
40184
|
-
if (polled.response.ok)
|
|
40185
|
-
sessionToken = polled.body.access_token;
|
|
40186
|
-
break;
|
|
40187
|
-
}
|
|
40298
|
+
if (polled.response.ok) return polled.body.access_token;
|
|
40188
40299
|
const error62 = polled.body.error;
|
|
40189
40300
|
if (error62 === "authorization_pending") continue;
|
|
40190
40301
|
if (error62 === "slow_down") {
|
|
@@ -40197,18 +40308,7 @@ To sign in, open ${import_picocolors2.default.bold(device.verification_uri)} and
|
|
|
40197
40308
|
if (error62 === "expired_token") break;
|
|
40198
40309
|
throw authError(polled.body, "The login failed");
|
|
40199
40310
|
}
|
|
40200
|
-
|
|
40201
|
-
throw new CliError(EXIT.auth, "expired_token", "The code expired. Run pch login again.");
|
|
40202
|
-
}
|
|
40203
|
-
try {
|
|
40204
|
-
return await use(createApi(target, { token: sessionToken }));
|
|
40205
|
-
} finally {
|
|
40206
|
-
await fetch(`${auth2}/sign-out`, {
|
|
40207
|
-
method: "POST",
|
|
40208
|
-
headers: { authorization: `Bearer ${sessionToken}`, "content-type": "application/json" },
|
|
40209
|
-
body: "{}"
|
|
40210
|
-
}).catch(() => void 0);
|
|
40211
|
-
}
|
|
40311
|
+
return null;
|
|
40212
40312
|
}
|
|
40213
40313
|
function runningIn(vars = process.env) {
|
|
40214
40314
|
if (vars.CLAUDECODE === "1") return "Claude Code";
|
|
@@ -42598,8 +42698,8 @@ async function agentInstall(ctx, named, options = {}) {
|
|
|
42598
42698
|
const agents = [];
|
|
42599
42699
|
for (const agent2 of wanted) agents.push(await install(agent2, server));
|
|
42600
42700
|
const manual = agents.filter((entry) => entry.state === "manual");
|
|
42601
|
-
const
|
|
42602
|
-
ctx.out.result({ server, agents, signed_in:
|
|
42701
|
+
const signedIn2 = Boolean(ctx.target.token);
|
|
42702
|
+
ctx.out.result({ server, agents, signed_in: signedIn2 }, () => {
|
|
42603
42703
|
for (const entry of agents) {
|
|
42604
42704
|
const name = AGENT_NAMES[entry.agent];
|
|
42605
42705
|
if (entry.state === "manual") {
|
|
@@ -42611,7 +42711,7 @@ async function agentInstall(ctx, named, options = {}) {
|
|
|
42611
42711
|
}
|
|
42612
42712
|
}
|
|
42613
42713
|
ctx.out.line(import_picocolors6.default.dim(`It runs ${commandLine(server)}, and holds no key.`));
|
|
42614
|
-
if (!
|
|
42714
|
+
if (!signedIn2) ctx.out.line(`Sign in first, or its tools won't work: ${pchLogin(ctx)}.`);
|
|
42615
42715
|
ctx.out.line(
|
|
42616
42716
|
"Restart your agent to load its tools. Until then, pch guide prints the same guide."
|
|
42617
42717
|
);
|
|
@@ -43173,9 +43273,9 @@ async function projectView(ctx, ref) {
|
|
|
43173
43273
|
);
|
|
43174
43274
|
}
|
|
43175
43275
|
if (project2.urls?.play) {
|
|
43176
|
-
const
|
|
43276
|
+
const signedIn2 = ownOnly?.channel === "private" && !unserved ? ", signed in" : "";
|
|
43177
43277
|
const which = unserved && project2.latest_release ? ` (${project2.latest_release.version})` : "";
|
|
43178
|
-
ctx.out.line(`Plays at: ${project2.urls.play}${
|
|
43278
|
+
ctx.out.line(`Plays at: ${project2.urls.play}${signedIn2}${which}`);
|
|
43179
43279
|
}
|
|
43180
43280
|
ctx.out.line(
|
|
43181
43281
|
`Source: ${repo ? VISIBILITY_LABELS[repo.visibility] : SOURCE_LABELS[project2.source_visibility]} \xB7 code ${project2.code_license} \xB7 art ${project2.art_license}`
|
|
@@ -46075,6 +46175,11 @@ function showReview({ out }, project2, review) {
|
|
|
46075
46175
|
out.line(
|
|
46076
46176
|
` ${ours ? nextStep(name, review.status, review.test, cloud).say : plain(review.next)}`
|
|
46077
46177
|
);
|
|
46178
|
+
if (review.feedback_ask) {
|
|
46179
|
+
out.line(
|
|
46180
|
+
` ${plain(review.feedback_ask.ask.replaceAll("send_feedback", "pch feedback send"))}`
|
|
46181
|
+
);
|
|
46182
|
+
}
|
|
46078
46183
|
for (const run3 of review.check_runs) {
|
|
46079
46184
|
out.line(` Checks (${run3.kind}): ${run3.status}${run3.summary ? `, ${run3.summary}` : ""}`);
|
|
46080
46185
|
for (const finding of run3.findings) {
|
|
@@ -46995,8 +47100,83 @@ function outFolder(out, byDefault) {
|
|
|
46995
47100
|
return byDefault ? "" : void 0;
|
|
46996
47101
|
}
|
|
46997
47102
|
|
|
46998
|
-
// src/commands/
|
|
47103
|
+
// src/commands/feedback.ts
|
|
46999
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);
|
|
47000
47180
|
import { stat as stat5 } from "node:fs/promises";
|
|
47001
47181
|
import { basename as basename6, join as join17, resolve as resolve13 } from "node:path";
|
|
47002
47182
|
|
|
@@ -47731,8 +47911,8 @@ async function push(ctx, given) {
|
|
|
47731
47911
|
},
|
|
47732
47912
|
() => {
|
|
47733
47913
|
const list = sent.map(({ ref, status: status2 }) => `${shortRef(ref)}${status2 === "deleted" ? " (deleted)" : ""}`).join(", ");
|
|
47734
|
-
if (list) out.line(`${dryRun ? "Would push" : "Pushed"} ${list} to ${
|
|
47735
|
-
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)}.`);
|
|
47736
47916
|
if (pushed.notice) out.line(pushed.notice);
|
|
47737
47917
|
}
|
|
47738
47918
|
);
|
|
@@ -47779,10 +47959,10 @@ async function clone2(ctx, ref, dir) {
|
|
|
47779
47959
|
brief: brief ? BRIEF_FILE : null
|
|
47780
47960
|
},
|
|
47781
47961
|
() => {
|
|
47782
|
-
out.line(`Cloned ${
|
|
47962
|
+
out.line(`Cloned ${import_picocolors17.default.bold(`${project2.owner}/${project2.slug}`)} into ${basename6(into)}`);
|
|
47783
47963
|
if (commitsAs) out.line(saidCommitsAs(commitsAs));
|
|
47784
|
-
out.line(
|
|
47785
|
-
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."));
|
|
47786
47966
|
if (brief) {
|
|
47787
47967
|
out.line(
|
|
47788
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.`
|
|
@@ -47793,7 +47973,7 @@ async function clone2(ctx, ref, dir) {
|
|
|
47793
47973
|
}
|
|
47794
47974
|
|
|
47795
47975
|
// src/commands/media.ts
|
|
47796
|
-
var
|
|
47976
|
+
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
47797
47977
|
import { readFile as readFile14 } from "node:fs/promises";
|
|
47798
47978
|
import { basename as basename7, extname as extname4, resolve as resolve14 } from "node:path";
|
|
47799
47979
|
import { setTimeout as sleep8 } from "node:timers/promises";
|
|
@@ -47889,11 +48069,11 @@ function showAdded(ctx, ref, media2) {
|
|
|
47889
48069
|
);
|
|
47890
48070
|
}
|
|
47891
48071
|
ctx.out.result(media2, () => {
|
|
47892
|
-
ctx.out.line(`${
|
|
47893
|
-
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));
|
|
47894
48074
|
else {
|
|
47895
48075
|
ctx.out.line(
|
|
47896
|
-
|
|
48076
|
+
import_picocolors18.default.dim(
|
|
47897
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}.`
|
|
47898
48078
|
)
|
|
47899
48079
|
);
|
|
@@ -47913,7 +48093,7 @@ async function mediaList(ctx, ref) {
|
|
|
47913
48093
|
for (const entry of media2.data) {
|
|
47914
48094
|
const failed3 = entry.status === "failed" && entry.error ? `: ${entry.error}` : "";
|
|
47915
48095
|
ctx.out.line(
|
|
47916
|
-
`${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}`) : ""}`
|
|
47917
48097
|
);
|
|
47918
48098
|
}
|
|
47919
48099
|
});
|
|
@@ -47941,7 +48121,7 @@ async function mediaOrder(ctx, ref, ids) {
|
|
|
47941
48121
|
}
|
|
47942
48122
|
|
|
47943
48123
|
// src/commands/publish.ts
|
|
47944
|
-
var
|
|
48124
|
+
var import_picocolors19 = __toESM(require_picocolors(), 1);
|
|
47945
48125
|
var import_semver2 = __toESM(require_semver2(), 1);
|
|
47946
48126
|
import { open as open2, readFile as readFile15 } from "node:fs/promises";
|
|
47947
48127
|
init_errors();
|
|
@@ -48048,7 +48228,7 @@ async function publish(ctx, dir, options) {
|
|
|
48048
48228
|
out.result(result, () => {
|
|
48049
48229
|
if (!submitted) {
|
|
48050
48230
|
out.line(
|
|
48051
|
-
`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.`
|
|
48052
48232
|
);
|
|
48053
48233
|
out.line(privateWords(release2.kind, true));
|
|
48054
48234
|
if (openCode) out.line(openCode);
|
|
@@ -48066,7 +48246,7 @@ async function publish(ctx, dir, options) {
|
|
|
48066
48246
|
` ${entry.path.padEnd(40)} ${formatSize(entry.size).padStart(8)} sha256 ${entry.sha256}`
|
|
48067
48247
|
);
|
|
48068
48248
|
}
|
|
48069
|
-
out.line(
|
|
48249
|
+
out.line(import_picocolors19.default.dim(url2));
|
|
48070
48250
|
});
|
|
48071
48251
|
}
|
|
48072
48252
|
function fromCommit(source) {
|
|
@@ -48086,7 +48266,7 @@ async function releaseSubmit(ctx, ref) {
|
|
|
48086
48266
|
ctx.out.result({ ...submitted, next, ...whoCanSee }, () => {
|
|
48087
48267
|
showSubmitted(ctx, release2, submitted.status, submitted);
|
|
48088
48268
|
if (openCode) ctx.out.line(openCode);
|
|
48089
|
-
ctx.out.line(
|
|
48269
|
+
ctx.out.line(import_picocolors19.default.dim(projectUrl(ctx.target.origin, release2)));
|
|
48090
48270
|
});
|
|
48091
48271
|
}
|
|
48092
48272
|
async function releasePromote(ctx, ref, options) {
|
|
@@ -48140,7 +48320,7 @@ async function releasePromote(ctx, ref, options) {
|
|
|
48140
48320
|
const next = nextStep(name, "draft", null).command;
|
|
48141
48321
|
ctx.out.result({ from: ref, release: { version: version2, status: "draft" }, url: url2, next }, () => {
|
|
48142
48322
|
ctx.out.line(
|
|
48143
|
-
`Made ${
|
|
48323
|
+
`Made ${import_picocolors19.default.bold(name)} from ${ref}, as a draft for ${options.channel}. It needs a passed test of its own.`
|
|
48144
48324
|
);
|
|
48145
48325
|
ctx.out.line(`Next: ${next}`);
|
|
48146
48326
|
});
|
|
@@ -48148,7 +48328,7 @@ async function releasePromote(ctx, ref, options) {
|
|
|
48148
48328
|
}
|
|
48149
48329
|
ctx.out.result({ from: ref, release: submitted, url: url2 }, () => {
|
|
48150
48330
|
showSubmitted(ctx, to, submitted.status, submitted);
|
|
48151
|
-
ctx.out.line(
|
|
48331
|
+
ctx.out.line(import_picocolors19.default.dim(url2));
|
|
48152
48332
|
});
|
|
48153
48333
|
}
|
|
48154
48334
|
function draftUntilTested(error62) {
|
|
@@ -48185,7 +48365,7 @@ function whoCanSeeIt(...words2) {
|
|
|
48185
48365
|
function showSubmitted(ctx, ref, status2, { channel, kind }) {
|
|
48186
48366
|
const name = releaseName(ref);
|
|
48187
48367
|
if (status2 === "published") {
|
|
48188
|
-
ctx.out.line(
|
|
48368
|
+
ctx.out.line(import_picocolors19.default.green(`Published ${name} to ${channel}`));
|
|
48189
48369
|
if (channel === "private") {
|
|
48190
48370
|
ctx.out.line(privateWords(kind, false));
|
|
48191
48371
|
ctx.out.line(`pch status ${name} says whether your team can play it yet, and where.`);
|
|
@@ -48339,7 +48519,7 @@ async function uploadFailed(file2, upload, response) {
|
|
|
48339
48519
|
}
|
|
48340
48520
|
|
|
48341
48521
|
// src/commands/release-test.ts
|
|
48342
|
-
var
|
|
48522
|
+
var import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
48343
48523
|
import { createHash as createHash3 } from "node:crypto";
|
|
48344
48524
|
import { mkdir as mkdir9, mkdtemp as mkdtemp3, writeFile as writeFile12 } from "node:fs/promises";
|
|
48345
48525
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
@@ -48451,9 +48631,19 @@ async function show3(ctx, release2, test2, { followed, ...extra } = {}) {
|
|
|
48451
48631
|
const play2 = test2.state === "passed" ? await playFirst(ctx, release2) : null;
|
|
48452
48632
|
const ask = test2.state === "waiting" ? askThePerson(test2) : play2;
|
|
48453
48633
|
const noteAsk = test2.note_ask?.replaceAll("suggest_note", "pch note suggest");
|
|
48454
|
-
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
|
+
};
|
|
48455
48645
|
out.result({ ...shown, ...ask ? { ask } : {} }, () => {
|
|
48456
|
-
const bold =
|
|
48646
|
+
const bold = import_picocolors20.default.bold(name);
|
|
48457
48647
|
if (test2.state === "waiting") {
|
|
48458
48648
|
out.line(
|
|
48459
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.`
|
|
@@ -48469,30 +48659,31 @@ async function show3(ctx, release2, test2, { followed, ...extra } = {}) {
|
|
|
48469
48659
|
`${bold}: it has been played and recorded. Look at the footage, then send your review.`
|
|
48470
48660
|
);
|
|
48471
48661
|
} else if (test2.state === "passed") {
|
|
48472
|
-
out.line(`${bold}: ${
|
|
48662
|
+
out.line(`${bold}: ${import_picocolors20.default.green("its test passed.")} It's still private.`);
|
|
48473
48663
|
if (test2.review) out.line(plain(test2.review.summary));
|
|
48474
48664
|
if (play2) out.line(play2);
|
|
48475
48665
|
out.line(
|
|
48476
48666
|
"Publish it once you're happy it's ready: that's your yes, and nothing does it for you."
|
|
48477
48667
|
);
|
|
48478
48668
|
} else if (test2.state === "failed") {
|
|
48479
|
-
out.line(`${bold}: ${
|
|
48669
|
+
out.line(`${bold}: ${import_picocolors20.default.red("its test failed")}, so this version can't be published.`);
|
|
48480
48670
|
showWhatFailed(ctx, test2);
|
|
48481
48671
|
out.line(`pch status ${name} says how to fix it.`);
|
|
48482
48672
|
out.line("Then move the version on in patch.json and publish that.");
|
|
48483
48673
|
} else if (test2.state === "couldnt_run") {
|
|
48484
|
-
out.line(`${bold}: ${
|
|
48674
|
+
out.line(`${bold}: ${import_picocolors20.default.yellow("its test couldn't run.")}`);
|
|
48485
48675
|
if (test2.outcome) out.line(plain(test2.outcome.reason));
|
|
48486
48676
|
} else {
|
|
48487
48677
|
out.line(`${bold}: its test was cancelled.`);
|
|
48488
48678
|
}
|
|
48489
48679
|
if (noteAsk) out.line(plain(noteAsk));
|
|
48680
|
+
if (feedbackAsk) out.line(plain(feedbackAsk.ask));
|
|
48490
48681
|
if (followed && GOING.has(test2.state)) {
|
|
48491
48682
|
out.line("It isn't over, and nothing's wrong: that's as long as --wait follows it here.");
|
|
48492
48683
|
}
|
|
48493
48684
|
if (command) out.line(`Next: ${command}`);
|
|
48494
48685
|
if (OPEN.has(test2.state) && !extra.started) {
|
|
48495
|
-
out.line(
|
|
48686
|
+
out.line(import_picocolors20.default.dim(`To start over: pch test cancel ${name}, then pch test start ${name}`));
|
|
48496
48687
|
}
|
|
48497
48688
|
});
|
|
48498
48689
|
if (test2.state === "failed") return EXIT.blocked;
|
|
@@ -48529,7 +48720,7 @@ async function testFootage(ctx, ref, options) {
|
|
|
48529
48720
|
const next = data.length ? reviewCommand(name) : nextStep(name, "testing", test2).command;
|
|
48530
48721
|
out.result({ test: test2.id, state: test2.state, data: sheets, next }, () => {
|
|
48531
48722
|
if (!sheets.length) {
|
|
48532
|
-
out.line(`${
|
|
48723
|
+
out.line(`${import_picocolors20.default.bold(name)} has no footage yet: its test is ${test2.state.replace("_", " ")}.`);
|
|
48533
48724
|
for (const result of test2.results) {
|
|
48534
48725
|
if (result.footage_error) out.line(plain(result.footage_error));
|
|
48535
48726
|
}
|
|
@@ -48604,7 +48795,7 @@ async function testCancel(ctx, ref) {
|
|
|
48604
48795
|
}
|
|
48605
48796
|
const next = `pch test start ${name}`;
|
|
48606
48797
|
ctx.out.result({ ...answer.data, next }, () => {
|
|
48607
|
-
ctx.out.line(`Cancelled ${
|
|
48798
|
+
ctx.out.line(`Cancelled ${import_picocolors20.default.bold(name)}'s test. Start another: ${next}`);
|
|
48608
48799
|
});
|
|
48609
48800
|
}
|
|
48610
48801
|
function withNext(failure2, release2, test2) {
|
|
@@ -48634,7 +48825,7 @@ async function testAutostart(ctx, state) {
|
|
|
48634
48825
|
}
|
|
48635
48826
|
|
|
48636
48827
|
// src/commands/search.ts
|
|
48637
|
-
var
|
|
48828
|
+
var import_picocolors21 = __toESM(require_picocolors(), 1);
|
|
48638
48829
|
init_guide_words();
|
|
48639
48830
|
var filter = (values) => values?.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean).join(",") || void 0;
|
|
48640
48831
|
var licences = (values) => values.flatMap((value) => value.split(",")).map((value) => licenceId(value.trim()) ?? value.trim());
|
|
@@ -48672,13 +48863,13 @@ async function search(ctx, q, options) {
|
|
|
48672
48863
|
out.result({ ...answer, ...aboutBlocks }, () => {
|
|
48673
48864
|
if (!data.length) out.line("Nothing found.");
|
|
48674
48865
|
for (const result of plain(data)) {
|
|
48675
|
-
out.line(`${
|
|
48866
|
+
out.line(`${import_picocolors21.default.bold(result.handle)} ${import_picocolors21.default.dim(facts(result))}`);
|
|
48676
48867
|
if (result.summary) out.line(` ${result.summary}`);
|
|
48677
48868
|
if (result.demo) out.line(` Live demo: ${ctx.target.origin}/${result.owner}/${result.slug}`);
|
|
48678
|
-
out.line(
|
|
48869
|
+
out.line(import_picocolors21.default.dim(` ${result.clone_url}`));
|
|
48679
48870
|
}
|
|
48680
48871
|
if (blocks) out.line(BLOCK_WORDS);
|
|
48681
|
-
if (cursor) out.line(
|
|
48872
|
+
if (cursor) out.line(import_picocolors21.default.dim("There are more; raise --limit."));
|
|
48682
48873
|
});
|
|
48683
48874
|
}
|
|
48684
48875
|
function facts(result) {
|
|
@@ -48703,14 +48894,14 @@ async function categories(ctx, options) {
|
|
|
48703
48894
|
const width = Math.max(0, ...data.map((category) => category.id.length));
|
|
48704
48895
|
for (const category of data) {
|
|
48705
48896
|
ctx.out.line(
|
|
48706
|
-
`${
|
|
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`}`)}`
|
|
48707
48898
|
);
|
|
48708
48899
|
}
|
|
48709
48900
|
});
|
|
48710
48901
|
}
|
|
48711
48902
|
|
|
48712
48903
|
// src/commands/update.ts
|
|
48713
|
-
var
|
|
48904
|
+
var import_picocolors22 = __toESM(require_picocolors(), 1);
|
|
48714
48905
|
var import_semver3 = __toESM(require_semver2(), 1);
|
|
48715
48906
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
48716
48907
|
import { createHash as createHash4 } from "node:crypto";
|
|
@@ -48897,7 +49088,7 @@ function startUpdateNotice(ctx, command, now = Date.now()) {
|
|
|
48897
49088
|
if (version2 && import_semver3.default.gt(version2, VERSION)) {
|
|
48898
49089
|
process.stderr.write(
|
|
48899
49090
|
`
|
|
48900
|
-
${
|
|
49091
|
+
${import_picocolors22.default.dim("\u203A")} pch ${version2} is out (you have ${VERSION}). To update, run: ${how}
|
|
48901
49092
|
`
|
|
48902
49093
|
);
|
|
48903
49094
|
}
|
|
@@ -48906,7 +49097,7 @@ ${import_picocolors21.default.dim("\u203A")} pch ${version2} is out (you have ${
|
|
|
48906
49097
|
}
|
|
48907
49098
|
|
|
48908
49099
|
// src/commands/validate.ts
|
|
48909
|
-
var
|
|
49100
|
+
var import_picocolors23 = __toESM(require_picocolors(), 1);
|
|
48910
49101
|
init_errors();
|
|
48911
49102
|
async function validate2(ctx, dir) {
|
|
48912
49103
|
const project2 = await loadProject(dir);
|
|
@@ -48965,12 +49156,12 @@ async function validate2(ctx, dir) {
|
|
|
48965
49156
|
notes
|
|
48966
49157
|
};
|
|
48967
49158
|
ctx.out.result(result, () => {
|
|
48968
|
-
const mark = result.ok ?
|
|
49159
|
+
const mark = result.ok ? import_picocolors23.default.green("\u2713") : import_picocolors23.default.red("\u2717");
|
|
48969
49160
|
ctx.out.line(
|
|
48970
49161
|
`${mark} ${file2.project}@${file2.version} \xB7 ${project2.kind} \xB7 ${result.trust_tier} \xB7 ${project2.files.length} files, ${formatSize(total)}`
|
|
48971
49162
|
);
|
|
48972
|
-
for (const issue2 of issues) ctx.out.line(` ${
|
|
48973
|
-
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)}`);
|
|
48974
49165
|
});
|
|
48975
49166
|
return result.ok ? EXIT.ok : EXIT.usage;
|
|
48976
49167
|
}
|
|
@@ -49010,13 +49201,22 @@ Start here:
|
|
|
49010
49201
|
pch media add, pch project update the cover and the listing
|
|
49011
49202
|
|
|
49012
49203
|
Add --json to any command for results an agent can read. Exit codes: 0 fine, 1 error, 2 bad usage,
|
|
49013
|
-
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.`
|
|
49014
49209
|
).showHelpAfterError(!wantsJson).configureOutput({
|
|
49015
49210
|
outputError: (text, write) => write(
|
|
49016
49211
|
wantsJson ? `${JSON.stringify({ error: { code: "bad_usage", message: text.replace(/^error: /, "").trim() } })}
|
|
49017
49212
|
` : text
|
|
49018
49213
|
)
|
|
49019
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
|
+
}
|
|
49020
49220
|
function action(run3) {
|
|
49021
49221
|
return async (...args2) => {
|
|
49022
49222
|
const command = args2.at(-1);
|
|
@@ -49028,7 +49228,7 @@ function action(run3) {
|
|
|
49028
49228
|
const code = await run3(ctx, ...args2.slice(0, -1));
|
|
49029
49229
|
process.exitCode = typeof code === "number" ? code : EXIT.ok;
|
|
49030
49230
|
} catch (error62) {
|
|
49031
|
-
process.exitCode = new Output(Boolean(globals.json)).error(error62);
|
|
49231
|
+
process.exitCode = new Output(Boolean(globals.json)).error(error62, commandPath(command));
|
|
49032
49232
|
}
|
|
49033
49233
|
await notice?.show();
|
|
49034
49234
|
};
|
|
@@ -49479,6 +49679,20 @@ note.command("suggest <about> <fact>").description(
|
|
|
49479
49679
|
(ctx, about, fact, options) => noteSuggest(ctx, about, fact, options)
|
|
49480
49680
|
)
|
|
49481
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)));
|
|
49482
49696
|
var words = process.argv.slice(2);
|
|
49483
49697
|
if (!program2.commands.some((one) => [one.name(), ...one.aliases()].some((n) => words.includes(n)))) {
|
|
49484
49698
|
program2.version(VERSION);
|