@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,17 +1,17 @@
1
1
  import {
2
2
  attachEnv
3
- } from "./chunk-47G477C7.js";
4
- import "./chunk-TSA5LD4D.js";
5
- import {
6
- realTmux
7
- } from "./chunk-YV7BMEEB.js";
8
- import "./chunk-YUSY3E47.js";
3
+ } from "./chunk-ZNRXLDSH.js";
9
4
  import {
10
5
  connect
11
- } from "./chunk-TC3XJUJA.js";
6
+ } from "./chunk-XXFUYME7.js";
7
+ import "./chunk-X34LPKG7.js";
12
8
  import {
13
9
  PROTOCOL_PORT
14
- } from "./chunk-JOCNCNRP.js";
10
+ } from "./chunk-VY45MACP.js";
11
+ import {
12
+ realTmux
13
+ } from "./chunk-ZEHTNFPV.js";
14
+ import "./chunk-YUSY3E47.js";
15
15
  import "./chunk-FGOFRWIB.js";
16
16
 
17
17
  // src/cli/attach.ts
@@ -47,7 +47,7 @@ async function attach(id, opts = {}) {
47
47
  if (process.stdout.isTTY) process.stdout.write(s);
48
48
  });
49
49
  write(`\x1B]0;${title}\x07`);
50
- return (opts.attach ?? attachInherit)(tmux.attachArgs(tmuxName), attachEnv(process.env));
50
+ return (opts.attach ?? attachInherit)(tmux.attachArgs(tmuxName, { readOnly: opts.readOnly }), attachEnv(process.env));
51
51
  }
52
52
  export {
53
53
  attach
@@ -7,7 +7,7 @@ import {
7
7
  parseClientMessage,
8
8
  parseServerMessage,
9
9
  readDaemonToken
10
- } from "./chunk-JOCNCNRP.js";
10
+ } from "./chunk-VY45MACP.js";
11
11
  import "./chunk-FGOFRWIB.js";
12
12
 
13
13
  // src/cli/bridge.ts
@@ -0,0 +1,105 @@
1
+ import {
2
+ connect,
3
+ sendMessageAndAwaitAck
4
+ } from "./chunk-XXFUYME7.js";
5
+ import {
6
+ CODEX_HOOK_VAR
7
+ } from "./chunk-X34LPKG7.js";
8
+
9
+ // src/cli/send.ts
10
+ import { randomUUID } from "crypto";
11
+ var USAGE = 'ledge send <nom> "<message>" [--summary <texte>] write to another Ledge session';
12
+ var SEND_MS = 4e3;
13
+ function sendUsage(args) {
14
+ const positional = [];
15
+ let summary;
16
+ for (let i = 0; i < args.length; i++) {
17
+ if (args[i] === "--summary") {
18
+ summary = args[++i];
19
+ if (summary === void 0) return void 0;
20
+ } else positional.push(args[i]);
21
+ }
22
+ const [to, text] = positional;
23
+ if (positional.length !== 2 || !to || !text) return void 0;
24
+ return { to, text, ...summary ? { summary } : {} };
25
+ }
26
+ async function sendOnClient(client, args, from, ms) {
27
+ const parsed = sendUsage(args);
28
+ if (!parsed) return { line: USAGE, code: 1 };
29
+ const { to, text, summary } = parsed;
30
+ let sent = false;
31
+ try {
32
+ const pending = sendMessageAndAwaitAck(client, { type: "session.message", rid: randomUUID(), from, to, text, ...summary ? { summary } : {} }, ms);
33
+ sent = true;
34
+ const ack = await pending;
35
+ if (ack.outcome === "accepted") return { line: `remis \xE0 ${to}`, code: 0 };
36
+ if (ack.outcome === "queued") return { line: `${to} est occup\xE9 : il le recevra d\xE8s qu'il sera libre`, code: 0 };
37
+ return { line: `refus\xE9 : ${ack.reason ?? "raison inconnue"}`, code: 1 };
38
+ } catch (e) {
39
+ return { line: sent ? "pas de r\xE9ponse de Ledge : le message est peut-\xEAtre d\xE9j\xE0 parti" : `refus\xE9 : ${e.message}`, code: 1 };
40
+ }
41
+ }
42
+ async function sendMessage(args, opts = {}) {
43
+ if (!sendUsage(args)) return { line: USAGE, code: 1 };
44
+ const env = opts.env ?? process.env;
45
+ const from = opts.from ?? (env[CODEX_HOOK_VAR] || env.LEDGE_SESSION);
46
+ if (!from) return { line: "ledge send ne marche que dans une session lanc\xE9e par Ledge", code: 1 };
47
+ const deadline = Date.now() + (opts.timeoutMs ?? SEND_MS);
48
+ const left = () => Math.max(0, deadline - Date.now());
49
+ let client;
50
+ try {
51
+ client = await connect({ port: opts.port, timeoutMs: left() });
52
+ } catch {
53
+ return { line: "daemon not running (start a session with `ledge claude`, or run `ledge daemon`)", code: 1 };
54
+ }
55
+ try {
56
+ return await sendOnClient(client, args, from, left());
57
+ } finally {
58
+ await client.close();
59
+ }
60
+ }
61
+ function parseLedgeCommand(command) {
62
+ command = command.replace(/[\r\n\s]+$/, "");
63
+ const words = [];
64
+ let word = "";
65
+ let inWord = false;
66
+ let quote;
67
+ for (let i = 0; i < command.length; i++) {
68
+ const c = command[i];
69
+ if (quote === "'") {
70
+ if (c === "'") quote = void 0;
71
+ else word += c;
72
+ continue;
73
+ }
74
+ if (quote === '"') {
75
+ if (c === '"') quote = void 0;
76
+ else if (c === "$" || c === "`") return void 0;
77
+ else if (c === "\\" && '"\\$`'.includes(command[i + 1] ?? "")) word += command[++i];
78
+ else word += c;
79
+ continue;
80
+ }
81
+ if (c === "\n" || c === "\r") return void 0;
82
+ if (c === " " || c === " ") {
83
+ if (inWord) words.push(word);
84
+ word = "";
85
+ inWord = false;
86
+ continue;
87
+ }
88
+ if (";&|<>()$`\\*?[~{".includes(c) || c === "#" && !inWord) return void 0;
89
+ inWord = true;
90
+ if (c === '"' || c === "'") quote = c;
91
+ else word += c;
92
+ }
93
+ if (quote) return void 0;
94
+ if (inWord) words.push(word);
95
+ if (words[0] !== "ledge") return void 0;
96
+ if (words[1] === "who" && words.length === 2) return { cmd: "who" };
97
+ if (words[1] === "send") return { cmd: "send", args: words.slice(2) };
98
+ return void 0;
99
+ }
100
+
101
+ export {
102
+ sendOnClient,
103
+ sendMessage,
104
+ parseLedgeCommand
105
+ };
@@ -0,0 +1,6 @@
1
+ // src/version.ts
2
+ var LEDGE_VERSION = true ? "0.12.1" : "0.0.0-dev";
3
+
4
+ export {
5
+ LEDGE_VERSION
6
+ };
@@ -1,10 +1,9 @@
1
1
  import {
2
2
  connect
3
- } from "./chunk-TC3XJUJA.js";
3
+ } from "./chunk-XXFUYME7.js";
4
4
  import {
5
5
  isArchived
6
- } from "./chunk-JOCNCNRP.js";
7
- import "./chunk-FGOFRWIB.js";
6
+ } from "./chunk-VY45MACP.js";
8
7
 
9
8
  // src/cli/who.ts
10
9
  import { basename } from "path";
@@ -37,6 +36,7 @@ async function printWho(opts = {}) {
37
36
  await client.close();
38
37
  }
39
38
  }
39
+
40
40
  export {
41
41
  formatWho,
42
42
  printWho
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  cleanTopic,
3
3
  sessionTitle
4
- } from "./chunk-JOCNCNRP.js";
4
+ } from "./chunk-VY45MACP.js";
5
5
 
6
6
  // src/daemon/crossSession.ts
7
7
  var FIRST_LINE = /^Another Claude session sent a message:\s*\n/;
@@ -19,6 +19,28 @@ function crossSession(text) {
19
19
  }
20
20
  return out.length ? out : void 0;
21
21
  }
22
+ var LEDGE_CLOSE = "</ledge-message>";
23
+ var LEDGE_BLOCK = /^<ledge-message\b([^>]*)>([\s\S]*?)(?:<\/ledge-message>|$)/;
24
+ var note = (from, to) => `(message de la session ${from}, pas de l'utilisateur ; pour r\xE9pondre : ${to === "codex" ? `ledge send ${from} "\u2026"` : `SendMessage vers ${from}`})`;
25
+ var ESCAPES = /\x1b(?:\[[0-?]*[ -/]*[@-~]|[\]PX^_][^\x07\x1b]*(?:\x07|\x1b\\)|[ -/]*[0-~])/g;
26
+ var CONTROLS = /[\x00-\x1f\x7f-\x9f]/g;
27
+ var typable = (text, newline) => text.replace(ESCAPES, "").replace(/\r\n|\r|\n/g, newline).replace(CONTROLS, " ");
28
+ var ledgeBody = (text) => typable(text.replaceAll("<", "\u2039"), " \u23CE ");
29
+ function formatLedgeMessage(m) {
30
+ const from = typable(m.from, " ").replace(/["<>]/g, "").trim();
31
+ return `<ledge-message from="${from}" tool="${m.tool}">${note(from, m.to)} ${ledgeBody(m.text)}${LEDGE_CLOSE}`;
32
+ }
33
+ function ledgeMessages(text) {
34
+ const m = LEDGE_BLOCK.exec(text.trimStart());
35
+ if (!m) return void 0;
36
+ const attrs = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]]));
37
+ const from = attrs.get("from");
38
+ const tool = attrs.get("tool");
39
+ if (!from) return void 0;
40
+ const body = m[2].trimStart();
41
+ const lead = [note(from, "codex"), note(from, "claude")].find((n) => body.startsWith(n));
42
+ return [{ from, ...tool ? { tool } : {}, text: (lead ? body.slice(lead.length) : body).trim() }];
43
+ }
22
44
  var NOTICE_HEAD = /^\[Cross-session ([\w -]+?) notice\]/;
23
45
  var NOTICE_SPLIT = /\n\s*(?=\[Cross-session [\w -]+? notice\])/;
24
46
  var DELIVERY = [
@@ -338,8 +360,54 @@ async function gitSummary(cwd, exec = execGit) {
338
360
  return [`# git status --short --branch`, status.stdout.trimEnd(), "", "# git log --oneline -10", log.stdout.trimEnd(), "", "# git diff --stat", diff.stdout.trimEnd() || "(clean)"].join("\n");
339
361
  }
340
362
 
363
+ // src/daemon/peers.ts
364
+ import { readdirSync, readFileSync } from "fs";
365
+ import { homedir } from "os";
366
+ import { basename as basename2, join } from "path";
367
+ var defaultDir = () => join(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"), "sessions");
368
+ function parsePeer(path) {
369
+ try {
370
+ const j = JSON.parse(readFileSync(path, "utf8"));
371
+ if (typeof j.pid !== "number" || typeof j.sessionId !== "string" || typeof j.name !== "string" || typeof j.messagingSocketPath !== "string") return void 0;
372
+ return { pid: j.pid, sessionId: j.sessionId, name: j.name, address: `uds:${j.messagingSocketPath}` };
373
+ } catch {
374
+ return void 0;
375
+ }
376
+ }
377
+ function readPeers(dir = defaultDir()) {
378
+ let names;
379
+ try {
380
+ names = readdirSync(dir);
381
+ } catch {
382
+ return [];
383
+ }
384
+ return names.filter((n) => n.endsWith(".json")).flatMap((n) => parsePeer(join(dir, n)) ?? []);
385
+ }
386
+ var bareName = (name) => name.replace(/ \[[^\]]*\]$/, "");
387
+ function peerFor(target, peers, sessions, dir = defaultDir()) {
388
+ const name = bareName(target);
389
+ const found = peers.filter((p) => p.name === name || p.address === name);
390
+ if (!found.length && name.startsWith("uds:") && name.endsWith(".sock")) {
391
+ const pid = basename2(name, ".sock");
392
+ const direct = /^\d+$/.test(pid) ? parsePeer(join(dir, `${pid}.json`)) : void 0;
393
+ if (direct) found.push(direct);
394
+ }
395
+ for (const { sessionId: id } of found) {
396
+ const s = sessions.find((s2) => s2.agentSessionId === id || s2.transcriptPath?.endsWith(`/${id}.jsonl`));
397
+ if (s) return s;
398
+ }
399
+ return void 0;
400
+ }
401
+ function ledgePeer(target, peers, sessions) {
402
+ const name = bareName(target);
403
+ return peerFor(target, peers, sessions) ?? sessions.find((s) => s.name === name && s.state !== "exited");
404
+ }
405
+
341
406
  export {
342
407
  crossSession,
408
+ ledgeBody,
409
+ formatLedgeMessage,
410
+ ledgeMessages,
343
411
  crossSessionNotices,
344
412
  QUESTION_TOOLS,
345
413
  pendingTool,
@@ -347,5 +415,8 @@ export {
347
415
  reduce,
348
416
  repositoryId,
349
417
  gitInfo,
350
- gitSummary
418
+ gitSummary,
419
+ readPeers,
420
+ bareName,
421
+ ledgePeer
351
422
  };