@paradow/ledge 0.16.0 → 0.17.0
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 +2 -0
- package/dist/cli/{attach-5H727FCW.js → attach-EYIX7FZN.js} +7 -7
- package/dist/cli/{bridge-LXMOQN6Q.js → bridge-6UHCD76N.js} +2 -2
- package/dist/cli/{chunk-MQICEVKR.js → chunk-36KJUV62.js} +6 -6
- package/dist/cli/chunk-5ZCHFGCV.js +115 -0
- package/dist/cli/{chunk-QJVODYJ5.js → chunk-AQ25HFBP.js} +40 -2
- package/dist/cli/{chunk-R5462SQM.js → chunk-ELLXOV4Q.js} +2 -1
- package/dist/cli/{chunk-BNKPXSUY.js → chunk-JQZHXVIZ.js} +1 -1
- package/dist/cli/{chunk-2H5FJH4Q.js → chunk-LAUNHDTX.js} +2 -2
- package/dist/cli/{chunk-DBSCO3HI.js → chunk-NC5YAEJB.js} +56 -7
- package/dist/cli/{chunk-S4ANPQGA.js → chunk-OVJL2QTN.js} +2 -2
- package/dist/cli/{chunk-TB47EHML.js → chunk-Q7YNLP66.js} +2 -2
- package/dist/cli/{chunk-Q5WWH7FG.js → chunk-QD27W6W5.js} +681 -630
- package/dist/cli/chunk-V2H7UD2N.js +6 -0
- package/dist/cli/{chunk-H36JD4E3.js → chunk-VE4LGNXX.js} +2 -2
- package/dist/cli/{chunk-DEXDXDO4.js → chunk-VVK4I6B5.js} +1 -1
- package/dist/cli/{daemon-HWAHX4RR.js → daemon-QEJLAIBT.js} +355 -40
- package/dist/cli/{hook-LY2YTK5U.js → hook-AE5HC6AS.js} +18 -15
- package/dist/cli/index.js +30 -11
- package/dist/cli/{launch-UQEFFPBN.js → launch-B4G5OFBV.js} +7 -7
- package/dist/cli/send-2OZ3YZRV.js +18 -0
- package/dist/cli/{setup-3N2EOJXS.js → setup-2VM4GCDJ.js} +4 -4
- package/dist/cli/{status-ZQSYXILE.js → status-3VJAO6UL.js} +3 -3
- package/dist/cli/task-PMKNYP6M.js +18 -0
- package/dist/cli/tui-DWA2EDC2.js +3561 -0
- package/dist/cli/{uninstall-W2OMKU3B.js → uninstall-AKUWE33D.js} +4 -4
- package/dist/cli/{version-5DA7G7HN.js → version-WOGCIZY3.js} +1 -1
- package/dist/cli/who-37TNDYVA.js +11 -0
- package/package.json +7 -1
- package/dist/cli/chunk-CBKKOJIG.js +0 -6
- package/dist/cli/send-EY63XQXI.js +0 -14
- package/dist/cli/who-VGIMGQNS.js +0 -11
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ ledge claude # start Claude Code in a tracked tmux session (tab renam
|
|
|
42
42
|
ledge claude --name max # pick the session's Ledge 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
|
+
ledge tui # follow and answer every session from this terminal (--notify bell|osc9|off)
|
|
45
46
|
ledge daemon # run the daemon in the foreground (normally run by the systemd service)
|
|
46
47
|
ledge uninstall # undo ledge setup: hooks, service and ~/.local/bin/ledge (--purge: ~/.ledge too)
|
|
47
48
|
```
|
|
@@ -65,6 +66,7 @@ systemctl --user disable --now ledge-daemon.service # back to on-demand start
|
|
|
65
66
|
~/.ledge/daemon.log dropped hooks, failed injections, poll errors
|
|
66
67
|
~/.ledge/chat.json the day's chat conversation (date, session id)
|
|
67
68
|
~/.ledge/chat/context/ files the chat reads (sessions.md, pane-*, reply-*, git-*)
|
|
69
|
+
~/.ledge/tui.json ledge tui's settings (its language)
|
|
68
70
|
~/.ledge/daemon.pid PID of the running daemon, removed when it stops cleanly
|
|
69
71
|
~/.config/systemd/user/ledge-daemon.service user unit written by `ledge setup`
|
|
70
72
|
~/.ledge/exit-<id> transient: the agent's exit code, written by the launcher's wrapper when the agent ends and read and deleted by `ledge claude`
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
attachEnv
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-36KJUV62.js";
|
|
4
4
|
import {
|
|
5
5
|
connect
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-VVK4I6B5.js";
|
|
7
|
+
import "./chunk-VE4LGNXX.js";
|
|
8
8
|
import {
|
|
9
9
|
PROTOCOL_PORT,
|
|
10
10
|
backendOf
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-QD27W6W5.js";
|
|
12
12
|
import {
|
|
13
13
|
realTmux
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-LAUNHDTX.js";
|
|
15
|
+
import "./chunk-JQZHXVIZ.js";
|
|
16
|
+
import "./chunk-ELLXOV4Q.js";
|
|
17
17
|
|
|
18
18
|
// src/cli/attach.ts
|
|
19
19
|
import { spawn } from "child_process";
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DaemonRefusedError,
|
|
3
3
|
connect
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VVK4I6B5.js";
|
|
5
5
|
import {
|
|
6
6
|
exitFile,
|
|
7
7
|
spawnSession
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VE4LGNXX.js";
|
|
9
9
|
import {
|
|
10
10
|
PROTOCOL_PORT
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-QD27W6W5.js";
|
|
12
12
|
import {
|
|
13
13
|
realTmux
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-LAUNHDTX.js";
|
|
15
15
|
import {
|
|
16
16
|
log
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-JQZHXVIZ.js";
|
|
18
18
|
import {
|
|
19
19
|
ensureLedgeDir
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-ELLXOV4Q.js";
|
|
21
21
|
|
|
22
22
|
// src/cli/launch.ts
|
|
23
23
|
import { spawn } from "child_process";
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
connect
|
|
3
|
+
} from "./chunk-VVK4I6B5.js";
|
|
4
|
+
import {
|
|
5
|
+
CODEX_HOOK_VAR
|
|
6
|
+
} from "./chunk-VE4LGNXX.js";
|
|
7
|
+
import {
|
|
8
|
+
TASK_TEXT_MAX,
|
|
9
|
+
TASK_TITLE_MAX
|
|
10
|
+
} from "./chunk-QD27W6W5.js";
|
|
11
|
+
|
|
12
|
+
// src/cli/task.ts
|
|
13
|
+
import { randomUUID } from "crypto";
|
|
14
|
+
var TASK_USAGE = `ledge task give <nom> "<titre>" ["<d\xE9tail>"] confier un travail \xE0 une session
|
|
15
|
+
ledge task list [--all] tes t\xE2ches (tenues ou confi\xE9es), ouvertes ; --all : closes aussi
|
|
16
|
+
ledge task show <id> une t\xE2che, son d\xE9tail et son historique
|
|
17
|
+
ledge task take <id> la prendre
|
|
18
|
+
ledge task done <id> ["<r\xE9sultat>"] la terminer (la session qui l'a confi\xE9e est pr\xE9venue)
|
|
19
|
+
ledge task refuse <id> "<raison>" la refuser, ou l'annuler si tu l'as confi\xE9e
|
|
20
|
+
ledge task pass <id> <nom> ["<note>"] la passer \xE0 une autre session`;
|
|
21
|
+
var TASK_MS = 4e3;
|
|
22
|
+
var ID = /^t[0-9a-f]{6}$/;
|
|
23
|
+
function taskArgs(args) {
|
|
24
|
+
const [op, ...rest] = args;
|
|
25
|
+
const id = rest[0] && ID.test(rest[0]) ? rest[0] : void 0;
|
|
26
|
+
const fits = (min, max) => rest.length >= min && rest.length <= max && rest.slice(0, min).every((a) => a.trim() !== "");
|
|
27
|
+
switch (op) {
|
|
28
|
+
case "give":
|
|
29
|
+
return fits(2, 3) && rest[1].length <= TASK_TITLE_MAX && (rest[2]?.length ?? 0) <= TASK_TEXT_MAX ? { op, to: rest[0], title: rest[1], ...rest[2] ? { text: rest[2] } : {} } : void 0;
|
|
30
|
+
case "list":
|
|
31
|
+
return rest.length === 0 ? { op } : rest.length === 1 && rest[0] === "--all" ? { op, all: true } : void 0;
|
|
32
|
+
case "show":
|
|
33
|
+
case "take":
|
|
34
|
+
return fits(1, 1) && id ? { op, task: id } : void 0;
|
|
35
|
+
case "done":
|
|
36
|
+
return fits(1, 2) && id && (rest[1]?.length ?? 0) <= TASK_TEXT_MAX ? { op, task: id, ...rest[1] ? { text: rest[1] } : {} } : void 0;
|
|
37
|
+
case "refuse":
|
|
38
|
+
return fits(2, 2) && id && rest[1].length <= TASK_TEXT_MAX ? { op, task: id, text: rest[1] } : void 0;
|
|
39
|
+
case "pass":
|
|
40
|
+
return fits(2, 3) && id && (rest[2]?.length ?? 0) <= TASK_TEXT_MAX ? { op, task: id, to: rest[1], ...rest[2] ? { text: rest[2] } : {} } : void 0;
|
|
41
|
+
default:
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
var STATE = { open: "\xE0 prendre", taken: "prise", done: "termin\xE9e", refused: "refus\xE9e", dropped: "abandonn\xE9e" };
|
|
46
|
+
var time = (iso) => iso.slice(0, 16).replace("T", " ");
|
|
47
|
+
function formatTask(t) {
|
|
48
|
+
return [
|
|
49
|
+
`${t.id} \xB7 ${STATE[t.state]} \xB7 de ${t.giverName} \xE0 ${t.ownerName} \xB7 ${time(t.at)}`,
|
|
50
|
+
t.title,
|
|
51
|
+
...t.text ? ["", t.text] : [],
|
|
52
|
+
...t.result ? ["", `${t.state === "done" ? "R\xE9sultat" : "Raison"} : ${t.result}`] : [],
|
|
53
|
+
"",
|
|
54
|
+
"Historique :",
|
|
55
|
+
...t.log.map((l) => ` ${time(l.at)} ${l.by} : ${l.what}`)
|
|
56
|
+
].join("\n");
|
|
57
|
+
}
|
|
58
|
+
async function taskOnClient(client, args, by, ms) {
|
|
59
|
+
const op = taskArgs(args);
|
|
60
|
+
if (!op) return { line: TASK_USAGE, code: 1 };
|
|
61
|
+
try {
|
|
62
|
+
client.send({ type: "task.cmd", rid: randomUUID(), by, ...op });
|
|
63
|
+
const r = await client.next("task.result", ms);
|
|
64
|
+
if (r.reason !== void 0) return { line: `refus\xE9 : ${r.reason}`, code: 1 };
|
|
65
|
+
if (op.op === "list") {
|
|
66
|
+
const list = r.tasks ?? [];
|
|
67
|
+
if (!list.length) return { line: op.all ? "aucune t\xE2che" : "aucune t\xE2che ouverte", code: 0 };
|
|
68
|
+
return { line: list.map((t2) => `${t2.id} ${STATE[t2.state].padEnd(10)} ${t2.giverName} \u2192 ${t2.ownerName} ${t2.title}`).join("\n"), code: 0 };
|
|
69
|
+
}
|
|
70
|
+
const t = r.task;
|
|
71
|
+
switch (op.op) {
|
|
72
|
+
case "give":
|
|
73
|
+
return { line: `t\xE2che ${t.id} confi\xE9e \xE0 ${t.ownerName} : elle lui arrive \xE0 son prochain moment libre`, code: 0 };
|
|
74
|
+
case "take":
|
|
75
|
+
return { line: `t\xE2che ${t.id} prise`, code: 0 };
|
|
76
|
+
case "done":
|
|
77
|
+
return { line: `t\xE2che ${t.id} termin\xE9e ; Ledge pr\xE9vient ${t.giverName}`, code: 0 };
|
|
78
|
+
case "refuse":
|
|
79
|
+
return { line: t.giver === by ? `t\xE2che ${t.id} annul\xE9e ; Ledge pr\xE9vient ${t.ownerName}` : `t\xE2che ${t.id} refus\xE9e ; Ledge pr\xE9vient ${t.giverName}`, code: 0 };
|
|
80
|
+
case "pass":
|
|
81
|
+
return { line: `t\xE2che ${t.id} pass\xE9e \xE0 ${t.ownerName}`, code: 0 };
|
|
82
|
+
default:
|
|
83
|
+
return { line: formatTask(t), code: 0 };
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return { line: `pas de r\xE9ponse de Ledge : ${e.message}`, code: 1 };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function runTask(args, opts = {}) {
|
|
90
|
+
if (!taskArgs(args)) return { line: TASK_USAGE, code: args.length ? 1 : 0 };
|
|
91
|
+
const env = opts.env ?? process.env;
|
|
92
|
+
const by = env[CODEX_HOOK_VAR] || env.LEDGE_SESSION;
|
|
93
|
+
if (!by) return { line: "ledge task ne marche que dans une session lanc\xE9e par Ledge", code: 1 };
|
|
94
|
+
const deadline = Date.now() + (opts.timeoutMs ?? TASK_MS);
|
|
95
|
+
const left = () => Math.max(0, deadline - Date.now());
|
|
96
|
+
let client;
|
|
97
|
+
try {
|
|
98
|
+
client = await connect({ port: opts.port, timeoutMs: left() });
|
|
99
|
+
} catch {
|
|
100
|
+
return { line: "daemon not running (start a session with `ledge claude`, or run `ledge daemon`)", code: 1 };
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
return await taskOnClient(client, args, by, left());
|
|
104
|
+
} finally {
|
|
105
|
+
await client.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
TASK_USAGE,
|
|
111
|
+
taskArgs,
|
|
112
|
+
formatTask,
|
|
113
|
+
taskOnClient,
|
|
114
|
+
runTask
|
|
115
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
connect,
|
|
3
3
|
sendMessageAndAwaitAck
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VVK4I6B5.js";
|
|
5
5
|
import {
|
|
6
6
|
CODEX_HOOK_VAR
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-VE4LGNXX.js";
|
|
8
8
|
|
|
9
9
|
// src/cli/send.ts
|
|
10
10
|
import { randomUUID } from "crypto";
|
|
@@ -58,6 +58,40 @@ async function sendMessage(args, opts = {}) {
|
|
|
58
58
|
await client.close();
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
var READ_USAGE = "ledge read <id> print a long message whole (its id is in the pointer you received)";
|
|
62
|
+
var MAIL_ID = /^m[0-9a-f]{6,40}$/;
|
|
63
|
+
async function readOnClient(client, args, by, ms) {
|
|
64
|
+
const [id] = args;
|
|
65
|
+
if (args.length !== 1 || !id || !MAIL_ID.test(id)) return { line: READ_USAGE, code: 1 };
|
|
66
|
+
try {
|
|
67
|
+
client.send({ type: "message.read", id, by });
|
|
68
|
+
const body = await client.next("message.body", ms);
|
|
69
|
+
if (body.text === void 0) return { line: `introuvable : ${body.reason ?? "raison inconnue"}`, code: 1 };
|
|
70
|
+
return { line: `De ${body.from} \xE0 ${body.to}, ${body.at} :
|
|
71
|
+
${body.text}`, code: 0 };
|
|
72
|
+
} catch (e) {
|
|
73
|
+
return { line: `pas de r\xE9ponse de Ledge : ${e.message}`, code: 1 };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function readMessage(args, opts = {}) {
|
|
77
|
+
if (args.length !== 1 || !MAIL_ID.test(args[0])) return { line: READ_USAGE, code: 1 };
|
|
78
|
+
const env = opts.env ?? process.env;
|
|
79
|
+
const by = env[CODEX_HOOK_VAR] || env.LEDGE_SESSION;
|
|
80
|
+
if (!by) return { line: "ledge read ne marche que dans une session lanc\xE9e par Ledge", code: 1 };
|
|
81
|
+
const deadline = Date.now() + (opts.timeoutMs ?? SEND_MS);
|
|
82
|
+
const left = () => Math.max(0, deadline - Date.now());
|
|
83
|
+
let client;
|
|
84
|
+
try {
|
|
85
|
+
client = await connect({ port: opts.port, timeoutMs: left() });
|
|
86
|
+
} catch {
|
|
87
|
+
return { line: "daemon not running (start a session with `ledge claude`, or run `ledge daemon`)", code: 1 };
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return await readOnClient(client, args, by, left());
|
|
91
|
+
} finally {
|
|
92
|
+
await client.close();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
61
95
|
function parseLedgeCommand(command) {
|
|
62
96
|
command = command.replace(/[\r\n\s]+$/, "");
|
|
63
97
|
const words = [];
|
|
@@ -95,11 +129,15 @@ function parseLedgeCommand(command) {
|
|
|
95
129
|
if (words[0] !== "ledge") return void 0;
|
|
96
130
|
if (words[1] === "who" && words.length === 2) return { cmd: "who" };
|
|
97
131
|
if (words[1] === "send") return { cmd: "send", args: words.slice(2) };
|
|
132
|
+
if (words[1] === "read") return { cmd: "read", args: words.slice(2) };
|
|
133
|
+
if (words[1] === "task") return { cmd: "task", args: words.slice(2) };
|
|
98
134
|
return void 0;
|
|
99
135
|
}
|
|
100
136
|
|
|
101
137
|
export {
|
|
102
138
|
sendOnClient,
|
|
103
139
|
sendMessage,
|
|
140
|
+
readOnClient,
|
|
141
|
+
readMessage,
|
|
104
142
|
parseLedgeCommand
|
|
105
143
|
};
|
|
@@ -17,7 +17,7 @@ function ledgeDir() {
|
|
|
17
17
|
}
|
|
18
18
|
function paths() {
|
|
19
19
|
const dir = ledgeDir();
|
|
20
|
-
return { dir, tmuxConf: join(dir, "tmux.conf"), journal: join(dir, "sessions.json"), log: join(dir, "daemon.log"), pid: join(dir, "daemon.pid"), history: join(dir, "history"), machines: join(dir, "machines.json"), projects: join(dir, "projects.json"), island: join(dir, "island.json"), token: join(dir, "token"), mobile: join(dir, "mobile.json"), mobileSession: join(dir, "mobile-session.json"), mobileKey: join(dir, "mobile-key"), devices: join(dir, "devices.json"), revocations: join(dir, "revocations.json") };
|
|
20
|
+
return { dir, tmuxConf: join(dir, "tmux.conf"), journal: join(dir, "sessions.json"), log: join(dir, "daemon.log"), pid: join(dir, "daemon.pid"), history: join(dir, "history"), mail: join(dir, "mail.json"), tasks: join(dir, "tasks.json"), machines: join(dir, "machines.json"), projects: join(dir, "projects.json"), island: join(dir, "island.json"), token: join(dir, "token"), mobile: join(dir, "mobile.json"), mobileSession: join(dir, "mobile-session.json"), mobileKey: join(dir, "mobile-key"), devices: join(dir, "devices.json"), revocations: join(dir, "revocations.json") };
|
|
21
21
|
}
|
|
22
22
|
async function ensureLedgeDir() {
|
|
23
23
|
const p = paths();
|
|
@@ -33,6 +33,7 @@ ${TITLE_LINES}`);
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export {
|
|
36
|
+
ledgeDir,
|
|
36
37
|
paths,
|
|
37
38
|
ensureLedgeDir
|
|
38
39
|
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ledgeTitleRest
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-VE4LGNXX.js";
|
|
4
4
|
import {
|
|
5
5
|
cleanTopic,
|
|
6
6
|
sessionTitle
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-QD27W6W5.js";
|
|
8
8
|
|
|
9
9
|
// src/daemon/crossSession.ts
|
|
10
|
+
import { createHash } from "crypto";
|
|
10
11
|
var FIRST_LINE = /^Another Claude session sent a message:\s*\n/;
|
|
11
12
|
var BLOCK = /<cross-session-message\b([^>]*)>([\s\S]*?)(?:<\/cross-session-message>|(?=\n[ \t]*<cross-session-message\b)|$)/g;
|
|
12
13
|
var ATTR = /([\w-]+)="([^"]*)"/g;
|
|
@@ -24,7 +25,7 @@ function crossSession(text) {
|
|
|
24
25
|
}
|
|
25
26
|
var LEDGE_CLOSE = "</ledge-message>";
|
|
26
27
|
var LEDGE_BLOCK = /^<ledge-message\b([^>]*)>([\s\S]*?)(?:<\/ledge-message>|$)/;
|
|
27
|
-
var messageNote = (from, to) => `(message de la session ${from}, pas de l'utilisateur ; pour r\xE9pondre : ${to === "
|
|
28
|
+
var messageNote = (from, to) => `(message de la session ${from}, pas de l'utilisateur ; pour r\xE9pondre : ${to === "claude" ? `SendMessage vers ${from}` : `ledge send ${from} "\u2026"`})`;
|
|
28
29
|
var NOTE_SENDER = /^\(message de la session (.+?), pas de l'utilisateur ; /;
|
|
29
30
|
function stripNote(text) {
|
|
30
31
|
const body = text.trimStart();
|
|
@@ -36,9 +37,20 @@ var ESCAPES = /\x1b(?:\[[0-?]*[ -/]*[@-~]|[\]PX^_][^\x07\x1b]*(?:\x07|\x1b\\)|[
|
|
|
36
37
|
var CONTROLS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
37
38
|
var typable = (text, newline) => text.replace(ESCAPES, "").replace(/\r\n|\r|\n/g, newline).replace(CONTROLS, " ");
|
|
38
39
|
var ledgeBody = (text) => typable(text.replaceAll("<", "\u2039"), " \u23CE ");
|
|
40
|
+
var INLINE_MAX = 800;
|
|
41
|
+
var INLINE_LINES = 8;
|
|
42
|
+
var POINTER_HEAD = 200;
|
|
43
|
+
var lineCount = (text) => text.split(/\r\n|\r|\n/).length;
|
|
44
|
+
var mailId = (rid, length = 6) => `m${createHash("sha1").update(rid).digest("hex").slice(0, length)}`;
|
|
45
|
+
var inline = (text) => text.length <= INLINE_MAX && lineCount(text) <= INLINE_LINES;
|
|
46
|
+
var attr = (s) => typable(s, " ").replace(/["<>]/g, "").trim();
|
|
39
47
|
function formatLedgeMessage(m) {
|
|
40
|
-
const from =
|
|
41
|
-
|
|
48
|
+
const from = attr(m.from);
|
|
49
|
+
const open = `<ledge-message from="${from}" tool="${m.tool}"${m.id ? ` id="${attr(m.id)}"` : ""}>${messageNote(from, m.to)} `;
|
|
50
|
+
if (!m.id || inline(m.text)) return `${open}${ledgeBody(m.text)}${LEDGE_CLOSE}`;
|
|
51
|
+
const first = m.summary?.trim() || m.text.trim().split(/\r\n|\r|\n/)[0].trim();
|
|
52
|
+
const head = ledgeBody(first.length > POINTER_HEAD ? `${first.slice(0, POINTER_HEAD)}\u2026` : first);
|
|
53
|
+
return `${open}${head} \u2026 [message de ${m.text.length} caract\xE8res, ${lineCount(m.text)} lignes : lis-le en entier avec ledge read ${attr(m.id)}]${LEDGE_CLOSE}`;
|
|
42
54
|
}
|
|
43
55
|
function ledgeMessages(text) {
|
|
44
56
|
const m = LEDGE_BLOCK.exec(text.trimStart());
|
|
@@ -46,8 +58,39 @@ function ledgeMessages(text) {
|
|
|
46
58
|
const attrs = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]]));
|
|
47
59
|
const from = attrs.get("from");
|
|
48
60
|
const tool = attrs.get("tool");
|
|
61
|
+
const id = attrs.get("id");
|
|
49
62
|
if (!from) return void 0;
|
|
50
|
-
return [{ from, ...tool ? { tool } : {}, text: stripNote(m[2]).text.trim() }];
|
|
63
|
+
return [{ from, ...tool ? { tool } : {}, ...id ? { id } : {}, text: stripNote(m[2]).text.trim() }];
|
|
64
|
+
}
|
|
65
|
+
var NOTICE_BLOCK = /^<ledge-notice\b([^>]*)>([\s\S]*?)(?:<\/ledge-notice>|$)/;
|
|
66
|
+
var NOTICE_QUOTE = 80;
|
|
67
|
+
var NOTICE_REASON = " \xBB) : ";
|
|
68
|
+
function formatLedgeNotice(n) {
|
|
69
|
+
const to = attr(n.to);
|
|
70
|
+
const flat = typable(n.text, " ").trim();
|
|
71
|
+
const quote = ledgeBody(flat.length > NOTICE_QUOTE ? `${flat.slice(0, NOTICE_QUOTE)}\u2026` : flat);
|
|
72
|
+
return `<ledge-notice to="${to}">Ledge n'a pas pu remettre ton message \xE0 ${to} (\xAB ${quote}${NOTICE_REASON}${ledgeBody(n.reason)}</ledge-notice>`;
|
|
73
|
+
}
|
|
74
|
+
var TASK_BLOCK = /^<ledge-task\b([^>]*)>([\s\S]*?)(?:<\/ledge-task>|$)/;
|
|
75
|
+
function formatLedgeTask(t) {
|
|
76
|
+
return `<ledge-task id="${attr(t.id)}" from="${attr(t.from)}">${ledgeBody(t.text)}</ledge-task>`;
|
|
77
|
+
}
|
|
78
|
+
function ledgeTask(text) {
|
|
79
|
+
const m = TASK_BLOCK.exec(text.trimStart());
|
|
80
|
+
if (!m) return void 0;
|
|
81
|
+
const attrs = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]]));
|
|
82
|
+
const id = attrs.get("id");
|
|
83
|
+
const from = attrs.get("from");
|
|
84
|
+
return id ? { id, ...from ? { from } : {}, text: m[2].trim() } : void 0;
|
|
85
|
+
}
|
|
86
|
+
function ledgeNotice(text) {
|
|
87
|
+
const m = NOTICE_BLOCK.exec(text.trimStart());
|
|
88
|
+
if (!m) return void 0;
|
|
89
|
+
const to = new Map([...m[1].matchAll(ATTR)].map((a) => [a[1], a[2]])).get("to");
|
|
90
|
+
if (!to) return void 0;
|
|
91
|
+
const body = m[2].trim();
|
|
92
|
+
const at = body.lastIndexOf(NOTICE_REASON);
|
|
93
|
+
return { to, ...at >= 0 ? { reason: body.slice(at + NOTICE_REASON.length) } : {}, text: body };
|
|
51
94
|
}
|
|
52
95
|
var NOTICE_HEAD = /^\[Cross-session ([\w -]+?) notice\]/;
|
|
53
96
|
var NOTICE_SPLIT = /\n\s*(?=\[Cross-session [\w -]+? notice\])/;
|
|
@@ -393,7 +436,7 @@ function reduceEvent(s, ev) {
|
|
|
393
436
|
case "PreToolUse":
|
|
394
437
|
case "PermissionRequest":
|
|
395
438
|
case "Notification": {
|
|
396
|
-
const notice2 = ev.name === "UserPromptSubmit" && !!ev.text && !!crossSessionNotices(ev.text);
|
|
439
|
+
const notice2 = ev.name === "UserPromptSubmit" && !!ev.text && (!!crossSessionNotices(ev.text) || !!ledgeNotice(ev.text) || !!ledgeTask(ev.text));
|
|
397
440
|
if (ev.name === "UserPromptSubmit" && ev.text && !s.topic && !notice2 && !crossSession(ev.text)) s = withTopic(s, ev.text, "prompt");
|
|
398
441
|
if (s.pending?.source === "rpc" && ev.name === "PreToolUse") return { ...s, lastHookAt: at, lastActivityAt: at };
|
|
399
442
|
const incoming = pendingFor(ev);
|
|
@@ -524,8 +567,14 @@ export {
|
|
|
524
567
|
messageNote,
|
|
525
568
|
stripNote,
|
|
526
569
|
ledgeBody,
|
|
570
|
+
mailId,
|
|
571
|
+
inline,
|
|
527
572
|
formatLedgeMessage,
|
|
528
573
|
ledgeMessages,
|
|
574
|
+
formatLedgeNotice,
|
|
575
|
+
formatLedgeTask,
|
|
576
|
+
ledgeTask,
|
|
577
|
+
ledgeNotice,
|
|
529
578
|
crossSessionNotices,
|
|
530
579
|
Refusal,
|
|
531
580
|
NO_ALWAYS,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
run
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LAUNHDTX.js";
|
|
4
4
|
import {
|
|
5
5
|
ensureLedgeDir,
|
|
6
6
|
paths
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-ELLXOV4Q.js";
|
|
8
8
|
|
|
9
9
|
// src/cli/setup.ts
|
|
10
10
|
import { readFile as readFile2, writeFile, mkdir, rename, realpath, rm, stat, lstat, readlink } from "fs/promises";
|