@paradow/ledge 0.15.0 → 0.15.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.
package/README.md CHANGED
@@ -30,7 +30,7 @@ pnpm --filter @paradow/ledge build
30
30
  chmod +x packages/ledge/dist/cli/index.js
31
31
  mkdir -p ~/.local/bin
32
32
  ln -sf "$PWD/packages/ledge/dist/cli/index.js" ~/.local/bin/ledge # ~/.local/bin must be on the PATH Claude Code uses for hooks
33
- ledge setup
33
+ ledge setup # the link becomes a script that runs the CLI with this node (a session Ledge launches may have no node on its PATH: nvm)
34
34
  ```
35
35
 
36
36
  The daemon is spawned from the built CLI (`dist/cli/index.js`), so rebuild after changes (`pnpm --filter @paradow/ledge build`).
@@ -43,7 +43,7 @@ ledge claude --name max # pick the bot's name
43
43
  ledge status # list bots and what they wait for
44
44
  ledge attach <id> # put this terminal on a running session (what the overlay opens a tab with)
45
45
  ledge daemon # run the daemon in the foreground (normally run by the systemd service)
46
- ledge uninstall # undo ledge setup: hooks, service and the ~/.local/bin link (--purge: ~/.ledge too)
46
+ ledge uninstall # undo ledge setup: hooks, service and ~/.local/bin/ledge (--purge: ~/.ledge too)
47
47
  ```
48
48
 
49
49
  `ledge setup` installs `ledge-daemon.service` as a systemd user unit, starts it and enables linger, so the daemon runs from WSL boot and restarts after a crash. Rerun it after a node upgrade or a repository move: the unit is rewritten and the service restarted. Useful commands:
@@ -0,0 +1,6 @@
1
+ // src/version.ts
2
+ var LEDGE_VERSION = true ? "0.15.1" : "0.0.0-dev";
3
+
4
+ export {
5
+ LEDGE_VERSION
6
+ };
@@ -356,9 +356,10 @@ function reduceEvent(s, ev) {
356
356
  case "PanePrompt": {
357
357
  const incoming = pendingFor(ev);
358
358
  const moved = s.pending?.source === "hook" && s.pending.kind === "question" && incoming.kind === "question" && !sameQuestion(incoming.text, s.pending.text);
359
- if (s.pending?.source === "hook" && !moved) return s;
359
+ const bare = s.pending?.source === "hook" && s.pending.kind === "permission" && !s.pending.tool;
360
+ if (s.pending?.source === "hook" && !moved && !bare) return s;
360
361
  if (!moved && hooksReportPrompts(s) && lastHookAt && Date.parse(at) - Date.parse(lastHookAt) < HOOK_PRIORITY_MS) return s;
361
- let pending = moved ? { ...incoming, ...s.pending.helperId ? { helperId: s.pending.helperId } : {} } : mergePending(s.pending, incoming);
362
+ let pending = moved ? { ...incoming, ...s.pending.helperId ? { helperId: s.pending.helperId } : {} } : bare ? incoming : mergePending(s.pending, incoming);
362
363
  if (pending.kind === "plan" && !pending.body && s.tool === "codex" && s.lastReply) pending = { ...pending, body: s.lastReply.text };
363
364
  if (s.pending && samePending(s.pending, pending)) return s;
364
365
  const state = pending.kind === "question" ? "waiting_input" : "waiting_permission";
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-R5462SQM.js";
8
8
 
9
9
  // src/cli/setup.ts
10
- import { readFile as readFile2, writeFile, mkdir, rename, realpath, rm, stat, lstat, readlink, symlink, unlink } from "fs/promises";
10
+ import { readFile as readFile2, writeFile, mkdir, rename, realpath, rm, stat, lstat, readlink } from "fs/promises";
11
11
  import { execFile } from "child_process";
12
12
  import { homedir, userInfo } from "os";
13
13
  import { join, dirname } from "path";
@@ -237,14 +237,23 @@ async function isDir(path) {
237
237
  return false;
238
238
  }
239
239
  }
240
- async function relinkBin(opts) {
240
+ var BIN_MARK = "#!/bin/sh\n# Written by `ledge setup`";
241
+ var shQuote = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
242
+ async function writeBin(opts) {
241
243
  const st = await lstat(opts.link).catch(() => null);
242
- if (!st?.isSymbolicLink()) return null;
243
- const current = await readlink(opts.link);
244
- if (current === opts.target || !current.endsWith("/dist/cli/index.js")) return null;
245
- await unlink(opts.link);
246
- await symlink(opts.target, opts.link);
247
- return `${opts.link} now points to ${opts.target} (was ${current})`;
244
+ if (!st) return null;
245
+ const was = st.isSymbolicLink() ? await readlink(opts.link) : null;
246
+ if (was !== null && !was.endsWith("/dist/cli/index.js")) return null;
247
+ const current = was === null && st.isFile() ? await readFile2(opts.link, "utf8").catch(() => "") : "";
248
+ if (was === null && !current.startsWith(BIN_MARK)) return null;
249
+ const script = `${BIN_MARK}: the hooks run \`ledge\` wherever node is not on the PATH.
250
+ exec ${shQuote(opts.node)} ${shQuote(opts.target)} "$@"
251
+ `;
252
+ if (current === script) return null;
253
+ const tmp = `${opts.link}.tmp`;
254
+ await writeFile(tmp, script, { mode: 493 });
255
+ await rename(tmp, opts.link);
256
+ return `${opts.link} runs ${opts.target} with ${opts.node}${was !== null ? ` (was a link to ${was})` : ""}`;
248
257
  }
249
258
  async function runSetup(opts = {}) {
250
259
  const out = [];
@@ -263,7 +272,7 @@ async function runSetup(opts = {}) {
263
272
  else if (await mergeJsonFile(join(cursorDir, "hooks.json"), mergeCursorHooks, "hooks.json")) out.push(`Cursor Agent hooks added in ${join(cursorDir, "hooks.json")}`);
264
273
  else out.push("Cursor Agent hooks already present");
265
274
  if ((opts.service ?? true) && !process.argv[1]?.endsWith(".ts")) {
266
- const line = await relinkBin({ link: join(homedir(), ".local", "bin", "ledge"), target: await realpath(process.argv[1]) });
275
+ const line = await writeBin({ link: join(homedir(), ".local", "bin", "ledge"), node: process.execPath, target: await realpath(process.argv[1]) });
267
276
  if (line) out.push(line);
268
277
  }
269
278
  if (opts.service ?? true) {
@@ -286,7 +295,8 @@ export {
286
295
  stopDetachedDaemon,
287
296
  installService,
288
297
  isDir,
289
- relinkBin,
298
+ BIN_MARK,
299
+ writeBin,
290
300
  runSetup,
291
301
  inDaemonService,
292
302
  tmuxServerAtRisk
@@ -27,7 +27,7 @@ import {
27
27
  reduce,
28
28
  repositoryId,
29
29
  shownCommand
30
- } from "./chunk-CU2E3W4L.js";
30
+ } from "./chunk-QCEVJMXI.js";
31
31
  import {
32
32
  PARENT_AGENT_VARS,
33
33
  agentEnv,
@@ -99,12 +99,12 @@ import {
99
99
  } from "./chunk-2LQITRTR.js";
100
100
  import {
101
101
  LEDGE_VERSION
102
- } from "./chunk-EHHYRQWY.js";
102
+ } from "./chunk-LKQ5DDQ4.js";
103
103
  import {
104
104
  SERVICE,
105
105
  inDaemonService,
106
106
  tmuxServerAtRisk
107
- } from "./chunk-DXVHFVJI.js";
107
+ } from "./chunk-ZANOMSLW.js";
108
108
  import {
109
109
  realTmux,
110
110
  runFile
@@ -13,7 +13,7 @@ import {
13
13
  QUESTION_TOOLS,
14
14
  bareName,
15
15
  gitInfo
16
- } from "./chunk-CU2E3W4L.js";
16
+ } from "./chunk-QCEVJMXI.js";
17
17
  import {
18
18
  CODEX_ENV_VAR,
19
19
  CODEX_HOOK_VAR,
package/dist/cli/index.js CHANGED
@@ -18,7 +18,7 @@ async function hookMain(args) {
18
18
  try {
19
19
  const chunks = [];
20
20
  for await (const c of process.stdin) chunks.push(c);
21
- const { runHook } = await import("./hook-BGIM76EW.js");
21
+ const { runHook } = await import("./hook-PRPEB3T7.js");
22
22
  const out = await runHook(event, Buffer.concat(chunks).toString("utf8"), { tool });
23
23
  if (out) process.stdout.write(`${out}
24
24
  `);
@@ -30,7 +30,7 @@ async function main(argv) {
30
30
  const [cmd, ...rest] = argv;
31
31
  switch (cmd) {
32
32
  case "daemon": {
33
- const { startDaemon } = await import("./daemon-NIEJZDEC.js");
33
+ const { startDaemon } = await import("./daemon-TBYCUHC3.js");
34
34
  await startDaemon();
35
35
  await new Promise(() => {
36
36
  });
@@ -38,17 +38,17 @@ async function main(argv) {
38
38
  }
39
39
  case "--version":
40
40
  case "version": {
41
- const { LEDGE_VERSION } = await import("./version-X45HSUBB.js");
41
+ const { LEDGE_VERSION } = await import("./version-5BLH6OA6.js");
42
42
  console.log(LEDGE_VERSION);
43
43
  return 0;
44
44
  }
45
45
  case "setup": {
46
- const { runSetup } = await import("./setup-2GROTY6B.js");
46
+ const { runSetup } = await import("./setup-LWYZEVNE.js");
47
47
  for (const l of await runSetup()) console.log(l);
48
48
  return 0;
49
49
  }
50
50
  case "uninstall": {
51
- const { runUninstall } = await import("./uninstall-JCX7QXU2.js");
51
+ const { runUninstall } = await import("./uninstall-TFC4LALT.js");
52
52
  const r = await runUninstall({ purge: rest.includes("--purge") });
53
53
  for (const l of r.lines) console.log(l);
54
54
  return r.ok ? 0 : 1;
@@ -1,4 +1,5 @@
1
1
  import {
2
+ BIN_MARK,
2
3
  CLAUDE_EVENTS,
3
4
  CODEX_EVENTS,
4
5
  CURSOR_EVENTS,
@@ -9,15 +10,16 @@ import {
9
10
  mergeClaudeHooks,
10
11
  mergeCodexHooks,
11
12
  mergeCursorHooks,
12
- relinkBin,
13
13
  runSetup,
14
14
  stopDetachedDaemon,
15
- systemdUnit
16
- } from "./chunk-DXVHFVJI.js";
15
+ systemdUnit,
16
+ writeBin
17
+ } from "./chunk-ZANOMSLW.js";
17
18
  import "./chunk-2H5FJH4Q.js";
18
19
  import "./chunk-BNKPXSUY.js";
19
20
  import "./chunk-R5462SQM.js";
20
21
  export {
22
+ BIN_MARK,
21
23
  CLAUDE_EVENTS,
22
24
  CODEX_EVENTS,
23
25
  CURSOR_EVENTS,
@@ -28,8 +30,8 @@ export {
28
30
  mergeClaudeHooks,
29
31
  mergeCodexHooks,
30
32
  mergeCursorHooks,
31
- relinkBin,
32
33
  runSetup,
33
34
  stopDetachedDaemon,
34
- systemdUnit
35
+ systemdUnit,
36
+ writeBin
35
37
  };
@@ -1,8 +1,9 @@
1
1
  import {
2
+ BIN_MARK,
2
3
  SERVICE,
3
4
  execRunner,
4
5
  stopDetachedDaemon
5
- } from "./chunk-DXVHFVJI.js";
6
+ } from "./chunk-ZANOMSLW.js";
6
7
  import "./chunk-2H5FJH4Q.js";
7
8
  import "./chunk-BNKPXSUY.js";
8
9
  import {
@@ -96,8 +97,8 @@ async function runningSessions(run, socket) {
96
97
  }
97
98
  async function removeLink(link, script) {
98
99
  try {
99
- if (!(await lstat(link)).isSymbolicLink()) return false;
100
- if (await realpath(link) !== await realpath(script)) return false;
100
+ const st = await lstat(link);
101
+ if (st.isFile() ? !(await readFile(link, "utf8")).startsWith(BIN_MARK) : !st.isSymbolicLink() || await realpath(link) !== await realpath(script)) return false;
101
102
  } catch {
102
103
  return false;
103
104
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  LEDGE_VERSION
3
- } from "./chunk-EHHYRQWY.js";
3
+ } from "./chunk-LKQ5DDQ4.js";
4
4
  export {
5
5
  LEDGE_VERSION
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paradow/ledge",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "Follow and answer your WSL agent sessions (Claude Code, Codex, Cursor Agent) from the Ledge island on Windows.",
5
5
  "type": "module",
6
6
  "bin": { "ledge": "dist/cli/index.js" },
@@ -1,6 +0,0 @@
1
- // src/version.ts
2
- var LEDGE_VERSION = true ? "0.15.0" : "0.0.0-dev";
3
-
4
- export {
5
- LEDGE_VERSION
6
- };