@paradow/ledge 0.8.1 → 0.9.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,25 +1,27 @@
1
1
  import {
2
- QUESTION_TOOLS
3
- } from "./chunk-Y5TCZTFL.js";
2
+ QUESTION_TOOLS,
3
+ gitInfo
4
+ } from "./chunk-I4C5U6AI.js";
4
5
  import {
5
6
  CODEX_ENV_VAR,
6
7
  CODEX_HOOK_VAR,
7
8
  codexPlan
8
- } from "./chunk-ZPANTZKC.js";
9
+ } from "./chunk-LYTRXJAQ.js";
9
10
  import {
10
11
  log
11
- } from "./chunk-L6IGH4LF.js";
12
+ } from "./chunk-YUSY3E47.js";
12
13
  import {
13
14
  connect
14
- } from "./chunk-6ZKNEJND.js";
15
- import "./chunk-KJWMK65Y.js";
15
+ } from "./chunk-IWJAJAPK.js";
16
+ import "./chunk-KDYAHPYN.js";
17
+ import "./chunk-FGOFRWIB.js";
16
18
 
17
19
  // src/cli/hook.ts
18
20
  import { readFileSync, openSync, fstatSync, readSync, closeSync } from "fs";
19
21
  import { basename, join } from "path";
20
22
  var str = (v) => typeof v === "string" ? v : void 0;
21
23
  var PROMPT_MAX = 500;
22
- var fromAgent = (t) => t.startsWith('<agent-message from="') || t.startsWith("<cross-session-message ");
24
+ var fromAgent = (t) => t.startsWith('<agent-message from="') || t.startsWith("<cross-session-message ") || t.startsWith("[Cross-session ");
23
25
  var promptText = (p) => {
24
26
  const t = str(p.prompt)?.trim();
25
27
  return t ? { text: t.slice(0, fromAgent(t) ? INPUT_MAX : PROMPT_MAX) } : {};
@@ -300,34 +302,68 @@ function mapCursorEvent(eventName, p, at, launched, transcript) {
300
302
  return null;
301
303
  }
302
304
  }
305
+ function identityContext(s) {
306
+ const where = [`projet ${s.repo ?? s.project}`, ...s.branch ? [`branche ${s.branch}`] : [], ...s.worktree ? [`worktree ${basename(s.cwd) || s.cwd}`] : []].join(", ");
307
+ return [
308
+ `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
+ "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."] : []
311
+ ].join("\n");
312
+ }
313
+ async function identity(agent, cwd, known, deadline) {
314
+ const name = known?.name ?? process.env.LEDGE_NAME;
315
+ const dir = known?.cwd ?? cwd;
316
+ if (!name || !dir) return void 0;
317
+ const git = known?.branch ? null : await Promise.race([gitInfo(dir).catch(() => null), new Promise((r) => setTimeout(() => r(null), Math.max(0, deadline - Date.now())))]);
318
+ return {
319
+ name,
320
+ tool: known?.tool ?? agent,
321
+ cwd: dir,
322
+ project: known?.project ?? (basename(dir) || dir),
323
+ ...known?.repo ?? git?.repo ? { repo: known?.repo ?? git.repo } : {},
324
+ ...known?.branch ?? git?.branch ? { branch: known?.branch ?? git.branch } : {},
325
+ ...known?.worktree ?? git?.worktree ? { worktree: true } : {}
326
+ };
327
+ }
303
328
  async function runHook(eventName, stdin, opts = {}) {
304
- const timeoutMs = opts.timeoutMs ?? 100;
305
- const deadline = Date.now() + timeoutMs;
306
329
  const agent = opts.tool ?? "claude";
307
330
  const label = agent === "claude" ? eventName : `${agent} ${eventName}`;
308
331
  if (process.env.LEDGE_CHAT === "1") {
309
332
  log(`hook ${label} ignored: Ledge chat`);
310
- return;
333
+ return void 0;
311
334
  }
335
+ let mapped = null;
336
+ let identify = false;
337
+ let known;
338
+ let deadline = Date.now() + (opts.timeoutMs ?? 100);
312
339
  try {
313
340
  const payload = JSON.parse(stdin || "{}");
314
- const mapped = agent === "cursor" ? mapCursorHook(eventName, payload, (/* @__PURE__ */ new Date()).toISOString()) : mapHook(agent, eventName, payload, (/* @__PURE__ */ new Date()).toISOString());
341
+ mapped = agent === "cursor" ? mapCursorHook(eventName, payload, (/* @__PURE__ */ new Date()).toISOString()) : mapHook(agent, eventName, payload, (/* @__PURE__ */ new Date()).toISOString());
315
342
  if (!mapped) {
316
343
  log(`hook ${label} ignored: unknown event, malformed payload, non-terminal SessionEnd or subagent event without a session of its own`);
317
- return;
344
+ return void 0;
318
345
  }
346
+ const launchedId = agent === "codex" ? codexLedgeId() : agent === "claude" ? process.env.LEDGE_SESSION : void 0;
347
+ identify = eventName === "SessionStart" && !!launchedId && mapped.id === launchedId;
348
+ const timeoutMs = opts.timeoutMs ?? (identify ? 1e3 : 100);
349
+ deadline = Date.now() + timeoutMs;
319
350
  const client = await connect({ port: opts.port, timeoutMs });
320
351
  client.send({ type: "session.event", id: mapped.id, event: mapped.event, agent: mapped.agent, ...mapped.cwd ? { cwd: mapped.cwd } : {} });
352
+ if (identify) known = (await client.next("snapshot", Math.max(0, deadline - Date.now())).catch(() => void 0))?.sessions.find((s) => s.id === mapped.id);
321
353
  await Promise.race([client.close(), new Promise((r) => setTimeout(r, Math.max(0, deadline - Date.now())))]);
322
354
  } catch (e) {
323
355
  log(`hook ${label} dropped: ${e.message}`);
324
356
  }
357
+ if (!identify || !mapped) return void 0;
358
+ const who = await identity(agent, mapped.cwd, known, deadline);
359
+ return who ? JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: identityContext(who) } }) : void 0;
325
360
  }
326
361
  export {
327
362
  INPUT_MAX,
328
363
  REPLY_MAX,
329
364
  codexLedgeId,
330
365
  cursorReply,
366
+ identityContext,
331
367
  mapClaudeHook,
332
368
  mapCursorHook,
333
369
  mapHook,
package/dist/cli/index.js CHANGED
@@ -8,6 +8,7 @@ ledge hook [codex|cursor] <Event> receive a hook (rea
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
+ ledge who list the live sessions (name, tool, state, project, branch, topic)
11
12
  ledge bridge exchange Ledge messages over SSH stdio
12
13
  ledge --version print the installed version`;
13
14
  async function hookMain(args) {
@@ -16,8 +17,10 @@ async function hookMain(args) {
16
17
  try {
17
18
  const chunks = [];
18
19
  for await (const c of process.stdin) chunks.push(c);
19
- const { runHook } = await import("./hook-BBVUY4RK.js");
20
- await runHook(event, Buffer.concat(chunks).toString("utf8"), { tool });
20
+ const { runHook } = await import("./hook-5HXY2UGM.js");
21
+ const out = await runHook(event, Buffer.concat(chunks).toString("utf8"), { tool });
22
+ if (out) process.stdout.write(`${out}
23
+ `);
21
24
  } catch {
22
25
  }
23
26
  if (tool === "cursor") process.stdout.write("{}\n");
@@ -26,7 +29,7 @@ async function main(argv) {
26
29
  const [cmd, ...rest] = argv;
27
30
  switch (cmd) {
28
31
  case "daemon": {
29
- const { startDaemon } = await import("./daemon-MSDX3HWZ.js");
32
+ const { startDaemon } = await import("./daemon-NDXT3ZDF.js");
30
33
  await startDaemon();
31
34
  await new Promise(() => {
32
35
  });
@@ -34,39 +37,44 @@ async function main(argv) {
34
37
  }
35
38
  case "--version":
36
39
  case "version": {
37
- const { LEDGE_VERSION } = await import("./version-VOKMNNLW.js");
40
+ const { LEDGE_VERSION } = await import("./version-AQN6JNH7.js");
38
41
  console.log(LEDGE_VERSION);
39
42
  return 0;
40
43
  }
41
44
  case "setup": {
42
- const { runSetup } = await import("./setup-RMPIUCPH.js");
45
+ const { runSetup } = await import("./setup-7AEH5DC3.js");
43
46
  for (const l of await runSetup()) console.log(l);
44
47
  return 0;
45
48
  }
46
49
  case "uninstall": {
47
- const { runUninstall } = await import("./uninstall-GI4WT3UQ.js");
50
+ const { runUninstall } = await import("./uninstall-7U7ENKOS.js");
48
51
  const r = await runUninstall({ purge: rest.includes("--purge") });
49
52
  for (const l of r.lines) console.log(l);
50
53
  return r.ok ? 0 : 1;
51
54
  }
52
55
  case "status": {
53
- const { printStatus } = await import("./status-L2GROM4J.js");
56
+ const { printStatus } = await import("./status-PDNC6IC2.js");
54
57
  console.log(await printStatus());
55
58
  return 0;
56
59
  }
60
+ case "who": {
61
+ const { printWho } = await import("./who-LIMQJMNI.js");
62
+ console.log(await printWho());
63
+ return 0;
64
+ }
57
65
  case "bridge": {
58
- const { bridge } = await import("./bridge-QKQW7UD6.js");
66
+ const { bridge } = await import("./bridge-XHPCIYE4.js");
59
67
  await bridge();
60
68
  return 0;
61
69
  }
62
70
  case "claude":
63
71
  case "codex":
64
72
  case "cursor": {
65
- const { launch } = await import("./launch-RWT4EWWI.js");
73
+ const { launch } = await import("./launch-HEJKGMYS.js");
66
74
  return launch(cmd, rest);
67
75
  }
68
76
  case "attach": {
69
- const { attach } = await import("./attach-KTZOG7E5.js");
77
+ const { attach } = await import("./attach-AZFKVVKL.js");
70
78
  return attach(rest[0] ?? "");
71
79
  }
72
80
  default:
@@ -5,15 +5,16 @@ import {
5
5
  ensureDaemon,
6
6
  launch,
7
7
  parseLaunchArgs
8
- } from "./chunk-7AKUPFMS.js";
8
+ } from "./chunk-65XGYBHL.js";
9
9
  import {
10
10
  agentEnv,
11
11
  exitFile
12
- } from "./chunk-ZPANTZKC.js";
13
- import "./chunk-3TNPEAMR.js";
14
- import "./chunk-L6IGH4LF.js";
15
- import "./chunk-6ZKNEJND.js";
16
- import "./chunk-KJWMK65Y.js";
12
+ } from "./chunk-LYTRXJAQ.js";
13
+ import "./chunk-TO4XO2ML.js";
14
+ import "./chunk-YUSY3E47.js";
15
+ import "./chunk-IWJAJAPK.js";
16
+ import "./chunk-KDYAHPYN.js";
17
+ import "./chunk-FGOFRWIB.js";
17
18
  export {
18
19
  DaemonError,
19
20
  agentEnv,
@@ -13,9 +13,10 @@ import {
13
13
  runSetup,
14
14
  stopDetachedDaemon,
15
15
  systemdUnit
16
- } from "./chunk-Q5CLZPYJ.js";
17
- import "./chunk-3TNPEAMR.js";
18
- import "./chunk-L6IGH4LF.js";
16
+ } from "./chunk-Q7RCYJ4X.js";
17
+ import "./chunk-TO4XO2ML.js";
18
+ import "./chunk-YUSY3E47.js";
19
+ import "./chunk-FGOFRWIB.js";
19
20
  export {
20
21
  CLAUDE_EVENTS,
21
22
  CODEX_EVENTS,
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  connect
3
- } from "./chunk-6ZKNEJND.js";
4
- import "./chunk-KJWMK65Y.js";
3
+ } from "./chunk-IWJAJAPK.js";
4
+ import "./chunk-KDYAHPYN.js";
5
+ import "./chunk-FGOFRWIB.js";
5
6
 
6
7
  // src/cli/status.ts
7
8
  async function printStatus(opts = {}) {
@@ -2,11 +2,12 @@ import {
2
2
  SERVICE,
3
3
  execRunner,
4
4
  stopDetachedDaemon
5
- } from "./chunk-Q5CLZPYJ.js";
6
- import "./chunk-3TNPEAMR.js";
5
+ } from "./chunk-Q7RCYJ4X.js";
6
+ import "./chunk-TO4XO2ML.js";
7
+ import "./chunk-YUSY3E47.js";
7
8
  import {
8
9
  paths
9
- } from "./chunk-L6IGH4LF.js";
10
+ } from "./chunk-FGOFRWIB.js";
10
11
 
11
12
  // src/cli/uninstall.ts
12
13
  import { readFile, writeFile, rename, rm, lstat, realpath } from "fs/promises";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  LEDGE_VERSION
3
- } from "./chunk-CCB5DTOS.js";
3
+ } from "./chunk-YXXXSRQX.js";
4
4
  export {
5
5
  LEDGE_VERSION
6
6
  };
@@ -0,0 +1,43 @@
1
+ import {
2
+ connect
3
+ } from "./chunk-IWJAJAPK.js";
4
+ import {
5
+ isArchived
6
+ } from "./chunk-KDYAHPYN.js";
7
+ import "./chunk-FGOFRWIB.js";
8
+
9
+ // src/cli/who.ts
10
+ import { basename } from "path";
11
+ function formatWho(sessions, self) {
12
+ const live = sessions.filter((s) => s.state !== "exited" && !isArchived(s));
13
+ if (live.length === 0) return "aucune autre session";
14
+ const rows = live.map((s) => [
15
+ s.id === self ? "*" : " ",
16
+ s.name,
17
+ s.tool,
18
+ s.state,
19
+ `${s.repo ?? s.project}${s.worktree ? ` (${basename(s.cwd)})` : ""}`,
20
+ s.branch ?? "-",
21
+ `${s.topic ?? "-"}${s.source === "hook_only" ? " \xB7 lecture seule" : ""}`
22
+ ]);
23
+ const head = [" ", "NOM", "OUTIL", "\xC9TAT", "PROJET", "BRANCHE", "SUJET"];
24
+ const widths = head.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
25
+ return [head, ...rows].map((r) => r.map((c, i) => i === r.length - 1 ? c : c.padEnd(widths[i])).join(" ")).join("\n");
26
+ }
27
+ async function printWho(opts = {}) {
28
+ let client;
29
+ try {
30
+ client = await connect({ port: opts.port, timeoutMs: 800 });
31
+ } catch {
32
+ return "daemon not running (start a session with `ledge claude`, or run `ledge daemon`)";
33
+ }
34
+ try {
35
+ return formatWho((await client.next("snapshot")).sessions, opts.self ?? process.env.LEDGE_SESSION);
36
+ } finally {
37
+ await client.close();
38
+ }
39
+ }
40
+ export {
41
+ formatWho,
42
+ printWho
43
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paradow/ledge",
3
- "version": "0.8.1",
3
+ "version": "0.9.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.8.1" : "0.0.0-dev";
3
-
4
- export {
5
- LEDGE_VERSION
6
- };