gitwarren 0.1.12 → 0.1.13

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
@@ -13,7 +13,7 @@ npx gitwarren serve --open
13
13
 
14
14
  That serves the review UI on `127.0.0.1:41427`, prints a URL carrying a token
15
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
16
+ review the diff. Ctrl-C stops it. Needs Node 22.14 or newer; on a machine without
17
17
  Node, the Homebrew formula and the install script in the
18
18
  [app repository](https://github.com/klarluft/gitwarren-app#the-gitwarren-command-line)
19
19
  bring their own.
@@ -50,6 +50,13 @@ URL an agent hands out, and lands on that review rather than the home screen.
50
50
 
51
51
  ## Letting an agent in
52
52
 
53
+ The quickest way is the plugin, which brings the server and a note that teaches
54
+ the agent when to open a review. In Claude Code, `/plugin marketplace add
55
+ klarluft/gitwarren-app` then `/plugin install gitwarren@gitwarren`; in Gemini
56
+ CLI, `gemini extensions install https://github.com/klarluft/gitwarren-app`;
57
+ Cursor, Codex, VS Code and Kiro read the same repository from their plugin
58
+ screens. The plugin finds this install by itself.
59
+
53
60
  Every install carries GitWarren's MCP server. What an agent needs is one
54
61
  stable command to start it with, and that is `~/.gitwarren/bin/gitwarren-mcp` —
55
62
  the same path on every machine, written by `gitwarren serve`, `gitwarren
package/lib/gitwarren.cjs CHANGED
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
+ const node_child_process = require("node:child_process");
2
3
  const node_module = require("node:module");
3
4
  const fs = require("node:fs");
4
5
  const node_path = require("node:path");
5
6
  const node_os = require("node:os");
6
7
  const Client = require("better-sqlite3");
7
8
  const crypto$1 = require("node:crypto");
8
- const node_child_process = require("node:child_process");
9
9
  const node_stream = require("node:stream");
10
10
  const require$$0$3 = require("events");
11
11
  const require$$1$1 = require("https");
@@ -10301,7 +10301,7 @@ function cmdQuote(value) {
10301
10301
  if (value.includes('"')) throw new Error(`a Windows path cannot contain a quote: ${value}`);
10302
10302
  return `"${value}"`;
10303
10303
  }
10304
- const APP_VERSION = "0.1.12";
10304
+ const APP_VERSION = "0.1.13";
10305
10305
  const REMOTE_ROOT = "$HOME/.gitwarren";
10306
10306
  const runOnHost = (route2, options) => {
10307
10307
  switch (route2.kind) {
@@ -26869,7 +26869,7 @@ Connection: close\r
26869
26869
  }
26870
26870
  };
26871
26871
  }
26872
- const VERSION$1 = "0.1.12";
26872
+ const VERSION$1 = "0.1.13";
26873
26873
  function isWebBuild(directory) {
26874
26874
  return fs.existsSync(node_path.join(directory, "index.html")) && !fs.existsSync(node_path.join(directory, "main.ts"));
26875
26875
  }
@@ -26885,7 +26885,7 @@ function resolveWebRoot() {
26885
26885
  }
26886
26886
  return null;
26887
26887
  }
26888
- function describeInstall(linkPort) {
26888
+ function describeInstall(linkPort, servedFor) {
26889
26889
  const mcp = describeMcpLaunch();
26890
26890
  return {
26891
26891
  version: VERSION$1,
@@ -26895,7 +26895,11 @@ function describeInstall(linkPort) {
26895
26895
  dataDirectory: getDataDirectory(),
26896
26896
  databasePath: getDatabasePath(),
26897
26897
  linkPort,
26898
- mcp
26898
+ mcp,
26899
+ // Spread rather than assigned, so the field is absent - not `undefined` -
26900
+ // in the JSON the other two servers send, which keeps their answers
26901
+ // byte-for-byte what they were.
26902
+ ...servedFor === void 0 ? {} : { servedFor }
26899
26903
  };
26900
26904
  }
26901
26905
  function whereTheOwnerIs(owner) {
@@ -26943,7 +26947,7 @@ function runListen(hooks = {}) {
26943
26947
  mount: "/",
26944
26948
  staticRoot: webRoot,
26945
26949
  token,
26946
- appInfo: () => describeInstall(linkPort),
26950
+ appInfo: () => describeInstall(linkPort, hooks.servedFor),
26947
26951
  tailnet: tailnetGate
26948
26952
  });
26949
26953
  const server = node_http.createServer((request, response) => {
@@ -27039,6 +27043,11 @@ function runDaemon(argv, hooks = {}) {
27039
27043
  return true;
27040
27044
  }
27041
27045
  const MCP_SERVER_NAME = "gitwarren";
27046
+ const PLUGIN_REPOSITORY = "klarluft/gitwarren-app";
27047
+ const PLUGIN_INSTALL = {
27048
+ /** Two commands, typed into Claude Code itself. */
27049
+ claudeCode: [`/plugin marketplace add ${PLUGIN_REPOSITORY}`, "/plugin install gitwarren@gitwarren"]
27050
+ };
27042
27051
  function agentSetupPrompt(mcp) {
27043
27052
  return `Set up the GitWarren MCP server for yourself. It speaks MCP over stdio and is started with the command ${mcp.command} (no arguments, no environment). Register it under the name "${MCP_SERVER_NAME}" in your own MCP configuration, then call its agent_identity tool to confirm it works.`;
27044
27053
  }
@@ -27197,6 +27206,10 @@ function runAgentSetup(argv) {
27197
27206
  console.log(snippet.text.trimEnd());
27198
27207
  }
27199
27208
  }
27209
+ console.error(
27210
+ `
27211
+ [gitwarren] In Claude Code the plugin does this and teaches the agent when to review: ` + PLUGIN_INSTALL.claudeCode.join(", then ")
27212
+ );
27200
27213
  const launchers = ensureLaunchers();
27201
27214
  if (launchers.created.includes(command)) {
27202
27215
  console.error(`
@@ -27619,7 +27632,7 @@ function runService(argv) {
27619
27632
  return true;
27620
27633
  }
27621
27634
  }
27622
- const VERSION = "0.1.12";
27635
+ const VERSION = "0.1.13";
27623
27636
  const USAGE = `gitwarren - code review for your own git repositories, in a browser tab
27624
27637
 
27625
27638
  Run it now
@@ -27711,12 +27724,42 @@ function runMcp(argv) {
27711
27724
  );
27712
27725
  return false;
27713
27726
  }
27727
+ if (!nativeAddonLoads(server)) return false;
27714
27728
  process.removeAllListeners("SIGINT");
27715
27729
  process.removeAllListeners("SIGTERM");
27716
27730
  if (argv.includes("--serve")) serveBesideMcp();
27717
27731
  node_module.createRequire(server)(server);
27718
27732
  return true;
27719
27733
  }
27734
+ const NODE_API_NEEDED = 10;
27735
+ const NODE_NEEDED = "Node 22.14 or newer, or Node 24";
27736
+ function nativeAddonLoads(server) {
27737
+ if (Number(process.versions.napi) < NODE_API_NEEDED) {
27738
+ console.error(
27739
+ `[gitwarren] Node ${process.versions.node} is too old for GitWarren's SQLite module, which needs ${NODE_NEEDED} (Node-API ${NODE_API_NEEDED}; this one has ${process.versions.napi}). Run this with a newer Node.`
27740
+ );
27741
+ process.exitCode = 1;
27742
+ return false;
27743
+ }
27744
+ let addon;
27745
+ try {
27746
+ addon = node_module.createRequire(server).resolve("better-sqlite3");
27747
+ } catch {
27748
+ return true;
27749
+ }
27750
+ const probe = node_child_process.spawnSync(
27751
+ process.execPath,
27752
+ ["-e", "new (require(process.argv[1]))(':memory:').prepare('select 1').get()", addon],
27753
+ { stdio: ["ignore", "ignore", "pipe"], timeout: 1e4, encoding: "utf8" }
27754
+ );
27755
+ if (probe.status === 0) return true;
27756
+ const how = probe.signal ? `crashed with ${probe.signal}` : probe.error ? `could not be started: ${probe.error.message}` : `exited with ${probe.status}: ${probe.stderr.trim().split("\n").pop() ?? ""}`;
27757
+ console.error(
27758
+ `[gitwarren] the SQLite module at ${addon} ${how} under Node ${process.versions.node}. GitWarren needs ${NODE_NEEDED}. If this Node is newer than that, the install itself is broken: if it came from npx, remove its gitwarren entry under the npm cache's _npx directory and run again.`
27759
+ );
27760
+ process.exitCode = 1;
27761
+ return false;
27762
+ }
27720
27763
  function serveBesideMcp() {
27721
27764
  const owner = readLiveDaemonRuntime();
27722
27765
  if (owner) {
@@ -27725,7 +27768,7 @@ function serveBesideMcp() {
27725
27768
  );
27726
27769
  return;
27727
27770
  }
27728
- if (!runDaemon(["--listen"], { brief: true })) {
27771
+ if (!runDaemon(["--listen"], { brief: true, servedFor: "agent" })) {
27729
27772
  process.exitCode = void 0;
27730
27773
  console.error(
27731
27774
  "[gitwarren] the review page could not be served, so links will not open until a GitWarren is started on this machine. The MCP server is running regardless."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitwarren",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
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",
@@ -12,6 +12,6 @@
12
12
  "type": "module",
13
13
  "bin": { "gitwarren": "bin/gitwarren.mjs" },
14
14
  "files": ["bin", "lib", "drizzle", "web", "README.md"],
15
- "engines": { "node": ">=22" },
15
+ "engines": { "node": ">=22.14" },
16
16
  "dependencies": { "better-sqlite3": "^13.0.3" }
17
17
  }
@@ -1,2 +1,2 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./main-BlPq7_RT.js","./routes-DFoqib84.js","./main-CPEXHyPM.css"])))=>i.map(i=>d[i]);
2
- import{a as U,A as B,i as F,b as j,r as H,c as b,e as I,d as D,l as M,p as N,f as q,H as G,h as J,o as x}from"./routes-DFoqib84.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))n(r);new MutationObserver(r=>{for(const i of r)if(i.type==="childList")for(const c of i.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&n(c)}).observe(document,{childList:!0,subtree:!0});function o(r){const i={};return r.integrity&&(i.integrity=r.integrity),r.referrerPolicy&&(i.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?i.credentials="include":r.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function n(r){if(r.ep)return;r.ep=!0;const i=o(r);fetch(r.href,i)}})();const K="modulepreload",V=function(e,t){return new URL(e,t).href},k={},X=function(t,o,n){let r=Promise.resolve();if(o&&o.length>0){let w=function(a){return Promise.all(a.map(p=>Promise.resolve(p).then(h=>({status:"fulfilled",value:h}),h=>({status:"rejected",reason:h}))))};const c=document.getElementsByTagName("link"),f=document.querySelector("meta[property=csp-nonce]"),y=f?.nonce||f?.getAttribute("nonce");r=w(o.map(a=>{if(a=V(a,n),a in k)return;k[a]=!0;const p=a.endsWith(".css"),h=p?'[rel="stylesheet"]':"";if(n)for(let l=c.length-1;l>=0;l--){const u=c[l];if(u.href===a&&(!p||u.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${a}"]${h}`))return;const s=document.createElement("link");if(s.rel=p?"stylesheet":K,p||(s.as="script"),s.crossOrigin="",s.href=a,y&&s.setAttribute("nonce",y),document.head.appendChild(s),p)return new Promise((l,u)=>{s.addEventListener("load",l),s.addEventListener("error",()=>u(new Error(`Unable to preload CSS for ${a}`)))})}))}function i(c){const f=new Event("vite:preloadError",{cancelable:!0});if(f.payload=c,window.dispatchEvent(f),!f.defaultPrevented)throw c}return r.then(c=>{for(const f of c||[])f.status==="rejected"&&i(f.reason);return t().catch(i)})},P="/gitwarren",O={socket:`${P}/socket`,appInfo:`${P}/app-info`,attachments:`${P}/attachments/`};function Y(e,t){const o=U(e);if(o===null)return e;const n=`${O.attachments}${o}`;return t===void 0?n:`${n}?${B}=${encodeURIComponent(t)}`}const $=32768;function z(e){let t="";for(let o=0;o<e.length;o+=$)t+=String.fromCharCode(...e.subarray(o,o+$));return btoa(t)}function A(e){const{params:t}=e;if(typeof t!="object"||t===null)return JSON.stringify(e);let o=null;for(const[n,r]of Object.entries(t)){const i=Q(r);i!==null&&(o??={...t},o[n]=z(i))}return JSON.stringify(o===null?e:{...e,params:o})}function Q(e){return e instanceof ArrayBuffer?new Uint8Array(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength):null}const C=[250,500,1e3,2e3,5e3];function Z(){return`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}${O.socket}`}function ee(){let e=null,t=0,o=1;const n=new Map,r=[],i=new Set,c=new Set,f=s=>{for(const l of i)l(s)},y=s=>{e?.readyState===WebSocket.OPEN?e.send(A(s)):r.push(s)},w=()=>{const s=[...n.values()];n.clear();for(const l of s)l.reject(new b("INTERNAL","The connection to GitWarren was lost before this finished. Nothing was retried; try again."))},a=()=>{const s=new WebSocket(Z());e=s,s.addEventListener("open",()=>{t=0;const u=r.splice(0,r.length);for(const d of u)s.send(A(d));f(!0)}),s.addEventListener("message",u=>{let d;try{d=JSON.parse(u.data)}catch{console.error("[carrier] a message from GitWarren was not JSON");return}if(typeof d!="object"||d===null)return;if(j(d)){for(const E of c)E(d);return}const g=d;if(typeof g.id!="number")return;const m=n.get(g.id);m&&(n.delete(g.id),m.resolve(g))});const l=()=>{if(e!==s)return;e=null,w(),f(!1);const u=C[Math.min(t,C.length-1)];t+=1,window.setTimeout(a,u)};s.addEventListener("close",l),s.addEventListener("error",()=>{console.error("[carrier] the connection to GitWarren failed")})};a();const p=new Map,h=(s,l,u)=>{const d=o++;return new Promise((m,E)=>{n.set(d,{resolve:m,reject:E}),y({id:d,method:s,params:l,...u===void 0?{}:{host:u}})}).then(m=>H(m))};return{request(s,l,u){if(!F(s))return h(s,l,u);const d=`${u??""}:${s}:${JSON.stringify(l??null)}`,g=p.get(d);if(g)return g;const m=h(s,l,u).finally(()=>p.delete(d));return p.set(d,m),m},connected:()=>e?.readyState===WebSocket.OPEN,onConnectionChange(s){return i.add(s),()=>i.delete(s)},onEvent(s){return c.add(s),()=>c.delete(s)}}}const R=()=>{};function v(e,t){return Promise.reject(new b("FORBIDDEN",`${e} is not available in a browser tab. ${t}`))}function te(e,t){const o=e.find(n=>n.instanceId===t);if(!o)throw new b("NOT_FOUND",`This GitWarren does not know a host with id ${t}. It may have been removed, or the link may have come from somewhere else.`);return o}async function ne(){const e=await fetch(O.appInfo,{credentials:"same-origin",headers:{Accept:"application/json"}});if(!e.ok)throw new b("INTERNAL",`GitWarren could not describe this install (HTTP ${e.status}).`);return await e.json()}function T(){const e=M();return{editors:e,defaultId:e[0]?.id??null}}const _={state:"unsupported",reason:"GitWarren in a browser is updated by whatever installed it - Homebrew, npm, or the tarball it was unpacked from."};function oe(){return new Promise(e=>{const t=document.createElement("input");t.type="file",t.accept="image/png,image/jpeg,image/gif,image/webp",t.style.position="fixed",t.style.left="-9999px",document.body.append(t);let o=!1;const n=i=>{o||(o=!0,window.removeEventListener("focus",r),t.remove(),e(i))},r=()=>{window.setTimeout(()=>n(t.files?.[0]??null),300)};t.addEventListener("change",()=>n(t.files?.[0]??null)),t.addEventListener("cancel",()=>n(null)),window.addEventListener("focus",r),t.click()})}function re(e){return{capabilities:{pickDirectory:!1,revealPath:!1,openAtLogin:!1},system:{pickDirectory:()=>Promise.resolve(null),revealPath:()=>v("Revealing a folder","Copy the path and open it yourself."),appInfo:ne,editors:()=>Promise.resolve(T()),getOpenAtLogin:()=>Promise.resolve(!1),setOpenAtLogin:()=>v("Starting at login","Run `gitwarren service install` on this machine.")},updates:{getStatus:()=>Promise.resolve(_),check:()=>Promise.resolve(_),installNow:()=>v("Installing an update","Update GitWarren the way you installed it."),subscribe:()=>R},connection:{connected:()=>e.connected(),subscribe:n=>e.onConnectionChange(n)},navigation:{onDeepLink:()=>R},pickAttachment:async n=>{const r=await oe();return r===null?null:await e.request("attachments.ingest",{bytes:await r.arrayBuffer(),originalName:r.name},n)},attachmentSrc:Y,openInEditor:async n=>{const{id:r,path:i,changes:c,line:f,editorId:y,host:w}=n,a=I(y)??I(T().defaultId??void 0);if(!a?.url)return v("Opening a file in an editor","No editor with a URL scheme was chosen for this tab.");if(w!==void 0&&!a.remoteUrl)return v("Opening a file on another machine",`${a.label} has no way to open a file it cannot see. VS Code, Cursor and Windsurf do.`);const p=await e.request("reviews.filePath",{id:r,path:i,changes:c},w),h=w===void 0?void 0:D(te(await e.request("hosts.list",void 0),w));window.location.href=h===void 0?a.url(p,f??1):a.remoteUrl(h,p,f??1)}}}const se="gitwarren",L=`${se}://`,W="review",S="h=";function ie(e){if(!e||e.slice(0,L.length).toLowerCase()!==L)return null;const o=(e.slice(L.length).split(/[?#]/)[0]??"").split("/").filter(Boolean),n=o[0]?.toLowerCase();if(n===W)return N(`#/reviews/${o.slice(1).join("/")}`);if(n!==void 0&&q(n)){const r=o.slice(1);return r.length===0?{name:"repositories",host:n}:r[0]?.toLowerCase()!==W?null:N(`#/h/${n}/reviews/${r.slice(1).join("/")}`)}return null}function ae(e){return e.replace(/^#/,"").startsWith(S)}function ce(e,t){const o=e.replace(/^#/,"");if(!o.startsWith(S))return null;const n=ie(`gitwarren://${o.slice(S.length)}`);if(!n)return G;if(n.host===void 0)return n;if(n.host===t){const{host:r,...i}=n;return i}return n}function le(e){return{request:(t,o,n)=>x(()=>e.request(t,o,n)),onEvent:t=>e.onEvent(t)}}function ue(){const e=ee(),t={carrier:le(e),shell:re(e)};return window.gitwarren=t,t}async function fe(e){if(!ae(window.location.hash))return;const{instanceId:t}=await e.shell.system.appInfo(),o=ce(window.location.hash,t);o&&window.location.replace(J(o))}const de=ue();await fe(de);await X(()=>import("./main-BlPq7_RT.js"),__vite__mapDeps([0,1,2]),import.meta.url);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./main-1gX2-1gC.js","./routes-DFoqib84.js","./main-CPEXHyPM.css"])))=>i.map(i=>d[i]);
2
+ import{a as U,A as B,i as F,b as j,r as H,c as b,e as I,d as D,l as M,p as N,f as q,H as G,h as J,o as x}from"./routes-DFoqib84.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))n(r);new MutationObserver(r=>{for(const i of r)if(i.type==="childList")for(const c of i.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&n(c)}).observe(document,{childList:!0,subtree:!0});function o(r){const i={};return r.integrity&&(i.integrity=r.integrity),r.referrerPolicy&&(i.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?i.credentials="include":r.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function n(r){if(r.ep)return;r.ep=!0;const i=o(r);fetch(r.href,i)}})();const K="modulepreload",V=function(e,t){return new URL(e,t).href},k={},X=function(t,o,n){let r=Promise.resolve();if(o&&o.length>0){let w=function(a){return Promise.all(a.map(p=>Promise.resolve(p).then(h=>({status:"fulfilled",value:h}),h=>({status:"rejected",reason:h}))))};const c=document.getElementsByTagName("link"),f=document.querySelector("meta[property=csp-nonce]"),y=f?.nonce||f?.getAttribute("nonce");r=w(o.map(a=>{if(a=V(a,n),a in k)return;k[a]=!0;const p=a.endsWith(".css"),h=p?'[rel="stylesheet"]':"";if(n)for(let l=c.length-1;l>=0;l--){const u=c[l];if(u.href===a&&(!p||u.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${a}"]${h}`))return;const s=document.createElement("link");if(s.rel=p?"stylesheet":K,p||(s.as="script"),s.crossOrigin="",s.href=a,y&&s.setAttribute("nonce",y),document.head.appendChild(s),p)return new Promise((l,u)=>{s.addEventListener("load",l),s.addEventListener("error",()=>u(new Error(`Unable to preload CSS for ${a}`)))})}))}function i(c){const f=new Event("vite:preloadError",{cancelable:!0});if(f.payload=c,window.dispatchEvent(f),!f.defaultPrevented)throw c}return r.then(c=>{for(const f of c||[])f.status==="rejected"&&i(f.reason);return t().catch(i)})},P="/gitwarren",O={socket:`${P}/socket`,appInfo:`${P}/app-info`,attachments:`${P}/attachments/`};function Y(e,t){const o=U(e);if(o===null)return e;const n=`${O.attachments}${o}`;return t===void 0?n:`${n}?${B}=${encodeURIComponent(t)}`}const $=32768;function z(e){let t="";for(let o=0;o<e.length;o+=$)t+=String.fromCharCode(...e.subarray(o,o+$));return btoa(t)}function A(e){const{params:t}=e;if(typeof t!="object"||t===null)return JSON.stringify(e);let o=null;for(const[n,r]of Object.entries(t)){const i=Q(r);i!==null&&(o??={...t},o[n]=z(i))}return JSON.stringify(o===null?e:{...e,params:o})}function Q(e){return e instanceof ArrayBuffer?new Uint8Array(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength):null}const C=[250,500,1e3,2e3,5e3];function Z(){return`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}${O.socket}`}function ee(){let e=null,t=0,o=1;const n=new Map,r=[],i=new Set,c=new Set,f=s=>{for(const l of i)l(s)},y=s=>{e?.readyState===WebSocket.OPEN?e.send(A(s)):r.push(s)},w=()=>{const s=[...n.values()];n.clear();for(const l of s)l.reject(new b("INTERNAL","The connection to GitWarren was lost before this finished. Nothing was retried; try again."))},a=()=>{const s=new WebSocket(Z());e=s,s.addEventListener("open",()=>{t=0;const u=r.splice(0,r.length);for(const d of u)s.send(A(d));f(!0)}),s.addEventListener("message",u=>{let d;try{d=JSON.parse(u.data)}catch{console.error("[carrier] a message from GitWarren was not JSON");return}if(typeof d!="object"||d===null)return;if(j(d)){for(const E of c)E(d);return}const g=d;if(typeof g.id!="number")return;const m=n.get(g.id);m&&(n.delete(g.id),m.resolve(g))});const l=()=>{if(e!==s)return;e=null,w(),f(!1);const u=C[Math.min(t,C.length-1)];t+=1,window.setTimeout(a,u)};s.addEventListener("close",l),s.addEventListener("error",()=>{console.error("[carrier] the connection to GitWarren failed")})};a();const p=new Map,h=(s,l,u)=>{const d=o++;return new Promise((m,E)=>{n.set(d,{resolve:m,reject:E}),y({id:d,method:s,params:l,...u===void 0?{}:{host:u}})}).then(m=>H(m))};return{request(s,l,u){if(!F(s))return h(s,l,u);const d=`${u??""}:${s}:${JSON.stringify(l??null)}`,g=p.get(d);if(g)return g;const m=h(s,l,u).finally(()=>p.delete(d));return p.set(d,m),m},connected:()=>e?.readyState===WebSocket.OPEN,onConnectionChange(s){return i.add(s),()=>i.delete(s)},onEvent(s){return c.add(s),()=>c.delete(s)}}}const R=()=>{};function v(e,t){return Promise.reject(new b("FORBIDDEN",`${e} is not available in a browser tab. ${t}`))}function te(e,t){const o=e.find(n=>n.instanceId===t);if(!o)throw new b("NOT_FOUND",`This GitWarren does not know a host with id ${t}. It may have been removed, or the link may have come from somewhere else.`);return o}async function ne(){const e=await fetch(O.appInfo,{credentials:"same-origin",headers:{Accept:"application/json"}});if(!e.ok)throw new b("INTERNAL",`GitWarren could not describe this install (HTTP ${e.status}).`);return await e.json()}function T(){const e=M();return{editors:e,defaultId:e[0]?.id??null}}const _={state:"unsupported",reason:"GitWarren in a browser is updated by whatever installed it - Homebrew, npm, or the tarball it was unpacked from."};function oe(){return new Promise(e=>{const t=document.createElement("input");t.type="file",t.accept="image/png,image/jpeg,image/gif,image/webp",t.style.position="fixed",t.style.left="-9999px",document.body.append(t);let o=!1;const n=i=>{o||(o=!0,window.removeEventListener("focus",r),t.remove(),e(i))},r=()=>{window.setTimeout(()=>n(t.files?.[0]??null),300)};t.addEventListener("change",()=>n(t.files?.[0]??null)),t.addEventListener("cancel",()=>n(null)),window.addEventListener("focus",r),t.click()})}function re(e){return{capabilities:{pickDirectory:!1,revealPath:!1,openAtLogin:!1},system:{pickDirectory:()=>Promise.resolve(null),revealPath:()=>v("Revealing a folder","Copy the path and open it yourself."),appInfo:ne,editors:()=>Promise.resolve(T()),getOpenAtLogin:()=>Promise.resolve(!1),setOpenAtLogin:()=>v("Starting at login","Run `gitwarren service install` on this machine.")},updates:{getStatus:()=>Promise.resolve(_),check:()=>Promise.resolve(_),installNow:()=>v("Installing an update","Update GitWarren the way you installed it."),subscribe:()=>R},connection:{connected:()=>e.connected(),subscribe:n=>e.onConnectionChange(n)},navigation:{onDeepLink:()=>R},pickAttachment:async n=>{const r=await oe();return r===null?null:await e.request("attachments.ingest",{bytes:await r.arrayBuffer(),originalName:r.name},n)},attachmentSrc:Y,openInEditor:async n=>{const{id:r,path:i,changes:c,line:f,editorId:y,host:w}=n,a=I(y)??I(T().defaultId??void 0);if(!a?.url)return v("Opening a file in an editor","No editor with a URL scheme was chosen for this tab.");if(w!==void 0&&!a.remoteUrl)return v("Opening a file on another machine",`${a.label} has no way to open a file it cannot see. VS Code, Cursor and Windsurf do.`);const p=await e.request("reviews.filePath",{id:r,path:i,changes:c},w),h=w===void 0?void 0:D(te(await e.request("hosts.list",void 0),w));window.location.href=h===void 0?a.url(p,f??1):a.remoteUrl(h,p,f??1)}}}const se="gitwarren",L=`${se}://`,W="review",S="h=";function ie(e){if(!e||e.slice(0,L.length).toLowerCase()!==L)return null;const o=(e.slice(L.length).split(/[?#]/)[0]??"").split("/").filter(Boolean),n=o[0]?.toLowerCase();if(n===W)return N(`#/reviews/${o.slice(1).join("/")}`);if(n!==void 0&&q(n)){const r=o.slice(1);return r.length===0?{name:"repositories",host:n}:r[0]?.toLowerCase()!==W?null:N(`#/h/${n}/reviews/${r.slice(1).join("/")}`)}return null}function ae(e){return e.replace(/^#/,"").startsWith(S)}function ce(e,t){const o=e.replace(/^#/,"");if(!o.startsWith(S))return null;const n=ie(`gitwarren://${o.slice(S.length)}`);if(!n)return G;if(n.host===void 0)return n;if(n.host===t){const{host:r,...i}=n;return i}return n}function le(e){return{request:(t,o,n)=>x(()=>e.request(t,o,n)),onEvent:t=>e.onEvent(t)}}function ue(){const e=ee(),t={carrier:le(e),shell:re(e)};return window.gitwarren=t,t}async function fe(e){if(!ae(window.location.hash))return;const{instanceId:t}=await e.shell.system.appInfo(),o=ce(window.location.hash,t);o&&window.location.replace(J(o))}const de=ue();await fe(de);await X(()=>import("./main-1gX2-1gC.js"),__vite__mapDeps([0,1,2]),import.meta.url);