@paradow/ledge 0.11.0 → 0.12.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.
@@ -1,19 +1,30 @@
1
+ import {
2
+ parseLedgeCommand,
3
+ sendOnClient
4
+ } from "./chunk-A7YEHYLY.js";
5
+ import {
6
+ formatWho
7
+ } from "./chunk-FRGNPUDO.js";
8
+ import {
9
+ connect,
10
+ sendMessageAndAwaitAck
11
+ } from "./chunk-XXFUYME7.js";
1
12
  import {
2
13
  QUESTION_TOOLS,
14
+ bareName,
3
15
  gitInfo
4
- } from "./chunk-OHKQKXYM.js";
16
+ } from "./chunk-QAEH5TJB.js";
5
17
  import {
6
18
  CODEX_ENV_VAR,
7
19
  CODEX_HOOK_VAR,
8
20
  codexPlan
9
- } from "./chunk-TSA5LD4D.js";
21
+ } from "./chunk-X34LPKG7.js";
22
+ import {
23
+ MESSAGE_MAX
24
+ } from "./chunk-VY45MACP.js";
10
25
  import {
11
26
  log
12
27
  } from "./chunk-YUSY3E47.js";
13
- import {
14
- connect
15
- } from "./chunk-TC3XJUJA.js";
16
- import "./chunk-JOCNCNRP.js";
17
28
  import "./chunk-FGOFRWIB.js";
18
29
 
19
30
  // src/cli/hook.ts
@@ -307,7 +318,7 @@ function identityContext(s) {
307
318
  return [
308
319
  `Tu es ${s.name}, ${where} : une session lanc\xE9e par Ledge (${s.tool}). Les autres sessions Ledge te connaissent sous le nom ${s.name}.`,
309
320
  "Pour voir les autres sessions (nom, outil, \xE9tat, projet, branche, sujet), lance `ledge who`.",
310
- ...s.tool === "claude" ? ["Pour \xE9crire \xE0 une autre session Ledge, utilise son nom Ledge comme adresse SendMessage."] : []
321
+ s.tool === "claude" ? "Pour \xE9crire \xE0 une autre session Ledge, Claude ou Codex, utilise son nom Ledge comme adresse SendMessage." : "Pour \xE9crire \xE0 une autre session Ledge, lance `ledge send <nom> \"<message>\"` (seul, sans && ni |). Ledge ex\xE9cute lui-m\xEAme `ledge send` et `ledge who` : leur r\xE9sultat t'arrive comme \xAB Command blocked by PreToolUse hook: \u2026 \xBB, c'est la r\xE9ponse, pas une erreur."
311
322
  ].join("\n");
312
323
  }
313
324
  async function identity(agent, cwd, known, deadline) {
@@ -325,6 +336,29 @@ async function identity(agent, cwd, known, deadline) {
325
336
  ...known?.worktree ?? git?.worktree ? { worktree: true } : {}
326
337
  };
327
338
  }
339
+ var SEND_MESSAGE_MS = 1500;
340
+ async function relayMessage(client, from, toolUseId, input, deadline) {
341
+ if (!toolUseId) return void 0;
342
+ const left = () => Math.max(0, deadline - Date.now());
343
+ const to = input.to;
344
+ const snapshot = await client.next("snapshot", left());
345
+ const tool = snapshot.sessions.find((s) => s.name === bareName(to))?.tool;
346
+ if (tool !== "codex" && input.message.length > MESSAGE_MAX) return void 0;
347
+ const summary = str(input.summary);
348
+ const ack = await sendMessageAndAwaitAck(client, { type: "session.message", rid: toolUseId, from, to, text: input.message, ...summary ? { summary } : {}, intercept: true }, left());
349
+ const target = tool ? `${to} (${tool})` : to;
350
+ const reason = ack.outcome === "accepted" ? `Message transmis par Ledge \xE0 ${target} : il le re\xE7oit maintenant.` : ack.outcome === "queued" ? `Message transmis par Ledge \xE0 ${target} : il le recevra d\xE8s qu'il sera libre.` : ack.outcome === "refused" ? `Ledge n'a pas pu transmettre le message \xE0 ${to} : ${ack.reason ?? "raison inconnue"}` : void 0;
351
+ return reason ? JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason } }) : void 0;
352
+ }
353
+ async function runLedgeCommand(client, cmd, from, deadline) {
354
+ const left = () => Math.max(0, deadline - Date.now());
355
+ let line;
356
+ if (cmd.cmd === "who") line = formatWho((await client.next("snapshot", left())).sessions, from);
357
+ else {
358
+ line = (await sendOnClient(client, cmd.args, from, left())).line;
359
+ }
360
+ return JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: line } });
361
+ }
328
362
  async function runHook(eventName, stdin, opts = {}) {
329
363
  const agent = opts.tool ?? "claude";
330
364
  const label = agent === "claude" ? eventName : `${agent} ${eventName}`;
@@ -336,6 +370,7 @@ async function runHook(eventName, stdin, opts = {}) {
336
370
  let identify = false;
337
371
  let known;
338
372
  let deadline = Date.now() + (opts.timeoutMs ?? 100);
373
+ let relayed;
339
374
  try {
340
375
  const payload = JSON.parse(stdin || "{}");
341
376
  mapped = agent === "cursor" ? mapCursorHook(eventName, payload, (/* @__PURE__ */ new Date()).toISOString()) : mapHook(agent, eventName, payload, (/* @__PURE__ */ new Date()).toISOString());
@@ -345,15 +380,21 @@ async function runHook(eventName, stdin, opts = {}) {
345
380
  }
346
381
  const launchedId = agent === "codex" ? codexLedgeId() : agent === "claude" ? process.env.LEDGE_SESSION : void 0;
347
382
  identify = eventName === "SessionStart" && !!launchedId && mapped.id === launchedId;
348
- const timeoutMs = opts.timeoutMs ?? (identify ? 1e3 : 100);
383
+ const input = payload.tool_input ?? {};
384
+ const intercept = agent === "claude" && eventName === "PreToolUse" && str(payload.tool_name) === "SendMessage" && typeof input.to === "string" && typeof input.message === "string" && !!input.message;
385
+ const command = agent === "codex" && eventName === "PreToolUse" && str(payload.tool_name) === "Bash" && typeof input.command === "string" ? parseLedgeCommand(input.command) : void 0;
386
+ const timeoutMs = opts.timeoutMs ?? (identify ? 1e3 : intercept || command ? SEND_MESSAGE_MS : 100);
349
387
  deadline = Date.now() + timeoutMs;
350
388
  const client = await connect({ port: opts.port, timeoutMs });
351
389
  client.send({ type: "session.event", id: mapped.id, event: mapped.event, agent: mapped.agent, ...mapped.cwd ? { cwd: mapped.cwd } : {} });
352
390
  if (identify) known = (await client.next("snapshot", Math.max(0, deadline - Date.now())).catch(() => void 0))?.sessions.find((s) => s.id === mapped.id);
391
+ if (intercept) relayed = await relayMessage(client, mapped.id, str(payload.tool_use_id), input, deadline);
392
+ if (command) relayed = await runLedgeCommand(client, command, mapped.id, deadline);
353
393
  await Promise.race([client.close(), new Promise((r) => setTimeout(r, Math.max(0, deadline - Date.now())))]);
354
394
  } catch (e) {
355
395
  log(`hook ${label} dropped: ${e.message}`);
356
396
  }
397
+ if (relayed) return relayed;
357
398
  if (!identify || !mapped) return void 0;
358
399
  const who = await identity(agent, mapped.cwd, known, deadline);
359
400
  return who ? JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: identityContext(who) } }) : void 0;
package/dist/cli/index.js CHANGED
@@ -2,13 +2,14 @@
2
2
 
3
3
  // src/cli/index.ts
4
4
  var USAGE = `ledge <claude|codex|cursor> [--name <bot>] [-- agent args] start an agent in a tracked session
5
- ledge attach <id> put this terminal on a running session
5
+ ledge attach [--read-only] <id> put this terminal on a running session
6
6
  ledge daemon run the daemon in the foreground
7
7
  ledge hook [codex|cursor] <Event> receive a hook (reads JSON on stdin)
8
8
  ledge setup install hooks and ~/.ledge
9
9
  ledge uninstall [--purge] remove hooks, service and link (--purge: ~/.ledge too)
10
10
  ledge status list sessions
11
11
  ledge who list the live sessions (name, tool, state, project, branch, topic)
12
+ ledge send <nom> "<message>" [--summary <texte>] write to another Ledge session
12
13
  ledge bridge exchange Ledge messages over SSH stdio
13
14
  ledge --version print the installed version`;
14
15
  async function hookMain(args) {
@@ -17,7 +18,7 @@ async function hookMain(args) {
17
18
  try {
18
19
  const chunks = [];
19
20
  for await (const c of process.stdin) chunks.push(c);
20
- const { runHook } = await import("./hook-7V2GJUNO.js");
21
+ const { runHook } = await import("./hook-OLZ2I4GM.js");
21
22
  const out = await runHook(event, Buffer.concat(chunks).toString("utf8"), { tool });
22
23
  if (out) process.stdout.write(`${out}
23
24
  `);
@@ -29,7 +30,7 @@ async function main(argv) {
29
30
  const [cmd, ...rest] = argv;
30
31
  switch (cmd) {
31
32
  case "daemon": {
32
- const { startDaemon } = await import("./daemon-YY66JWIB.js");
33
+ const { startDaemon } = await import("./daemon-LV7V2VP5.js");
33
34
  await startDaemon();
34
35
  await new Promise(() => {
35
36
  });
@@ -37,45 +38,51 @@ async function main(argv) {
37
38
  }
38
39
  case "--version":
39
40
  case "version": {
40
- const { LEDGE_VERSION } = await import("./version-QSIZ47AV.js");
41
+ const { LEDGE_VERSION } = await import("./version-BGEWDJNC.js");
41
42
  console.log(LEDGE_VERSION);
42
43
  return 0;
43
44
  }
44
45
  case "setup": {
45
- const { runSetup } = await import("./setup-SG43PPOZ.js");
46
+ const { runSetup } = await import("./setup-TULNBDFB.js");
46
47
  for (const l of await runSetup()) console.log(l);
47
48
  return 0;
48
49
  }
49
50
  case "uninstall": {
50
- const { runUninstall } = await import("./uninstall-SD736TBP.js");
51
+ const { runUninstall } = await import("./uninstall-OFSLCP4T.js");
51
52
  const r = await runUninstall({ purge: rest.includes("--purge") });
52
53
  for (const l of r.lines) console.log(l);
53
54
  return r.ok ? 0 : 1;
54
55
  }
55
56
  case "status": {
56
- const { printStatus } = await import("./status-QOXWNLLF.js");
57
+ const { printStatus } = await import("./status-LLITXNOW.js");
57
58
  console.log(await printStatus());
58
59
  return 0;
59
60
  }
60
61
  case "who": {
61
- const { printWho } = await import("./who-WVYFPCII.js");
62
+ const { printWho } = await import("./who-K6VK7EXV.js");
62
63
  console.log(await printWho());
63
64
  return 0;
64
65
  }
66
+ case "send": {
67
+ const { sendMessage } = await import("./send-VV25P46X.js");
68
+ const r = await sendMessage(rest);
69
+ console.log(r.line);
70
+ return r.code;
71
+ }
65
72
  case "bridge": {
66
- const { bridge } = await import("./bridge-ZOHP53DV.js");
73
+ const { bridge } = await import("./bridge-53ZTDSST.js");
67
74
  await bridge();
68
75
  return 0;
69
76
  }
70
77
  case "claude":
71
78
  case "codex":
72
79
  case "cursor": {
73
- const { launch } = await import("./launch-2IQZPPTY.js");
80
+ const { launch } = await import("./launch-ZGICGFVU.js");
74
81
  return launch(cmd, rest);
75
82
  }
76
83
  case "attach": {
77
- const { attach } = await import("./attach-6JONTTMA.js");
78
- return attach(rest[0] ?? "");
84
+ const { attach } = await import("./attach-UWADG6NX.js");
85
+ return attach(rest.find((a) => !a.startsWith("--")) ?? "", { readOnly: rest.includes("--read-only") });
79
86
  }
80
87
  default:
81
88
  console.log(USAGE);
@@ -5,15 +5,15 @@ import {
5
5
  ensureDaemon,
6
6
  launch,
7
7
  parseLaunchArgs
8
- } from "./chunk-47G477C7.js";
8
+ } from "./chunk-ZNRXLDSH.js";
9
+ import "./chunk-XXFUYME7.js";
9
10
  import {
10
11
  agentEnv,
11
12
  exitFile
12
- } from "./chunk-TSA5LD4D.js";
13
- import "./chunk-YV7BMEEB.js";
13
+ } from "./chunk-X34LPKG7.js";
14
+ import "./chunk-VY45MACP.js";
15
+ import "./chunk-ZEHTNFPV.js";
14
16
  import "./chunk-YUSY3E47.js";
15
- import "./chunk-TC3XJUJA.js";
16
- import "./chunk-JOCNCNRP.js";
17
17
  import "./chunk-FGOFRWIB.js";
18
18
  export {
19
19
  DaemonError,
@@ -0,0 +1,14 @@
1
+ import {
2
+ parseLedgeCommand,
3
+ sendMessage,
4
+ sendOnClient
5
+ } from "./chunk-A7YEHYLY.js";
6
+ import "./chunk-XXFUYME7.js";
7
+ import "./chunk-X34LPKG7.js";
8
+ import "./chunk-VY45MACP.js";
9
+ import "./chunk-FGOFRWIB.js";
10
+ export {
11
+ parseLedgeCommand,
12
+ sendMessage,
13
+ sendOnClient
14
+ };
@@ -13,8 +13,8 @@ import {
13
13
  runSetup,
14
14
  stopDetachedDaemon,
15
15
  systemdUnit
16
- } from "./chunk-USGAFLXF.js";
17
- import "./chunk-YV7BMEEB.js";
16
+ } from "./chunk-XIO4OIHR.js";
17
+ import "./chunk-ZEHTNFPV.js";
18
18
  import "./chunk-YUSY3E47.js";
19
19
  import "./chunk-FGOFRWIB.js";
20
20
  export {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  connect
3
- } from "./chunk-TC3XJUJA.js";
4
- import "./chunk-JOCNCNRP.js";
3
+ } from "./chunk-XXFUYME7.js";
4
+ import "./chunk-VY45MACP.js";
5
5
  import "./chunk-FGOFRWIB.js";
6
6
 
7
7
  // src/cli/status.ts
@@ -2,8 +2,8 @@ import {
2
2
  SERVICE,
3
3
  execRunner,
4
4
  stopDetachedDaemon
5
- } from "./chunk-USGAFLXF.js";
6
- import "./chunk-YV7BMEEB.js";
5
+ } from "./chunk-XIO4OIHR.js";
6
+ import "./chunk-ZEHTNFPV.js";
7
7
  import "./chunk-YUSY3E47.js";
8
8
  import {
9
9
  paths
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  LEDGE_VERSION
3
- } from "./chunk-RVEGELWF.js";
3
+ } from "./chunk-DKL4DFNI.js";
4
4
  export {
5
5
  LEDGE_VERSION
6
6
  };
@@ -0,0 +1,11 @@
1
+ import {
2
+ formatWho,
3
+ printWho
4
+ } from "./chunk-FRGNPUDO.js";
5
+ import "./chunk-XXFUYME7.js";
6
+ import "./chunk-VY45MACP.js";
7
+ import "./chunk-FGOFRWIB.js";
8
+ export {
9
+ formatWho,
10
+ printWho
11
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paradow/ledge",
3
- "version": "0.11.0",
3
+ "version": "0.12.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.11.0" : "0.0.0-dev";
3
-
4
- export {
5
- LEDGE_VERSION
6
- };