gitwarren 0.1.8-beta.1 → 0.1.9
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
CHANGED
|
@@ -8,33 +8,58 @@ This package is the command line. There is also a desktop app; see
|
|
|
8
8
|
[gitwarren-app](https://github.com/klarluft/gitwarren-app).
|
|
9
9
|
|
|
10
10
|
```
|
|
11
|
-
npx gitwarren serve
|
|
11
|
+
npx gitwarren serve --open
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
That serves the review UI on `127.0.0.1:41427
|
|
15
|
-
|
|
16
|
-
review the diff.
|
|
14
|
+
That serves the review UI on `127.0.0.1:41427`, prints a URL carrying a token
|
|
15
|
+
minted for that launch, and opens it. Add a repository, pick two refs, and
|
|
16
|
+
review the diff. Ctrl-C stops it. Needs Node 22 or newer; on a machine without
|
|
17
|
+
Node, the Homebrew formula and the install script in the
|
|
18
|
+
[app repository](https://github.com/klarluft/gitwarren-app#the-gitwarren-command-line)
|
|
19
|
+
bring their own.
|
|
17
20
|
|
|
18
21
|
## Commands
|
|
19
22
|
|
|
23
|
+
**Run it now**
|
|
24
|
+
|
|
25
|
+
| | |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `gitwarren serve [--open]` | run GitWarren in this terminal and print its URL; `--open` opens it too |
|
|
28
|
+
| `gitwarren open [link]` | open the running GitWarren in your browser, carrying the token |
|
|
29
|
+
|
|
30
|
+
**Keep it running**
|
|
31
|
+
|
|
20
32
|
| | |
|
|
21
33
|
| --- | --- |
|
|
22
|
-
| `gitwarren
|
|
23
|
-
| `gitwarren
|
|
24
|
-
| `gitwarren
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
| `gitwarren service install` | run GitWarren in the background, from now and at every login |
|
|
35
|
+
| `gitwarren service uninstall` | stop that, and remove the login item |
|
|
36
|
+
| `gitwarren service status` | what is running, and where the data is |
|
|
37
|
+
|
|
38
|
+
**Let a coding agent in**
|
|
39
|
+
|
|
40
|
+
| | |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `gitwarren agent-setup` | print the one sentence to give an agent so it can reach GitWarren over MCP |
|
|
43
|
+
|
|
44
|
+
`gitwarren serve --stdio` answers GitWarren's protocol on stdin and stdout; it
|
|
45
|
+
is what a GitWarren on another machine spawns, and nothing a person needs to
|
|
46
|
+
type.
|
|
28
47
|
|
|
29
48
|
`gitwarren open` takes a `gitwarren://` deep link or the `http://127.0.0.1`
|
|
30
49
|
URL an agent hands out, and lands on that review rather than the home screen.
|
|
31
50
|
|
|
32
51
|
## Letting an agent in
|
|
33
52
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
Every install carries GitWarren's MCP server. What an agent needs is one
|
|
54
|
+
stable command to start it with, and that is `~/.gitwarren/bin/gitwarren-mcp` —
|
|
55
|
+
the same path on every machine, written by `gitwarren serve`, `gitwarren
|
|
56
|
+
agent-setup` and `gitwarren service install` alike, and kept pointing at
|
|
57
|
+
whichever install ran last. `gitwarren agent-setup` prints the sentence to
|
|
58
|
+
paste into the agent.
|
|
59
|
+
|
|
60
|
+
The MCP server reads the same SQLite file the browser view does, so an agent
|
|
61
|
+
can open and comment on reviews whether or not GitWarren is being served. What
|
|
62
|
+
serving adds is that the links an agent hands you have something to open.
|
|
38
63
|
|
|
39
64
|
## Where your data is
|
|
40
65
|
|
package/lib/gitwarren.cjs
CHANGED
|
@@ -10242,7 +10242,7 @@ function cmdQuote(value) {
|
|
|
10242
10242
|
if (value.includes('"')) throw new Error(`a Windows path cannot contain a quote: ${value}`);
|
|
10243
10243
|
return `"${value}"`;
|
|
10244
10244
|
}
|
|
10245
|
-
const APP_VERSION = "0.1.
|
|
10245
|
+
const APP_VERSION = "0.1.9";
|
|
10246
10246
|
const REMOTE_ROOT = "$HOME/.gitwarren";
|
|
10247
10247
|
const runOnHost = (route2, options) => {
|
|
10248
10248
|
switch (route2.kind) {
|
|
@@ -16691,7 +16691,7 @@ function describeMcpLaunch() {
|
|
|
16691
16691
|
available,
|
|
16692
16692
|
stable: true,
|
|
16693
16693
|
direct: { command: launcher, args: [], env: {} },
|
|
16694
|
-
note: available ? void 0 : "No MCP launcher on this machine yet. `gitwarren
|
|
16694
|
+
note: available ? void 0 : "No MCP launcher on this machine yet. `gitwarren agent-setup` writes it, and so does `gitwarren serve`."
|
|
16695
16695
|
};
|
|
16696
16696
|
}
|
|
16697
16697
|
const MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
|
|
@@ -26868,7 +26868,7 @@ Connection: close\r
|
|
|
26868
26868
|
}
|
|
26869
26869
|
};
|
|
26870
26870
|
}
|
|
26871
|
-
const VERSION$1 = "0.1.
|
|
26871
|
+
const VERSION$1 = "0.1.9";
|
|
26872
26872
|
function isWebBuild(directory) {
|
|
26873
26873
|
return fs.existsSync(node_path.join(directory, "index.html")) && !fs.existsSync(node_path.join(directory, "main.ts"));
|
|
26874
26874
|
}
|
|
@@ -26914,11 +26914,12 @@ function whereTheOwnerIs(owner) {
|
|
|
26914
26914
|
${url}
|
|
26915
26915
|
`;
|
|
26916
26916
|
}
|
|
26917
|
-
function runListen() {
|
|
26917
|
+
function runListen(hooks = {}) {
|
|
26918
26918
|
const owner = readLiveDaemonRuntime();
|
|
26919
26919
|
if (owner) {
|
|
26920
|
+
const who = owner.owner === "gui" ? "the desktop app" : "another `gitwarren serve`, in a terminal or as the background service (which `gitwarren service uninstall` stops)";
|
|
26920
26921
|
console.error(
|
|
26921
|
-
`[gitwarren
|
|
26922
|
+
`[gitwarren] GitWarren is already running on this machine (pid ${owner.pid}), as ${who}. Only one can serve a data directory at a time. Quit that first, or use the one that is running` + whereTheOwnerIs(owner)
|
|
26922
26923
|
);
|
|
26923
26924
|
process.exitCode = 1;
|
|
26924
26925
|
return false;
|
|
@@ -26926,7 +26927,7 @@ function runListen() {
|
|
|
26926
26927
|
const webRoot = resolveWebRoot();
|
|
26927
26928
|
if (!webRoot) {
|
|
26928
26929
|
console.error(
|
|
26929
|
-
"[gitwarren
|
|
26930
|
+
"[gitwarren] no web build found. Run `npm run build:web`, or set GITWARREN_WEB_ROOT to the directory holding its index.html."
|
|
26930
26931
|
);
|
|
26931
26932
|
process.exitCode = 1;
|
|
26932
26933
|
return false;
|
|
@@ -26953,10 +26954,10 @@ function runListen() {
|
|
|
26953
26954
|
server.on("error", (error2) => {
|
|
26954
26955
|
if (error2.code === "EADDRINUSE") {
|
|
26955
26956
|
console.error(
|
|
26956
|
-
`[gitwarren
|
|
26957
|
+
`[gitwarren] port ${LINK_SERVER_PORT} is already in use. That port is the one every GitWarren link names, so serving on a different one would mean serving where nobody is looking. Free it and start again.`
|
|
26957
26958
|
);
|
|
26958
26959
|
} else {
|
|
26959
|
-
console.error("[gitwarren
|
|
26960
|
+
console.error("[gitwarren] the server failed", error2);
|
|
26960
26961
|
}
|
|
26961
26962
|
shutdownListen();
|
|
26962
26963
|
process.exitCode = 1;
|
|
@@ -26969,13 +26970,16 @@ function runListen() {
|
|
|
26969
26970
|
linkPort,
|
|
26970
26971
|
owner: "daemon"
|
|
26971
26972
|
});
|
|
26973
|
+
const url = `http://${LINK_SERVER_HOST}:${LINK_SERVER_PORT}/?${TOKEN_PARAM}=${token}`;
|
|
26972
26974
|
console.error(
|
|
26973
|
-
`[gitwarren
|
|
26975
|
+
`[gitwarren] GitWarren is running at
|
|
26974
26976
|
|
|
26975
|
-
|
|
26977
|
+
${url}
|
|
26976
26978
|
|
|
26977
|
-
The token is
|
|
26979
|
+
Open that URL, or run \`gitwarren open\` in another terminal. The token in it is minted for this launch and becomes a cookie the first time the page loads.
|
|
26980
|
+
Press Ctrl-C to stop. To keep GitWarren running in the background instead, from now and at every login, use \`gitwarren service install\`.`
|
|
26978
26981
|
);
|
|
26982
|
+
hooks.onListening?.(url);
|
|
26979
26983
|
});
|
|
26980
26984
|
closeOnExit = () => {
|
|
26981
26985
|
handler.close();
|
|
@@ -27002,10 +27006,11 @@ hand it is a way to see what the protocol says:
|
|
|
27002
27006
|
|
|
27003
27007
|
--listen serves the web view on loopback and prints a URL carrying this
|
|
27004
27008
|
launch's token. It claims this machine's data directory, so it refuses to
|
|
27005
|
-
start while the app is running - see daemon/listen.ts.
|
|
27009
|
+
start while the app is running - see daemon/listen.ts. \`gitwarren serve\` with
|
|
27010
|
+
no flag means this one.
|
|
27006
27011
|
`;
|
|
27007
|
-
function runDaemon(argv) {
|
|
27008
|
-
if (argv.includes("--listen")) return runListen();
|
|
27012
|
+
function runDaemon(argv, hooks = {}) {
|
|
27013
|
+
if (argv.includes("--listen")) return runListen(hooks);
|
|
27009
27014
|
if (!argv.includes("--stdio")) {
|
|
27010
27015
|
console.error(USAGE$4);
|
|
27011
27016
|
return false;
|
|
@@ -27062,6 +27067,99 @@ command = ${tomlString(mcp.command)}
|
|
|
27062
27067
|
}
|
|
27063
27068
|
];
|
|
27064
27069
|
}
|
|
27070
|
+
const WEB_ROOT_ENV_VAR = "GITWARREN_WEB_ROOT";
|
|
27071
|
+
function relaunchEnv() {
|
|
27072
|
+
const env = { [MIGRATIONS_DIR_ENV_VAR]: resolveMigrationsFolder() };
|
|
27073
|
+
const webRoot = resolveWebRoot();
|
|
27074
|
+
if (webRoot) env[WEB_ROOT_ENV_VAR] = webRoot;
|
|
27075
|
+
const dataDir = process.env[DATA_DIR_ENV_VAR]?.trim();
|
|
27076
|
+
if (dataDir) env[DATA_DIR_ENV_VAR] = dataDir;
|
|
27077
|
+
return env;
|
|
27078
|
+
}
|
|
27079
|
+
function findMcpServer(script) {
|
|
27080
|
+
const candidates = [
|
|
27081
|
+
script && node_path.join(node_path.dirname(script), "server.cjs"),
|
|
27082
|
+
script && node_path.join(node_path.dirname(script), "..", "lib", "server.cjs"),
|
|
27083
|
+
node_path.join(process.cwd(), "out", "mcp", "server.cjs")
|
|
27084
|
+
];
|
|
27085
|
+
for (const candidate of candidates) {
|
|
27086
|
+
if (candidate && fs.existsSync(candidate)) return fs.realpathSync(candidate);
|
|
27087
|
+
}
|
|
27088
|
+
return null;
|
|
27089
|
+
}
|
|
27090
|
+
function describeSelf() {
|
|
27091
|
+
const argv1 = process.argv[1];
|
|
27092
|
+
let script = null;
|
|
27093
|
+
if (argv1 !== void 0) {
|
|
27094
|
+
try {
|
|
27095
|
+
const resolved2 = fs.realpathSync(argv1);
|
|
27096
|
+
script = /\.[cm]?ts$/i.test(resolved2) ? null : resolved2;
|
|
27097
|
+
} catch {
|
|
27098
|
+
script = null;
|
|
27099
|
+
}
|
|
27100
|
+
}
|
|
27101
|
+
return { node: fs.realpathSync(process.execPath), script, mcpServer: findMcpServer(script), env: relaunchEnv() };
|
|
27102
|
+
}
|
|
27103
|
+
const BANNER = (what) => [
|
|
27104
|
+
`GitWarren ${what}. Written by the gitwarren command line, so a config or a`,
|
|
27105
|
+
"login item that names this path keeps working when the install moves.",
|
|
27106
|
+
"Generated file - rerun `gitwarren service install` to refresh it, do not edit this."
|
|
27107
|
+
];
|
|
27108
|
+
function bannerFor(what, comment, eol) {
|
|
27109
|
+
return BANNER(what).map((line) => `${comment} ${line}${eol}`).join("");
|
|
27110
|
+
}
|
|
27111
|
+
function posixLauncher(what, self, script) {
|
|
27112
|
+
const exports2 = Object.entries(self.env).map(([name, value]) => `export ${name}=${shellQuote(value)}
|
|
27113
|
+
`).join("");
|
|
27114
|
+
return "#!/bin/sh\n" + bannerFor(what, "#", "\n") + exports2 + `exec ${shellQuote(self.node)} ${shellQuote(script)} "$@"
|
|
27115
|
+
`;
|
|
27116
|
+
}
|
|
27117
|
+
function windowsLauncher(what, self, script) {
|
|
27118
|
+
const sets = Object.entries(self.env).map(([name, value]) => `set "${name}=${value}"\r
|
|
27119
|
+
`).join("");
|
|
27120
|
+
return "@echo off\r\n" + bannerFor(what, "REM", "\r\n") + "setlocal\r\n" + sets + `${cmdQuote(self.node)} ${cmdQuote(script)} %*\r
|
|
27121
|
+
exit /b %ERRORLEVEL%\r
|
|
27122
|
+
`;
|
|
27123
|
+
}
|
|
27124
|
+
function write(path, contents) {
|
|
27125
|
+
let current2 = null;
|
|
27126
|
+
try {
|
|
27127
|
+
current2 = fs.readFileSync(path, "utf8");
|
|
27128
|
+
} catch {
|
|
27129
|
+
}
|
|
27130
|
+
if (current2 !== contents) {
|
|
27131
|
+
fs.mkdirSync(getLauncherDirectory(), { recursive: true });
|
|
27132
|
+
fs.writeFileSync(path, contents, "utf8");
|
|
27133
|
+
}
|
|
27134
|
+
if (process.platform !== "win32") fs.chmodSync(path, 493);
|
|
27135
|
+
return current2 === null;
|
|
27136
|
+
}
|
|
27137
|
+
function writeLaunchers(self) {
|
|
27138
|
+
if (self.script === null) {
|
|
27139
|
+
throw new Error(
|
|
27140
|
+
"this gitwarren is running from a source checkout, so there is no single file a login item could name. Build it first (`npm run build:daemon`), or install GitWarren from a tarball, Homebrew or npm."
|
|
27141
|
+
);
|
|
27142
|
+
}
|
|
27143
|
+
const posix = process.platform !== "win32";
|
|
27144
|
+
const created = [];
|
|
27145
|
+
const cli = getCliLauncherPath();
|
|
27146
|
+
if (write(cli, (posix ? posixLauncher : windowsLauncher)("CLI", self, self.script))) created.push(cli);
|
|
27147
|
+
let mcp = null;
|
|
27148
|
+
if (self.mcpServer !== null) {
|
|
27149
|
+
mcp = getMcpLauncherPath();
|
|
27150
|
+
if (write(mcp, (posix ? posixLauncher : windowsLauncher)("MCP server", self, self.mcpServer)))
|
|
27151
|
+
created.push(mcp);
|
|
27152
|
+
}
|
|
27153
|
+
return { cli, mcp, created };
|
|
27154
|
+
}
|
|
27155
|
+
function ensureLaunchers() {
|
|
27156
|
+
try {
|
|
27157
|
+
const { created } = writeLaunchers(describeSelf());
|
|
27158
|
+
return { created, refused: null };
|
|
27159
|
+
} catch (error2) {
|
|
27160
|
+
return { created: [], refused: error2 instanceof Error ? error2.message : String(error2) };
|
|
27161
|
+
}
|
|
27162
|
+
}
|
|
27065
27163
|
const USAGE$3 = `gitwarren agent-setup [--manual]
|
|
27066
27164
|
|
|
27067
27165
|
Prints the sentence to paste into an agent so it can reach this machine's
|
|
@@ -27089,14 +27187,25 @@ function runAgentSetup(argv) {
|
|
|
27089
27187
|
console.log(snippet.text.trimEnd());
|
|
27090
27188
|
}
|
|
27091
27189
|
}
|
|
27092
|
-
|
|
27190
|
+
const launchers = ensureLaunchers();
|
|
27191
|
+
if (launchers.created.includes(command)) {
|
|
27192
|
+
console.error(`
|
|
27193
|
+
[gitwarren] wrote ${command}, which the sentence above names.`);
|
|
27194
|
+
} else if (!fs.existsSync(command)) {
|
|
27093
27195
|
console.error(
|
|
27094
27196
|
`
|
|
27095
|
-
[gitwarren] no launcher at ${command} yet.
|
|
27197
|
+
[gitwarren] no launcher at ${command} yet, and this run could not write one` + (launchers.refused ? `: ${launchers.refused}` : ".")
|
|
27096
27198
|
);
|
|
27097
27199
|
}
|
|
27098
27200
|
return true;
|
|
27099
27201
|
}
|
|
27202
|
+
function openInBrowser(url, onError) {
|
|
27203
|
+
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
27204
|
+
const child = node_child_process.execFile(command, args, { windowsHide: true }, (error2) => {
|
|
27205
|
+
if (error2) onError(`could not ask ${command} to open a browser (${error2.message})`);
|
|
27206
|
+
});
|
|
27207
|
+
child.unref();
|
|
27208
|
+
}
|
|
27100
27209
|
const REVIEW_TABS = ["conversation", "commits", "files", "browse"];
|
|
27101
27210
|
const HOME = { name: "repositories" };
|
|
27102
27211
|
const HOST_SEGMENT = "h";
|
|
@@ -27217,21 +27326,6 @@ token so the tab is authenticated without anything being copied.
|
|
|
27217
27326
|
--print print the URL instead of opening it - for an ssh session, or for
|
|
27218
27327
|
piping somewhere.
|
|
27219
27328
|
`;
|
|
27220
|
-
function openInBrowser(url) {
|
|
27221
|
-
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
27222
|
-
const child = node_child_process.execFile(command, args, { windowsHide: true }, (error2) => {
|
|
27223
|
-
if (error2) {
|
|
27224
|
-
console.error(
|
|
27225
|
-
`[gitwarren] could not ask ${command} to open a browser (${error2.message}). The URL is:
|
|
27226
|
-
|
|
27227
|
-
${url}
|
|
27228
|
-
`
|
|
27229
|
-
);
|
|
27230
|
-
process.exitCode = 1;
|
|
27231
|
-
}
|
|
27232
|
-
});
|
|
27233
|
-
child.unref();
|
|
27234
|
-
}
|
|
27235
27329
|
function runOpen(argv) {
|
|
27236
27330
|
const print = argv.includes("--print");
|
|
27237
27331
|
const rest = argv.filter((argument) => argument !== "--print");
|
|
@@ -27242,7 +27336,7 @@ function runOpen(argv) {
|
|
|
27242
27336
|
const owner = readLiveDaemonRuntime();
|
|
27243
27337
|
if (owner === null) {
|
|
27244
27338
|
console.error(
|
|
27245
|
-
"[gitwarren]
|
|
27339
|
+
"[gitwarren] GitWarren is not running on this machine, so there is nothing to open. `gitwarren serve` runs it in a terminal; `gitwarren service install` keeps it running in the background."
|
|
27246
27340
|
);
|
|
27247
27341
|
process.exitCode = 1;
|
|
27248
27342
|
return true;
|
|
@@ -27273,91 +27367,16 @@ ${USAGE$2}`);
|
|
|
27273
27367
|
return true;
|
|
27274
27368
|
}
|
|
27275
27369
|
if (print) console.log(url);
|
|
27276
|
-
else
|
|
27370
|
+
else
|
|
27371
|
+
openInBrowser(url, (message) => {
|
|
27372
|
+
console.error(`[gitwarren] ${message}. The URL is:
|
|
27373
|
+
|
|
27374
|
+
${url}
|
|
27375
|
+
`);
|
|
27376
|
+
process.exitCode = 1;
|
|
27377
|
+
});
|
|
27277
27378
|
return true;
|
|
27278
27379
|
}
|
|
27279
|
-
const WEB_ROOT_ENV_VAR = "GITWARREN_WEB_ROOT";
|
|
27280
|
-
function relaunchEnv() {
|
|
27281
|
-
const env = { [MIGRATIONS_DIR_ENV_VAR]: resolveMigrationsFolder() };
|
|
27282
|
-
const webRoot = resolveWebRoot();
|
|
27283
|
-
if (webRoot) env[WEB_ROOT_ENV_VAR] = webRoot;
|
|
27284
|
-
const dataDir = process.env[DATA_DIR_ENV_VAR]?.trim();
|
|
27285
|
-
if (dataDir) env[DATA_DIR_ENV_VAR] = dataDir;
|
|
27286
|
-
return env;
|
|
27287
|
-
}
|
|
27288
|
-
function findMcpServer(script) {
|
|
27289
|
-
const candidates = [
|
|
27290
|
-
script && node_path.join(node_path.dirname(script), "server.cjs"),
|
|
27291
|
-
script && node_path.join(node_path.dirname(script), "..", "lib", "server.cjs"),
|
|
27292
|
-
node_path.join(process.cwd(), "out", "mcp", "server.cjs")
|
|
27293
|
-
];
|
|
27294
|
-
for (const candidate of candidates) {
|
|
27295
|
-
if (candidate && fs.existsSync(candidate)) return fs.realpathSync(candidate);
|
|
27296
|
-
}
|
|
27297
|
-
return null;
|
|
27298
|
-
}
|
|
27299
|
-
function describeSelf() {
|
|
27300
|
-
const argv1 = process.argv[1];
|
|
27301
|
-
let script = null;
|
|
27302
|
-
if (argv1 !== void 0) {
|
|
27303
|
-
try {
|
|
27304
|
-
const resolved2 = fs.realpathSync(argv1);
|
|
27305
|
-
script = /\.[cm]?ts$/i.test(resolved2) ? null : resolved2;
|
|
27306
|
-
} catch {
|
|
27307
|
-
script = null;
|
|
27308
|
-
}
|
|
27309
|
-
}
|
|
27310
|
-
return { node: fs.realpathSync(process.execPath), script, mcpServer: findMcpServer(script), env: relaunchEnv() };
|
|
27311
|
-
}
|
|
27312
|
-
const BANNER = (what) => [
|
|
27313
|
-
`GitWarren ${what}. Written by \`gitwarren service install\`, so a config or a`,
|
|
27314
|
-
"login item that names this path keeps working when the install moves.",
|
|
27315
|
-
"Generated file - run the command again, do not edit this."
|
|
27316
|
-
];
|
|
27317
|
-
function bannerFor(what, comment, eol) {
|
|
27318
|
-
return BANNER(what).map((line) => `${comment} ${line}${eol}`).join("");
|
|
27319
|
-
}
|
|
27320
|
-
function posixLauncher(what, self, script) {
|
|
27321
|
-
const exports2 = Object.entries(self.env).map(([name, value]) => `export ${name}=${shellQuote(value)}
|
|
27322
|
-
`).join("");
|
|
27323
|
-
return "#!/bin/sh\n" + bannerFor(what, "#", "\n") + exports2 + `exec ${shellQuote(self.node)} ${shellQuote(script)} "$@"
|
|
27324
|
-
`;
|
|
27325
|
-
}
|
|
27326
|
-
function windowsLauncher(what, self, script) {
|
|
27327
|
-
const sets = Object.entries(self.env).map(([name, value]) => `set "${name}=${value}"\r
|
|
27328
|
-
`).join("");
|
|
27329
|
-
return "@echo off\r\n" + bannerFor(what, "REM", "\r\n") + "setlocal\r\n" + sets + `${cmdQuote(self.node)} ${cmdQuote(script)} %*\r
|
|
27330
|
-
exit /b %ERRORLEVEL%\r
|
|
27331
|
-
`;
|
|
27332
|
-
}
|
|
27333
|
-
function write(path, contents) {
|
|
27334
|
-
let current2 = null;
|
|
27335
|
-
try {
|
|
27336
|
-
current2 = fs.readFileSync(path, "utf8");
|
|
27337
|
-
} catch {
|
|
27338
|
-
}
|
|
27339
|
-
if (current2 !== contents) {
|
|
27340
|
-
fs.mkdirSync(getLauncherDirectory(), { recursive: true });
|
|
27341
|
-
fs.writeFileSync(path, contents, "utf8");
|
|
27342
|
-
}
|
|
27343
|
-
if (process.platform !== "win32") fs.chmodSync(path, 493);
|
|
27344
|
-
}
|
|
27345
|
-
function writeLaunchers(self) {
|
|
27346
|
-
if (self.script === null) {
|
|
27347
|
-
throw new Error(
|
|
27348
|
-
"this gitwarren is running from a source checkout, so there is no single file a login item could name. Build it first (`npm run build:daemon`), or install GitWarren from a tarball, Homebrew or npm."
|
|
27349
|
-
);
|
|
27350
|
-
}
|
|
27351
|
-
const posix = process.platform !== "win32";
|
|
27352
|
-
const cli = getCliLauncherPath();
|
|
27353
|
-
write(cli, (posix ? posixLauncher : windowsLauncher)("CLI", self, self.script));
|
|
27354
|
-
let mcp = null;
|
|
27355
|
-
if (self.mcpServer !== null) {
|
|
27356
|
-
mcp = getMcpLauncherPath();
|
|
27357
|
-
write(mcp, (posix ? posixLauncher : windowsLauncher)("MCP server", self, self.mcpServer));
|
|
27358
|
-
}
|
|
27359
|
-
return { cli, mcp };
|
|
27360
|
-
}
|
|
27361
27380
|
const LAUNCHD_LABEL = "com.gitwarren.daemon";
|
|
27362
27381
|
const SYSTEMD_UNIT = "gitwarren.service";
|
|
27363
27382
|
const WINDOWS_TASK = "GitWarren daemon";
|
|
@@ -27416,11 +27435,18 @@ const USAGE$1 = `gitwarren service install [--no-login-item]
|
|
|
27416
27435
|
gitwarren service uninstall
|
|
27417
27436
|
gitwarren service status
|
|
27418
27437
|
|
|
27419
|
-
install
|
|
27420
|
-
|
|
27421
|
-
|
|
27422
|
-
|
|
27423
|
-
|
|
27438
|
+
install keeps GitWarren running in the background: it starts now, and again
|
|
27439
|
+
at every login, so \`gitwarren open\` and the links an agent hands
|
|
27440
|
+
you always have something to open. Registers a LaunchAgent on
|
|
27441
|
+
macOS, a systemd user unit on Linux, an at-logon task on Windows.
|
|
27442
|
+
Also writes ~/.gitwarren/bin/gitwarren and gitwarren-mcp, the
|
|
27443
|
+
stable paths the app and an agent start this install by.
|
|
27444
|
+
--no-login-item writes those two files and registers nothing,
|
|
27445
|
+
for a machine that is only ever reached from another one.
|
|
27446
|
+
uninstall stops the background GitWarren and removes the login item. The two
|
|
27447
|
+
files in ~/.gitwarren/bin stay, because an agent config may name
|
|
27448
|
+
one; delete that directory by hand to remove them.
|
|
27449
|
+
status what is registered, what is running, and where the data is.
|
|
27424
27450
|
`;
|
|
27425
27451
|
function macLogPath() {
|
|
27426
27452
|
return node_path.join(node_os.homedir(), "Library", "Logs", "GitWarren", "daemon.log");
|
|
@@ -27471,6 +27497,7 @@ function registerLoginItem(launcher) {
|
|
|
27471
27497
|
"ONLOGON",
|
|
27472
27498
|
"/F"
|
|
27473
27499
|
]);
|
|
27500
|
+
ask("schtasks", ["/Run", "/TN", WINDOWS_TASK]);
|
|
27474
27501
|
return `Scheduled Task "${WINDOWS_TASK}"`;
|
|
27475
27502
|
}
|
|
27476
27503
|
if (path === null) throw new Error("no login item path for this platform");
|
|
@@ -27526,14 +27553,22 @@ function install(argv) {
|
|
|
27526
27553
|
);
|
|
27527
27554
|
}
|
|
27528
27555
|
if (argv.includes("--no-login-item")) {
|
|
27529
|
-
console.log("
|
|
27556
|
+
console.log("Nothing was registered to start at login (--no-login-item).");
|
|
27530
27557
|
return true;
|
|
27531
27558
|
}
|
|
27532
|
-
console.log(registerLoginItem(cli) + " will run `gitwarren serve --listen` at login.");
|
|
27533
27559
|
const owner = readLiveDaemonRuntime();
|
|
27560
|
+
console.log(`registered ${registerLoginItem(cli)}`);
|
|
27534
27561
|
if (owner?.owner === "gui") {
|
|
27535
27562
|
console.log(
|
|
27536
|
-
"\
|
|
27563
|
+
"\nThe desktop app is running and owns this data directory, so the background GitWarren has stood aside for now. Only one can serve at a time; the login item does its job on the logins where you do not open the app."
|
|
27564
|
+
);
|
|
27565
|
+
} else if (owner !== null) {
|
|
27566
|
+
console.log(
|
|
27567
|
+
"\nA `gitwarren serve` is already running in a terminal, so the background GitWarren has stood aside for now. Stop that one and it takes over at the next login, or straight away if you run `gitwarren service install` again."
|
|
27568
|
+
);
|
|
27569
|
+
} else {
|
|
27570
|
+
console.log(
|
|
27571
|
+
"\nGitWarren is now running in the background, and will start again when you log in. `gitwarren open` opens it in your browser; `gitwarren service uninstall` undoes all of this."
|
|
27537
27572
|
);
|
|
27538
27573
|
}
|
|
27539
27574
|
console.log(`
|
|
@@ -27549,17 +27584,19 @@ function runService(argv) {
|
|
|
27549
27584
|
case "uninstall":
|
|
27550
27585
|
console.log(`removed ${removeLoginItem()}`);
|
|
27551
27586
|
console.log(
|
|
27552
|
-
"The
|
|
27587
|
+
"GitWarren no longer runs in the background or starts at login. `gitwarren serve` still runs it in a terminal. The two files in ~/.gitwarren/bin were left alone, because an agent config may name one; delete that directory to remove them."
|
|
27553
27588
|
);
|
|
27554
27589
|
return true;
|
|
27555
27590
|
case "status": {
|
|
27556
27591
|
const owner = readLiveDaemonRuntime();
|
|
27557
|
-
|
|
27558
|
-
console.log(`
|
|
27592
|
+
const mcp = getMcpLauncherPath();
|
|
27593
|
+
console.log(`Login item: ${loginItemState()}`);
|
|
27594
|
+
console.log(`CLI launcher: ${fs.existsSync(getCliLauncherPath()) ? getCliLauncherPath() : "not written"}`);
|
|
27595
|
+
console.log(`MCP launcher: ${fs.existsSync(mcp) ? mcp : "not written - `gitwarren agent-setup` writes it"}`);
|
|
27559
27596
|
console.log(
|
|
27560
|
-
`
|
|
27597
|
+
`Running now: ${owner === null ? "nothing - `gitwarren serve` runs it in this terminal" : `${owner.owner === "gui" ? "the desktop app" : "gitwarren serve"} (pid ${owner.pid})`}`
|
|
27561
27598
|
);
|
|
27562
|
-
console.log(`Data:
|
|
27599
|
+
console.log(`Data: ${getDataDirectory()}`);
|
|
27563
27600
|
return true;
|
|
27564
27601
|
}
|
|
27565
27602
|
default:
|
|
@@ -27572,30 +27609,75 @@ function runService(argv) {
|
|
|
27572
27609
|
return true;
|
|
27573
27610
|
}
|
|
27574
27611
|
}
|
|
27575
|
-
const VERSION = "0.1.
|
|
27576
|
-
const USAGE = `gitwarren -
|
|
27612
|
+
const VERSION = "0.1.9";
|
|
27613
|
+
const USAGE = `gitwarren - code review for your own git repositories, in a browser tab
|
|
27614
|
+
|
|
27615
|
+
Run it now
|
|
27616
|
+
gitwarren serve run GitWarren in this terminal and print its URL
|
|
27617
|
+
(--open also opens the browser; Ctrl-C stops it)
|
|
27618
|
+
gitwarren open [link] open the running GitWarren in your browser
|
|
27619
|
+
|
|
27620
|
+
Keep it running
|
|
27621
|
+
gitwarren service install run GitWarren in the background, from now and at
|
|
27622
|
+
every login
|
|
27623
|
+
gitwarren service uninstall stop that, and remove the login item
|
|
27624
|
+
gitwarren service status what is running, and where the data is
|
|
27577
27625
|
|
|
27578
|
-
|
|
27579
|
-
gitwarren
|
|
27580
|
-
|
|
27581
|
-
|
|
27582
|
-
gitwarren
|
|
27583
|
-
|
|
27584
|
-
gitwarren agent-setup print the sentence that points an agent here
|
|
27626
|
+
Let a coding agent in
|
|
27627
|
+
gitwarren agent-setup print the one sentence to give an agent so it can
|
|
27628
|
+
reach this GitWarren over MCP
|
|
27629
|
+
|
|
27630
|
+
gitwarren serve --stdio answer GitWarren's protocol on stdin/stdout (this
|
|
27631
|
+
is what another machine's GitWarren spawns)
|
|
27585
27632
|
gitwarren --version
|
|
27586
27633
|
|
|
27587
|
-
Run a subcommand with
|
|
27634
|
+
Run a subcommand with --help to see its own usage.
|
|
27588
27635
|
|
|
27589
|
-
|
|
27590
|
-
\`gitwarren open\` carries that token for you
|
|
27636
|
+
GitWarren serves on 127.0.0.1 only, behind a token minted per launch, and
|
|
27637
|
+
\`gitwarren open\` carries that token for you. Reviews live in one SQLite file
|
|
27638
|
+
on this machine - serving, the background service and the MCP server all read
|
|
27639
|
+
the same one, so an agent can use GitWarren whether or not it is being served.
|
|
27591
27640
|
`;
|
|
27641
|
+
const SERVE_USAGE = `gitwarren serve [--open]
|
|
27642
|
+
gitwarren serve --stdio
|
|
27643
|
+
|
|
27644
|
+
Runs GitWarren in this terminal, serving the review UI on 127.0.0.1 and printing
|
|
27645
|
+
a URL that carries this launch's token. Ctrl-C stops it.
|
|
27646
|
+
|
|
27647
|
+
--open also open that URL in your browser
|
|
27648
|
+
--stdio answer GitWarren's protocol on stdin/stdout instead. This is what a
|
|
27649
|
+
GitWarren on another machine spawns over ssh or wsl.exe; by hand it
|
|
27650
|
+
is a way to see what the protocol says.
|
|
27651
|
+
|
|
27652
|
+
Serving also writes ~/.gitwarren/bin/gitwarren-mcp, the command a coding agent
|
|
27653
|
+
starts the MCP server with - see \`gitwarren agent-setup\`.
|
|
27654
|
+
`;
|
|
27655
|
+
function afterListening(url, open2) {
|
|
27656
|
+
const launchers = ensureLaunchers();
|
|
27657
|
+
if (launchers.refused) {
|
|
27658
|
+
console.error(
|
|
27659
|
+
`
|
|
27660
|
+
[gitwarren] no launcher was written to ~/.gitwarren/bin, so an agent has nothing to start yet: ${launchers.refused}`
|
|
27661
|
+
);
|
|
27662
|
+
} else if (launchers.created.length > 0) {
|
|
27663
|
+
console.error(`
|
|
27664
|
+
[gitwarren] wrote ${launchers.created.join(" and ")}`);
|
|
27665
|
+
}
|
|
27666
|
+
if (open2) {
|
|
27667
|
+
openInBrowser(url, (message) => console.error(`[gitwarren] ${message}. The URL is above.`));
|
|
27668
|
+
}
|
|
27669
|
+
}
|
|
27592
27670
|
function runCli(argv) {
|
|
27593
27671
|
const [command, ...rest] = argv;
|
|
27594
27672
|
switch (command) {
|
|
27595
|
-
case "serve":
|
|
27596
|
-
|
|
27597
|
-
|
|
27598
|
-
|
|
27673
|
+
case "serve": {
|
|
27674
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
27675
|
+
console.log(SERVE_USAGE);
|
|
27676
|
+
return true;
|
|
27677
|
+
}
|
|
27678
|
+
const argv2 = rest.includes("--stdio") || rest.includes("--listen") ? rest : [...rest, "--listen"];
|
|
27679
|
+
return runDaemon(argv2, { onListening: (url) => afterListening(url, rest.includes("--open")) });
|
|
27680
|
+
}
|
|
27599
27681
|
case "open":
|
|
27600
27682
|
return runOpen(rest);
|
|
27601
27683
|
case "service":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitwarren",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Code review for your own machines and your own agents, served on loopback. No Electron, no account.",
|
|
5
5
|
"keywords": ["code-review", "git", "diff", "mcp", "local-first", "ssh", "wsl", "tailscale", "self-hosted"],
|
|
6
6
|
"homepage": "https://github.com/klarluft/gitwarren-app",
|