@zuzuucodes/cli 1.2.3 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/bin/zuzuu.mjs +8 -2
  2. package/package.json +1 -1
  3. package/web-app/dist/index.js +19 -0
  4. package/web-app/dist/instance-file.js +62 -0
  5. package/web-app/dist/server.js +65 -3
  6. package/web-app/dist/sessions.js +42 -8
  7. package/web-app/dist/zuzuu-api.js +24 -3
  8. package/web-app/web-dist/assets/{DiffTab-Clz0uEu_.js → DiffTab-CihRJjzf.js} +1 -1
  9. package/web-app/web-dist/assets/{MonacoFile-DguRe1Rt.js → MonacoFile-DJvpGyW2.js} +1 -1
  10. package/web-app/web-dist/assets/{cssMode-C4OLzNbC.js → cssMode-R1Bks9TO.js} +1 -1
  11. package/web-app/web-dist/assets/{dist-DYKlGApw.js → dist-jCnX6g-O.js} +1 -1
  12. package/web-app/web-dist/assets/{htmlMode-D1O2jo0s.js → htmlMode-Csqnn3yv.js} +1 -1
  13. package/web-app/web-dist/assets/index-D_MPtALn.css +2 -0
  14. package/web-app/web-dist/assets/index-Ye54YyTn.js +267 -0
  15. package/web-app/web-dist/assets/{jsonMode-D8YStjhJ.js → jsonMode-DRBg9jwi.js} +1 -1
  16. package/web-app/web-dist/assets/{monaco-setup-BBNGrQzm.js → monaco-setup-Dszx738Y.js} +3 -3
  17. package/web-app/web-dist/assets/{tsMode-B8P6eQAV.js → tsMode-9YOHYiVQ.js} +1 -1
  18. package/web-app/web-dist/index.html +2 -2
  19. package/zuzuu/commands/doctor.mjs +10 -0
  20. package/zuzuu/commands/hook.mjs +24 -1
  21. package/zuzuu/commands/session.mjs +103 -0
  22. package/zuzuu/commands/status.mjs +10 -3
  23. package/zuzuu/commands/web.mjs +113 -8
  24. package/zuzuu/live/live-store.mjs +7 -0
  25. package/zuzuu/session-git.mjs +392 -0
  26. package/web-app/web-dist/assets/index-Cfwhe1gB.js +0 -270
  27. package/web-app/web-dist/assets/index-RHYMLHDZ.css +0 -2
package/bin/zuzuu.mjs CHANGED
@@ -32,6 +32,7 @@ import { code } from '../zuzuu/commands/code.mjs';
32
32
  import { web } from '../zuzuu/commands/web.mjs';
33
33
  import { explain } from '../zuzuu/commands/explain.mjs';
34
34
  import { inbox } from '../zuzuu/commands/inbox.mjs';
35
+ import { session } from '../zuzuu/commands/session.mjs';
35
36
 
36
37
  function parseArgs(argv) {
37
38
  const a = { _: [] };
@@ -60,7 +61,9 @@ function help() {
60
61
  usage: zuzuu <command> [options]
61
62
 
62
63
  code [dir] launch OpenCode as the bundled default host (faculty home + capture + gate + digest)
63
- web [dir] launch the visual workbench (installs @zuzuucodes/web on demand)
64
+ web [dir] [--stop|--status]
65
+ launch the visual workbench (reuses a running one;
66
+ --stop ends it, --status reports it)
64
67
  init scaffold the faculty home (.zuzuu/) — git-style, idempotent
65
68
  status detected hosts + recorded sessions
66
69
  capture [--host NAME] capture a session → .zuzuu/.traces + .zuzuu/sessions.json
@@ -89,6 +92,8 @@ usage: zuzuu <command> [options]
89
92
  pin/list/show/roll back faculty generations (lockfiles)
90
93
  enable background hooks: invisible live capture + guardrails gate
91
94
  disable remove the background hooks
95
+ session [status|merge|continue|discard]
96
+ the invisible session branch (one per agent session)
92
97
  eval [--faculty f] rank pending proposals by eval score, highest first
93
98
  migrate [--home] one-time migrators: proposal schema · --home moves agent/ → .zuzuu/
94
99
  doctor environment + session health (reconciles lost sessions)
@@ -105,7 +110,7 @@ const args = parseArgs(rest);
105
110
 
106
111
  switch (cmd) {
107
112
  case 'code': process.exit(code(args)); break;
108
- case 'web': web(args); break;
113
+ case 'web': await web(args); break;
109
114
  case 'init': init(args); break;
110
115
  case 'remember': remember(args); break;
111
116
  case 'recall': await recall(args); break;
@@ -122,6 +127,7 @@ switch (cmd) {
122
127
  case 'enable': enable(args); break;
123
128
  case 'disable': disable(args); break;
124
129
  case 'hook': runHook(args._[0], { host: args.host, session: args.session }); break;
130
+ case 'session': session(args); break;
125
131
  case 'eval': evalCmd(args); break;
126
132
  case 'migrate': migrate(args); break;
127
133
  case 'generation': generation(args); break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zuzuucodes/cli",
3
- "version": "1.2.3",
3
+ "version": "1.3.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
7
7
  import crypto from "node:crypto";
8
8
  import { WebcodeServer } from "./server.js";
9
9
  import { addRecent } from "./config.js";
10
+ import { writeInstanceFile, removeInstanceFile } from "./instance-file.js";
10
11
  const HERE = path.dirname(fileURLToPath(import.meta.url));
11
12
  const DEFAULT_PORT = 7770;
12
13
  function parseArgs(argv) {
@@ -147,14 +148,32 @@ async function main() {
147
148
  console.log(`\n zuzuu-web v${pkg.version}`);
148
149
  console.log(` workspace ${root}`);
149
150
  console.log(` url ${url}\n`);
151
+ // Singleton contract: record this instance so `zuzuu web` can reuse it
152
+ // instead of spawning a duplicate (never in hosted mode; never fatal).
153
+ if (!hosted) {
154
+ writeInstanceFile({
155
+ root,
156
+ port: boundPort,
157
+ pid: process.pid,
158
+ token,
159
+ startedAt: new Date().toISOString(),
160
+ version: pkg.version,
161
+ });
162
+ }
150
163
  if (args.open)
151
164
  openBrowser(url);
152
165
  });
153
166
  const shutdown = () => {
167
+ if (!hosted)
168
+ removeInstanceFile(root);
154
169
  server.stop();
155
170
  process.exit(0);
156
171
  };
157
172
  process.on("SIGINT", shutdown);
158
173
  process.on("SIGTERM", shutdown);
174
+ process.on("exit", () => {
175
+ if (!hosted)
176
+ removeInstanceFile(root); // best-effort; idempotent after shutdown()
177
+ });
159
178
  }
160
179
  void main();
@@ -0,0 +1,62 @@
1
+ // Per-workspace daemon instance state — the singleton contract with `zuzuu web`.
2
+ //
3
+ // After a successful listen the daemon writes
4
+ // ~/.webcode/instances/<sha256(realpath-root).slice(0,16)>.json
5
+ // and removes it on clean shutdown. The zuzuu CLI computes the same path for a
6
+ // workspace to discover (and reuse / stop) an already-running daemon instead of
7
+ // spawning a fresh one — so the port + token stay stable across `zuzuu web` runs.
8
+ //
9
+ // Security note: the file contains the auth token. That's acceptable here —
10
+ // it's 0600 in the user's own home directory, and the same token already
11
+ // appears in the daemon's own stdout URL. Hosted mode never writes this file.
12
+ import crypto from "node:crypto";
13
+ import fs from "node:fs";
14
+ import os from "node:os";
15
+ import path from "node:path";
16
+ export function instancesDir() {
17
+ return path.join(os.homedir(), ".webcode", "instances");
18
+ }
19
+ /** Deterministic per-workspace file path. `root` must already be realpath'd. */
20
+ export function instancePath(root, dir = instancesDir()) {
21
+ const id = crypto.createHash("sha256").update(root).digest("hex").slice(0, 16);
22
+ return path.join(dir, `${id}.json`);
23
+ }
24
+ /** Write the instance file (0600). Never throws — a failed write only costs reuse. */
25
+ export function writeInstanceFile(info, dir = instancesDir()) {
26
+ const file = instancePath(info.root, dir);
27
+ try {
28
+ fs.mkdirSync(dir, { recursive: true });
29
+ fs.writeFileSync(file, JSON.stringify(info, null, 2) + "\n", { mode: 0o600 });
30
+ fs.chmodSync(file, 0o600); // mode option only applies on create; enforce on overwrite too
31
+ return file;
32
+ }
33
+ catch (err) {
34
+ console.warn(`zuzuu-web: could not write instance state (${String(err)}) — continuing without it`);
35
+ return null;
36
+ }
37
+ }
38
+ /** Best-effort read; null on missing/corrupt. */
39
+ export function readInstanceFile(root, dir = instancesDir()) {
40
+ try {
41
+ return JSON.parse(fs.readFileSync(instancePath(root, dir), "utf8"));
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
47
+ /**
48
+ * Best-effort removal on shutdown. Only removes the file if it still belongs
49
+ * to `pid` — if another daemon raced us and overwrote it, leave theirs alone.
50
+ */
51
+ export function removeInstanceFile(root, pid = process.pid, dir = instancesDir()) {
52
+ const file = instancePath(root, dir);
53
+ try {
54
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
55
+ if (typeof parsed.pid === "number" && parsed.pid !== pid)
56
+ return; // not ours anymore
57
+ fs.unlinkSync(file);
58
+ }
59
+ catch {
60
+ /* already gone or unreadable — nothing to do */
61
+ }
62
+ }
@@ -11,7 +11,7 @@ import { execFile } from "node:child_process";
11
11
  import { promisify } from "node:util";
12
12
  import { SessionManager } from "./sessions.js";
13
13
  import { createFsApi } from "./fs-api.js";
14
- import { createZuzuuApi } from "./zuzuu-api.js";
14
+ import { createZuzuuApi, runZuzuuMut } from "./zuzuu-api.js";
15
15
  import { search } from "./search.js";
16
16
  import { listFiles } from "./file-list.js";
17
17
  import { listWorkflows, saveWorkflow } from "./workflows.js";
@@ -25,6 +25,8 @@ import { handleFsSocket } from "./ws-fs.js";
25
25
  import { PathError, resolveSafe, safeJoin } from "./safe-path.js";
26
26
  const AUTH_COOKIE = "webcode_auth";
27
27
  const COOKIE_MAX_AGE = 30 * 24 * 3600;
28
+ /** Host CLIs an agent/command session may run. Argv-spawned, never a shell. */
29
+ const DEFAULT_COMMAND_ALLOWLIST = ["claude", "gemini", "codex", "pi", "opencode", "zuzuu"];
28
30
  const STATIC_MIME = {
29
31
  ".html": "text/html; charset=utf-8",
30
32
  ".js": "text/javascript",
@@ -46,10 +48,12 @@ export class WebcodeServer {
46
48
  authSessions = new Set();
47
49
  allowedHosts;
48
50
  allowedOrigins;
51
+ commandAllowlist;
49
52
  server = null;
50
53
  constructor(cfg) {
51
54
  this.cfg = cfg;
52
55
  this.root = cfg.root;
56
+ this.commandAllowlist = new Set(cfg.commandAllowlist ?? DEFAULT_COMMAND_ALLOWLIST);
53
57
  this.sessions = new SessionManager(cfg.root);
54
58
  const hostNames = ["127.0.0.1", "localhost", "[::1]"];
55
59
  this.allowedHosts = new Set(hostNames.flatMap((h) => [h, `${h}:${cfg.port}`]));
@@ -81,6 +85,22 @@ export class WebcodeServer {
81
85
  this.root = resolved;
82
86
  await config.addRecent(resolved);
83
87
  }
88
+ /**
89
+ * Agent PTY exited → squash-merge its invisible session branch back to
90
+ * main via the zuzuu CLI. Runs in the session's cwd (the repo the agent
91
+ * worked in). CLI-only, like every zuzuu mutation; absent CLI is recorded,
92
+ * never fatal. Session.runCloseHook guarantees this runs once per session.
93
+ */
94
+ async closeAgentSession(session) {
95
+ const r = await runZuzuuMut(session.cwd, ["session", "merge"], {
96
+ binary: this.cfg.zuzuuBinary,
97
+ });
98
+ if (r.ok)
99
+ return { ok: true, merge: r.data };
100
+ if (r.code === "absent")
101
+ return { cliAbsent: true };
102
+ return { ok: false, ...(r.stderr !== undefined ? { stderr: r.stderr } : {}), ...(r.data !== undefined ? { refusal: r.data } : {}) };
103
+ }
84
104
  // ── security gates ─────────────────────────────────────────────────
85
105
  /** Host allowlist defeats DNS rebinding: rebinding changes DNS, not the Host header. */
86
106
  hostAllowed(host) {
@@ -152,10 +172,52 @@ export class WebcodeServer {
152
172
  catch {
153
173
  // empty body is fine
154
174
  }
175
+ // Direct command sessions: the allowlist keeps the spawn surface honest
176
+ // (authenticated localhost daemon or not). Argv only — never a shell.
177
+ if (body.command !== undefined) {
178
+ if (typeof body.command !== "string" || !this.commandAllowlist.has(body.command)) {
179
+ return c.json({ error: "command not allowed" }, 400);
180
+ }
181
+ if (body.args !== undefined &&
182
+ (!Array.isArray(body.args) || !body.args.every((a) => typeof a === "string"))) {
183
+ return c.json({ error: "args must be an array of strings" }, 400);
184
+ }
185
+ }
186
+ else if (body.args !== undefined) {
187
+ return c.json({ error: "args require command" }, 400);
188
+ }
189
+ if (body.type !== undefined && body.type !== "shell" && body.type !== "agent") {
190
+ return c.json({ error: "bad type" }, 400);
191
+ }
192
+ if (body.host !== undefined && (typeof body.host !== "string" || body.host.length > 64)) {
193
+ return c.json({ error: "bad host" }, 400);
194
+ }
155
195
  const cwd = body.cwd ? safeJoin(this.root, body.cwd) : this.root;
156
- const session = this.sessions.create(cwd, body.cols, body.rows);
196
+ const type = body.type ?? "shell";
197
+ const session = this.sessions.create(cwd, body.cols, body.rows, {
198
+ ...(body.command !== undefined ? { command: body.command, args: body.args ?? [] } : {}),
199
+ type,
200
+ ...(body.host !== undefined ? { host: body.host } : {}),
201
+ ...(type === "agent" ? { onClose: (s) => this.closeAgentSession(s) } : {}),
202
+ });
157
203
  return c.json(session.info(), 201);
158
204
  });
205
+ // Single-session read: the SPA polls this once after the Exit frame to
206
+ // pick up closeResult (the agent-exit auto-merge outcome). Awaiting
207
+ // whenClosed() means a poll that races the merge still gets the result.
208
+ app.get("/api/sessions/:id", async (c) => {
209
+ const session = this.sessions.get(c.req.param("id"));
210
+ if (!session)
211
+ return c.json({ error: "no such session" }, 404);
212
+ await session.whenClosed();
213
+ const body = {
214
+ ...session.info(),
215
+ ...(session.closeResult !== undefined
216
+ ? { closeResult: session.closeResult }
217
+ : {}),
218
+ };
219
+ return c.json(body);
220
+ });
159
221
  app.delete("/api/sessions/:id", (c) => {
160
222
  const ok = this.sessions.close(c.req.param("id"));
161
223
  return ok ? c.json({ ok: true }) : c.json({ error: "no such session" }, 404);
@@ -333,7 +395,7 @@ export class WebcodeServer {
333
395
  return c.json({ ok: true, root: this.root });
334
396
  });
335
397
  app.route("/api/fs", createFsApi(() => this.root));
336
- app.route("/api/zuzuu", createZuzuuApi(() => this.root));
398
+ app.route("/api/zuzuu", createZuzuuApi(() => this.root, { binary: cfg.zuzuuBinary }));
337
399
  // Static SPA with index.html fallback
338
400
  app.get("*", async (c) => {
339
401
  let rel = decodeURIComponent(new URL(c.req.url).pathname);
@@ -65,10 +65,17 @@ export class Session {
65
65
  cwd;
66
66
  root;
67
67
  onUpdate;
68
+ opts;
68
69
  id = crypto.randomBytes(8).toString("hex");
69
70
  createdAt = Date.now();
70
71
  title;
71
72
  alive = true;
73
+ type;
74
+ host;
75
+ /** result of the agent-exit close hook (e.g. the session-git merge) */
76
+ closeResult;
77
+ closeRan = false;
78
+ closeSettled = Promise.resolve();
72
79
  /** live working directory of the shell (absolute) */
73
80
  cwdAbs;
74
81
  pty;
@@ -87,13 +94,16 @@ export class Session {
87
94
  castEvents = [];
88
95
  castBytes = 0;
89
96
  castTruncated = false;
90
- constructor(cwd, root, cols = 80, rows = 24, onUpdate) {
97
+ constructor(cwd, root, cols = 80, rows = 24, onUpdate, opts = {}) {
91
98
  this.cwd = cwd;
92
99
  this.root = root;
93
100
  this.onUpdate = onUpdate;
101
+ this.opts = opts;
94
102
  this.cwdAbs = cwd;
95
- const shell = pickShell();
96
- this.title = shell.split("/").pop() ?? shell;
103
+ this.type = opts.type ?? "shell";
104
+ this.host = opts.host;
105
+ const file = opts.command ?? pickShell();
106
+ this.title = file.split("/").pop() ?? file;
97
107
  this.mirror = new Terminal({ cols, rows, scrollback: SCROLLBACK, allowProposedApi: true });
98
108
  this.mirror.loadAddon(this.serializer);
99
109
  // OSC 7: the shell reports its real cwd instantly/exactly; this makes
@@ -111,10 +121,15 @@ export class Session {
111
121
  }
112
122
  return true;
113
123
  });
114
- const injection = process.platform === "win32" ? null : buildInjection(shell);
124
+ // Direct command sessions (agents) get NO shell and NO rc injection:
125
+ // the argv is spawned as-is with a plain env, so nothing a host CLI
126
+ // prints/parses is polluted by our shell-integration hook.
127
+ const injection = opts.command || process.platform === "win32" ? null : buildInjection(file);
115
128
  this.tempDir = injection?.tempDir;
116
- const args = injection?.args ?? (process.platform === "win32" ? [] : ["-l"]);
117
- this.pty = pty.spawn(shell, args, {
129
+ const args = opts.command !== undefined
130
+ ? opts.args ?? []
131
+ : injection?.args ?? (process.platform === "win32" ? [] : ["-l"]);
132
+ this.pty = pty.spawn(file, args, {
118
133
  name: "xterm-256color",
119
134
  cols,
120
135
  rows,
@@ -148,11 +163,28 @@ export class Session {
148
163
  });
149
164
  this.pty.onExit(({ exitCode, signal }) => {
150
165
  this.alive = false;
166
+ this.runCloseHook();
151
167
  this.exitPayload = JSON.stringify({ exitCode, signal });
152
168
  this.send(encodeFrame(ServerOp.Exit, this.exitPayload));
153
169
  this.onUpdate();
154
170
  });
155
171
  }
172
+ /** Agent exit → run the close hook (session-git merge) exactly once. */
173
+ runCloseHook() {
174
+ const onClose = this.opts.onClose;
175
+ if (this.type !== "agent" || !onClose || this.closeRan)
176
+ return;
177
+ this.closeRan = true;
178
+ this.closeSettled = onClose(this).then((result) => {
179
+ this.closeResult = result;
180
+ }, () => {
181
+ this.closeResult = { ok: false, stderr: "close hook failed" };
182
+ });
183
+ }
184
+ /** Resolves once any pending agent close hook has settled (immediately otherwise). */
185
+ whenClosed() {
186
+ return this.closeSettled;
187
+ }
156
188
  /** Single-attachment model: a new client takes over the session. */
157
189
  attach(ws) {
158
190
  if (this.socket && this.socket !== ws) {
@@ -296,6 +328,8 @@ export class Session {
296
328
  cwd: this.cwdAbs,
297
329
  alive: this.alive,
298
330
  createdAt: this.createdAt,
331
+ type: this.type,
332
+ ...(this.host !== undefined ? { host: this.host } : {}),
299
333
  };
300
334
  }
301
335
  }
@@ -305,8 +339,8 @@ export class SessionManager {
305
339
  constructor(defaultCwd = os.homedir()) {
306
340
  this.defaultCwd = defaultCwd;
307
341
  }
308
- create(cwd, cols, rows) {
309
- const session = new Session(cwd ?? this.defaultCwd, this.defaultCwd, cols, rows, () => { });
342
+ create(cwd, cols, rows, opts) {
343
+ const session = new Session(cwd ?? this.defaultCwd, this.defaultCwd, cols, rows, () => { }, opts);
310
344
  this.sessions.set(session.id, session);
311
345
  return session;
312
346
  }
@@ -98,8 +98,17 @@ export function runZuzuuMut(root, args, opts = {}) {
98
98
  });
99
99
  child.on("close", (code) => {
100
100
  clearTimeout(timer);
101
- if (code !== 0)
102
- return finish({ ok: false, code: "failed", stderr: err.slice(-STDERR_TAIL) });
101
+ if (code !== 0) {
102
+ // zuzuu prints structured JSON even on refusals (exit 1, e.g.
103
+ // empty-squash-with-checkpoints) — keep it so the UI can act on reason.
104
+ try {
105
+ const parsed = JSON.parse(out);
106
+ return finish({ ok: false, code: "failed", stderr: err.slice(-STDERR_TAIL), data: parsed });
107
+ }
108
+ catch {
109
+ return finish({ ok: false, code: "failed", stderr: err.slice(-STDERR_TAIL) });
110
+ }
111
+ }
103
112
  try {
104
113
  finish({ ok: true, data: JSON.parse(out) });
105
114
  }
@@ -279,7 +288,7 @@ export function createZuzuuApi(getRoot, opts = {}) {
279
288
  if (!r.ok) {
280
289
  return r.code === "absent"
281
290
  ? c.json({ error: "zuzuu CLI required" }, 503)
282
- : c.json({ error: "zuzuu command failed", stderr: r.stderr ?? "" }, 502);
291
+ : c.json({ error: "zuzuu command failed", stderr: r.stderr ?? "", data: r.data ?? null }, 502);
283
292
  }
284
293
  return c.json(r.data);
285
294
  };
@@ -327,6 +336,18 @@ export function createZuzuuApi(getRoot, opts = {}) {
327
336
  return c.json({ error: "bad id" }, 400);
328
337
  return mutate(c, ["generation", "rollback", id]);
329
338
  });
339
+ // ── Session-git (the invisible zz/session-* branch) — CLI-only, no
340
+ // file-read fallback: branch state lives in git, only the CLI computes it.
341
+ app.get("/session", async (c) => {
342
+ const viaCli = await runZuzuu(root, ["session", "status"], { binary: opts.binary });
343
+ if (viaCli)
344
+ return c.json(viaCli);
345
+ return c.json({ enabled: false, cliAbsent: true });
346
+ });
347
+ app.post("/session/merge", (c) => mutate(c, ["session", "merge"]));
348
+ app.post("/session/continue", (c) => mutate(c, ["session", "continue"]));
349
+ // --yes rides server-side: the SPA's confirm dialog is the human gate
350
+ app.post("/session/discard", (c) => mutate(c, ["session", "discard", "--yes"]));
330
351
  app.get("/eval", async (c) => {
331
352
  const viaCli = await runZuzuu(root, ["eval"], { binary: opts.binary });
332
353
  if (viaCli)
@@ -1 +1 @@
1
- import{d as e,f as t,u as n}from"./index-Cfwhe1gB.js";import{i as r,n as i,t as a}from"./monaco-setup-BBNGrQzm.js";var o=t();function s({path:t,name:s}){let{data:l,isLoading:u,error:d}=e({queryKey:[`git`,`diff`,t],queryFn:async()=>{let[e,r]=await Promise.all([n.gitDiff(t),n.readFile(t).catch(()=>``)]);return{original:e.original,working:r}}});return d?(0,o.jsx)(c,{danger:!0,children:d.message}):u||!l?(0,o.jsx)(c,{children:`loading diff…`}):(0,o.jsx)(r,{original:l.original,modified:l.working,language:i(s),theme:a(),options:{readOnly:!0,renderSideBySide:!0,fontFamily:`"JetBrains Mono Variable", ui-monospace, monospace`,fontSize:13,minimap:{enabled:!1},automaticLayout:!0}})}function c({children:e,danger:t}){return(0,o.jsx)(`div`,{className:`flex h-full items-center justify-center text-ui ${t?`text-danger`:`text-ink-500`}`,children:e})}export{s as DiffTab,s as default};
1
+ import{d as e,f as t,u as n}from"./index-Ye54YyTn.js";import{i as r,n as i,t as a}from"./monaco-setup-Dszx738Y.js";var o=t();function s({path:t,name:s}){let{data:l,isLoading:u,error:d}=e({queryKey:[`git`,`diff`,t],queryFn:async()=>{let[e,r]=await Promise.all([n.gitDiff(t),n.readFile(t).catch(()=>``)]);return{original:e.original,working:r}}});return d?(0,o.jsx)(c,{danger:!0,children:d.message}):u||!l?(0,o.jsx)(c,{children:`loading diff…`}):(0,o.jsx)(r,{original:l.original,modified:l.working,language:i(s),theme:a(),options:{readOnly:!0,renderSideBySide:!0,fontFamily:`"JetBrains Mono Variable", ui-monospace, monospace`,fontSize:13,minimap:{enabled:!1},automaticLayout:!0}})}function c({children:e,danger:t}){return(0,o.jsx)(`div`,{className:`flex h-full items-center justify-center text-ui ${t?`text-danger`:`text-ink-500`}`,children:e})}export{s as DiffTab,s as default};
@@ -1 +1 @@
1
- import{f as e,l as t}from"./index-Cfwhe1gB.js";import{n,r,t as i}from"./monaco-setup-BBNGrQzm.js";var a=e();function o({path:e,name:o}){let c=t(t=>t.buffers[e]),l=t(e=>e.setValue),u=t(e=>e.save);return!c||c.loading?(0,a.jsx)(s,{children:`loading…`}):c.error?(0,a.jsx)(s,{danger:!0,children:c.error}):(0,a.jsx)(r,{path:e,language:n(o),theme:i(),value:c.value,onChange:t=>l(e,t??``),onMount:(t,n)=>{t.addCommand(n.KeyMod.CtrlCmd|n.KeyCode.KeyS,()=>{u(e)})},options:{fontFamily:`"JetBrains Mono Variable", ui-monospace, monospace`,fontSize:13,lineHeight:1.5,minimap:{enabled:!0,scale:1},scrollBeyondLastLine:!1,smoothScrolling:!0,renderWhitespace:`selection`,tabSize:2,automaticLayout:!0,padding:{top:8}}})}function s({children:e,danger:t}){return(0,a.jsx)(`div`,{className:`flex h-full items-center justify-center text-ui ${t?`text-danger`:`text-ink-500`}`,children:e})}export{o as MonacoFile,o as default};
1
+ import{f as e,l as t}from"./index-Ye54YyTn.js";import{n,r,t as i}from"./monaco-setup-Dszx738Y.js";var a=e();function o({path:e,name:o}){let c=t(t=>t.buffers[e]),l=t(e=>e.setValue),u=t(e=>e.save);return!c||c.loading?(0,a.jsx)(s,{children:`loading…`}):c.error?(0,a.jsx)(s,{danger:!0,children:c.error}):(0,a.jsx)(r,{path:e,language:n(o),theme:i(),value:c.value,onChange:t=>l(e,t??``),onMount:(t,n)=>{t.addCommand(n.KeyMod.CtrlCmd|n.KeyCode.KeyS,()=>{u(e)})},options:{fontFamily:`"JetBrains Mono Variable", ui-monospace, monospace`,fontSize:13,lineHeight:1.5,minimap:{enabled:!0,scale:1},scrollBeyondLastLine:!1,smoothScrolling:!0,renderWhitespace:`selection`,tabSize:2,automaticLayout:!0,padding:{top:8}}})}function s({children:e,danger:t}){return(0,a.jsx)(`div`,{className:`flex h-full items-center justify-center text-ui ${t?`text-danger`:`text-ink-500`}`,children:e})}export{o as MonacoFile,o as default};
@@ -1 +1 @@
1
- import{h as e}from"./editor.api2-BmGoRSl4.js";import{o as t}from"./monaco-setup-BBNGrQzm.js";import{_ as n,a as r,c as i,d as a,f as o,g as s,h as c,i as l,l as u,m as d,n as f,o as p,p as m,r as h,s as g,t as _,u as v,v as y}from"./lspLanguageFeatures-gTnJsses.js";var b=120*1e3,x=class{constructor(e){this._defaults=e,this._worker=null,this._client=null,this._idleCheckInterval=window.setInterval(()=>this._checkIfIdle(),30*1e3),this._lastUsedTime=0,this._configChangeListener=this._defaults.onDidChange(()=>this._stopWorker())}_stopWorker(){this._worker&&=(this._worker.dispose(),null),this._client=null}dispose(){clearInterval(this._idleCheckInterval),this._configChangeListener.dispose(),this._stopWorker()}_checkIfIdle(){this._worker&&Date.now()-this._lastUsedTime>b&&this._stopWorker()}_getClient(){return this._lastUsedTime=Date.now(),this._client||=(this._worker=t({moduleId:`vs/language/css/cssWorker`,createWorker:()=>new Worker(new URL(`/assets/css.worker-CvXBzhp8.js`,``+import.meta.url),{type:`module`}),label:this._defaults.languageId,createData:{options:this._defaults.options,languageId:this._defaults.languageId}}),this._worker.getProxy()),this._client}getLanguageServiceWorker(...e){let t;return this._getClient().then(e=>{t=e}).then(t=>{if(this._worker)return this._worker.withSyncedResources(e)}).then(e=>t)}};function S(t){let n=[],s=[],c=new x(t);n.push(c);let g=(...e)=>c.getLanguageServiceWorker(...e);function y(){let{languageId:n,modeConfiguration:c}=t;w(s),c.completionItems&&s.push(e.registerCompletionItemProvider(n,new _(g,[`/`,`-`,`:`]))),c.hovers&&s.push(e.registerHoverProvider(n,new a(g))),c.documentHighlights&&s.push(e.registerDocumentHighlightProvider(n,new p(g))),c.definitions&&s.push(e.registerDefinitionProvider(n,new f(g))),c.references&&s.push(e.registerReferenceProvider(n,new o(g))),c.documentSymbols&&s.push(e.registerDocumentSymbolProvider(n,new u(g))),c.rename&&s.push(e.registerRenameProvider(n,new m(g))),c.colors&&s.push(e.registerColorProvider(n,new l(g))),c.foldingRanges&&s.push(e.registerFoldingRangeProvider(n,new v(g))),c.diagnostics&&s.push(new h(n,g,t.onDidChange)),c.selectionRanges&&s.push(e.registerSelectionRangeProvider(n,new d(g))),c.documentFormattingEdits&&s.push(e.registerDocumentFormattingEditProvider(n,new r(g))),c.documentRangeFormattingEdits&&s.push(e.registerDocumentRangeFormattingEditProvider(n,new i(g)))}return y(),n.push(C(s)),C(n)}function C(e){return{dispose:()=>w(e)}}function w(e){for(;e.length;)e.pop().dispose()}export{_ as CompletionAdapter,f as DefinitionAdapter,h as DiagnosticsAdapter,l as DocumentColorAdapter,r as DocumentFormattingEditProvider,p as DocumentHighlightAdapter,g as DocumentLinkAdapter,i as DocumentRangeFormattingEditProvider,u as DocumentSymbolAdapter,v as FoldingRangeAdapter,a as HoverAdapter,o as ReferenceAdapter,m as RenameAdapter,d as SelectionRangeAdapter,x as WorkerManager,c as fromPosition,s as fromRange,S as setupMode,n as toRange,y as toTextEdit};
1
+ import{h as e}from"./editor.api2-BmGoRSl4.js";import{o as t}from"./monaco-setup-Dszx738Y.js";import{_ as n,a as r,c as i,d as a,f as o,g as s,h as c,i as l,l as u,m as d,n as f,o as p,p as m,r as h,s as g,t as _,u as v,v as y}from"./lspLanguageFeatures-gTnJsses.js";var b=120*1e3,x=class{constructor(e){this._defaults=e,this._worker=null,this._client=null,this._idleCheckInterval=window.setInterval(()=>this._checkIfIdle(),30*1e3),this._lastUsedTime=0,this._configChangeListener=this._defaults.onDidChange(()=>this._stopWorker())}_stopWorker(){this._worker&&=(this._worker.dispose(),null),this._client=null}dispose(){clearInterval(this._idleCheckInterval),this._configChangeListener.dispose(),this._stopWorker()}_checkIfIdle(){this._worker&&Date.now()-this._lastUsedTime>b&&this._stopWorker()}_getClient(){return this._lastUsedTime=Date.now(),this._client||=(this._worker=t({moduleId:`vs/language/css/cssWorker`,createWorker:()=>new Worker(new URL(`/assets/css.worker-CvXBzhp8.js`,``+import.meta.url),{type:`module`}),label:this._defaults.languageId,createData:{options:this._defaults.options,languageId:this._defaults.languageId}}),this._worker.getProxy()),this._client}getLanguageServiceWorker(...e){let t;return this._getClient().then(e=>{t=e}).then(t=>{if(this._worker)return this._worker.withSyncedResources(e)}).then(e=>t)}};function S(t){let n=[],s=[],c=new x(t);n.push(c);let g=(...e)=>c.getLanguageServiceWorker(...e);function y(){let{languageId:n,modeConfiguration:c}=t;w(s),c.completionItems&&s.push(e.registerCompletionItemProvider(n,new _(g,[`/`,`-`,`:`]))),c.hovers&&s.push(e.registerHoverProvider(n,new a(g))),c.documentHighlights&&s.push(e.registerDocumentHighlightProvider(n,new p(g))),c.definitions&&s.push(e.registerDefinitionProvider(n,new f(g))),c.references&&s.push(e.registerReferenceProvider(n,new o(g))),c.documentSymbols&&s.push(e.registerDocumentSymbolProvider(n,new u(g))),c.rename&&s.push(e.registerRenameProvider(n,new m(g))),c.colors&&s.push(e.registerColorProvider(n,new l(g))),c.foldingRanges&&s.push(e.registerFoldingRangeProvider(n,new v(g))),c.diagnostics&&s.push(new h(n,g,t.onDidChange)),c.selectionRanges&&s.push(e.registerSelectionRangeProvider(n,new d(g))),c.documentFormattingEdits&&s.push(e.registerDocumentFormattingEditProvider(n,new r(g))),c.documentRangeFormattingEdits&&s.push(e.registerDocumentRangeFormattingEditProvider(n,new i(g)))}return y(),n.push(C(s)),C(n)}function C(e){return{dispose:()=>w(e)}}function w(e){for(;e.length;)e.pop().dispose()}export{_ as CompletionAdapter,f as DefinitionAdapter,h as DiagnosticsAdapter,l as DocumentColorAdapter,r as DocumentFormattingEditProvider,p as DocumentHighlightAdapter,g as DocumentLinkAdapter,i as DocumentRangeFormattingEditProvider,u as DocumentSymbolAdapter,v as FoldingRangeAdapter,a as HoverAdapter,o as ReferenceAdapter,m as RenameAdapter,d as SelectionRangeAdapter,x as WorkerManager,c as fromPosition,s as fromRange,S as setupMode,n as toRange,y as toTextEdit};