@paradow/ledge 0.16.1 → 0.18.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.
Files changed (40) hide show
  1. package/README.md +2 -0
  2. package/dist/cli/{attach-5H727FCW.js → attach-I6TTCGNV.js} +12 -7
  3. package/dist/cli/{bridge-LXMOQN6Q.js → bridge-APNFBPNE.js} +2 -2
  4. package/dist/cli/chunk-3EIRKAX7.js +165 -0
  5. package/dist/cli/chunk-3HCID6NW.js +145 -0
  6. package/dist/cli/{chunk-BNKPXSUY.js → chunk-43D6NMYH.js} +1 -1
  7. package/dist/cli/{daemon-DKH63BGV.js → chunk-7IQ6FV2F.js} +6117 -6985
  8. package/dist/cli/{chunk-R5462SQM.js → chunk-7ZAA75HG.js} +2 -1
  9. package/dist/cli/chunk-BVBX222F.js +117 -0
  10. package/dist/cli/{chunk-S4ANPQGA.js → chunk-FQZ3IX6E.js} +2 -2
  11. package/dist/cli/chunk-G6K2NSQS.js +6 -0
  12. package/dist/cli/{chunk-DEXDXDO4.js → chunk-GXFU4XAX.js} +1 -1
  13. package/dist/cli/chunk-HP3G2TYJ.js +1772 -0
  14. package/dist/cli/{chunk-DBSCO3HI.js → chunk-KKNE7BFZ.js} +108 -7
  15. package/dist/cli/{chunk-MQICEVKR.js → chunk-QQZALYNI.js} +38 -12
  16. package/dist/cli/{chunk-H36JD4E3.js → chunk-TRTS24PF.js} +23 -5
  17. package/dist/cli/{chunk-TB47EHML.js → chunk-WCS24ICX.js} +5 -3
  18. package/dist/cli/{chunk-2H5FJH4Q.js → chunk-WJ6YMZTT.js} +2 -2
  19. package/dist/cli/chunk-Y5DJ5INP.js +60 -0
  20. package/dist/cli/{chunk-QJVODYJ5.js → chunk-ZUYOA55W.js} +43 -4
  21. package/dist/cli/daemon-SKC7VKYQ.js +2240 -0
  22. package/dist/cli/handover-UXISKYPS.js +16 -0
  23. package/dist/cli/{hook-LY2YTK5U.js → hook-5UAARBWU.js} +93 -29
  24. package/dist/cli/index.js +45 -12
  25. package/dist/cli/launch-T5FJ42XS.js +34 -0
  26. package/dist/cli/send-NLVR6PL2.js +18 -0
  27. package/dist/cli/{setup-3N2EOJXS.js → setup-EEIXRAMU.js} +4 -4
  28. package/dist/cli/{status-ZQSYXILE.js → status-2OCYHOWZ.js} +3 -3
  29. package/dist/cli/task-AOOWHPMN.js +18 -0
  30. package/dist/cli/team-TVNSVYWW.js +101 -0
  31. package/dist/cli/tui-MQLMUUJB.js +4314 -0
  32. package/dist/cli/{uninstall-W2OMKU3B.js → uninstall-UUN7LIIT.js} +4 -4
  33. package/dist/cli/{version-6BMBIXHN.js → version-AEN2CRT2.js} +1 -1
  34. package/dist/cli/who-52MENZTT.js +11 -0
  35. package/package.json +7 -1
  36. package/dist/cli/chunk-J4ICZEGP.js +0 -6
  37. package/dist/cli/chunk-Q5WWH7FG.js +0 -1423
  38. package/dist/cli/launch-UQEFFPBN.js +0 -27
  39. package/dist/cli/send-EY63XQXI.js +0 -14
  40. 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,24 @@
1
1
  import {
2
2
  attachEnv
3
- } from "./chunk-MQICEVKR.js";
3
+ } from "./chunk-QQZALYNI.js";
4
+ import "./chunk-7IQ6FV2F.js";
5
+ import "./chunk-3EIRKAX7.js";
6
+ import "./chunk-OLLQBK6N.js";
7
+ import "./chunk-KKNE7BFZ.js";
8
+ import "./chunk-3HCID6NW.js";
9
+ import "./chunk-TRTS24PF.js";
4
10
  import {
5
11
  connect
6
- } from "./chunk-DEXDXDO4.js";
7
- import "./chunk-H36JD4E3.js";
12
+ } from "./chunk-GXFU4XAX.js";
8
13
  import {
9
14
  PROTOCOL_PORT,
10
15
  backendOf
11
- } from "./chunk-Q5WWH7FG.js";
16
+ } from "./chunk-HP3G2TYJ.js";
12
17
  import {
13
18
  realTmux
14
- } from "./chunk-2H5FJH4Q.js";
15
- import "./chunk-BNKPXSUY.js";
16
- import "./chunk-R5462SQM.js";
19
+ } from "./chunk-WJ6YMZTT.js";
20
+ import "./chunk-43D6NMYH.js";
21
+ import "./chunk-7ZAA75HG.js";
17
22
 
18
23
  // src/cli/attach.ts
19
24
  import { spawn } from "child_process";
@@ -7,8 +7,8 @@ import {
7
7
  parseClientMessage,
8
8
  parseServerMessage,
9
9
  readDaemonToken
10
- } from "./chunk-Q5WWH7FG.js";
11
- import "./chunk-R5462SQM.js";
10
+ } from "./chunk-HP3G2TYJ.js";
11
+ import "./chunk-7ZAA75HG.js";
12
12
 
13
13
  // src/cli/bridge.ts
14
14
  import WebSocket from "ws";
@@ -0,0 +1,165 @@
1
+ import {
2
+ definitionDirs,
3
+ definitionFiles,
4
+ listRoles,
5
+ readSmall,
6
+ unquote
7
+ } from "./chunk-3HCID6NW.js";
8
+ import {
9
+ SeatsSchema,
10
+ TeamNameSchema
11
+ } from "./chunk-HP3G2TYJ.js";
12
+ import {
13
+ log
14
+ } from "./chunk-43D6NMYH.js";
15
+ import {
16
+ ledgeDir
17
+ } from "./chunk-7ZAA75HG.js";
18
+
19
+ // src/daemon/teams.ts
20
+ import { writeFileSync, mkdirSync, renameSync } from "fs";
21
+ import { join } from "path";
22
+ var scalar = (raw) => unquote(raw.replace(/\s+#.*$/, "").trim());
23
+ function flowMap(raw) {
24
+ const inner = raw.trim().replace(/^\{/, "").replace(/\}$/, "");
25
+ const out = {};
26
+ for (const part of inner.split(",")) {
27
+ if (!part.trim()) continue;
28
+ const i = part.indexOf(":");
29
+ if (i <= 0) throw new Error(`not a key: value pair: ${part.trim().slice(0, 40)}`);
30
+ out[part.slice(0, i).trim()] = scalar(part.slice(i + 1));
31
+ }
32
+ return out;
33
+ }
34
+ function parseTemplateText(text) {
35
+ const src = text.replace(/^/, "").replace(/\r\n/g, "\n");
36
+ if (src.trimStart().startsWith("{")) return JSON.parse(src);
37
+ const out = {};
38
+ let list = null;
39
+ let item = null;
40
+ let itemIndent = -1;
41
+ for (const raw of src.split("\n")) {
42
+ if (!raw.trim() || raw.trimStart().startsWith("#")) continue;
43
+ if (raw.includes(" ")) throw new Error("tabs are not allowed in YAML");
44
+ const indent = raw.length - raw.trimStart().length;
45
+ const line = raw.trim();
46
+ if (indent === 0 && !line.startsWith("- ")) {
47
+ item = null;
48
+ list = null;
49
+ const i2 = line.indexOf(":");
50
+ if (i2 <= 0) throw new Error(`line without a key: ${line.slice(0, 60)}`);
51
+ const key = line.slice(0, i2).trim();
52
+ const rest = line.slice(i2 + 1).trim();
53
+ if (rest === "") {
54
+ list = [];
55
+ out[key] = list;
56
+ } else if (rest.startsWith("[")) out[key] = rest.replace(/^\[|\]$/g, "").split(",").map(scalar).filter(Boolean);
57
+ else out[key] = scalar(rest);
58
+ continue;
59
+ }
60
+ if (!list) throw new Error(`indented line outside a list: ${line.slice(0, 60)}`);
61
+ if (line.startsWith("- ") || line === "-") {
62
+ const rest = line.slice(1).trim();
63
+ if (rest.startsWith("{")) {
64
+ list.push(flowMap(rest));
65
+ item = null;
66
+ continue;
67
+ }
68
+ item = {};
69
+ itemIndent = indent;
70
+ list.push(item);
71
+ if (rest) {
72
+ const i2 = rest.indexOf(":");
73
+ if (i2 <= 0) {
74
+ list[list.length - 1] = scalar(rest);
75
+ item = null;
76
+ continue;
77
+ }
78
+ item[rest.slice(0, i2).trim()] = scalar(rest.slice(i2 + 1));
79
+ }
80
+ continue;
81
+ }
82
+ if (!item || indent <= itemIndent) throw new Error(`line outside a seat: ${line.slice(0, 60)}`);
83
+ const i = line.indexOf(":");
84
+ if (i <= 0) throw new Error(`line without a key: ${line.slice(0, 60)}`);
85
+ item[line.slice(0, i).trim()] = scalar(line.slice(i + 1));
86
+ }
87
+ return out;
88
+ }
89
+ function parseTemplate(id, text, source, path) {
90
+ let raw;
91
+ try {
92
+ raw = parseTemplateText(text);
93
+ } catch (e) {
94
+ return e.message;
95
+ }
96
+ const name = TeamNameSchema.safeParse(typeof raw.name === "string" ? raw.name : id);
97
+ if (!name.success) return "name: a name of 1 to 60 characters";
98
+ const seats = SeatsSchema.safeParse(raw.seats);
99
+ if (!seats.success) {
100
+ const issue = seats.error.issues[0];
101
+ return `seats${issue.path.length ? `.${issue.path.join(".")}` : ""}: ${issue.message}`;
102
+ }
103
+ return { id, name: name.data, seats: seats.data, source, path };
104
+ }
105
+ function listTemplates(cwd) {
106
+ const templates = /* @__PURE__ */ new Map();
107
+ const problems = [];
108
+ for (const { dir, source } of definitionDirs("teams", cwd)) {
109
+ for (const path of definitionFiles(dir, [".yaml", ".yml"])) {
110
+ const id = path.slice(dir.length + 1).replace(/\.ya?ml$/, "");
111
+ if (templates.has(id)) continue;
112
+ let t;
113
+ try {
114
+ t = parseTemplate(id, readSmall(path), source, path);
115
+ } catch (e) {
116
+ t = e.message;
117
+ }
118
+ if (typeof t === "string") {
119
+ log(`team ${path} skipped: ${t}`);
120
+ problems.push({ path, reason: t });
121
+ continue;
122
+ }
123
+ templates.set(id, t);
124
+ }
125
+ }
126
+ return { templates: [...templates.values()].sort((a, b) => a.id.localeCompare(b.id)), problems };
127
+ }
128
+ function resolveSeats(seats, roles, defaults = { tool: "claude" }) {
129
+ const byId = new Map(roles.map((r) => [r.id, r]));
130
+ const unknown = seats.filter((s) => !byId.has(s.role)).map((s) => s.role);
131
+ if (unknown.length) return { unknown };
132
+ return seats.map((s) => {
133
+ const role = byId.get(s.role);
134
+ const tool = s.tool ?? role.tool ?? defaults.tool;
135
+ const own = s.tool === void 0 || s.tool === role.tool;
136
+ const model = s.model ?? (own ? role.model : void 0) ?? (tool === defaults.tool ? defaults.model : void 0);
137
+ const effort = s.effort ?? (own ? role.effort : void 0) ?? (tool === defaults.tool ? defaults.effort : void 0);
138
+ return { role, tool, ...model ? { model } : {}, ...effort ? { effort } : {}, ...s.name ? { name: s.name } : {} };
139
+ });
140
+ }
141
+ function listDefinitions(cwd) {
142
+ const r = listRoles(cwd);
143
+ const t = listTemplates(cwd);
144
+ return { roles: r.roles, templates: t.templates, problems: [...r.problems, ...t.problems] };
145
+ }
146
+ function templateId(name) {
147
+ const id = name.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
148
+ return id || "team";
149
+ }
150
+ var yamlScalar = (v) => /^[\w./@:+-]+$/.test(v) && !/^(true|false|null|yes|no|~)$/i.test(v) ? v : JSON.stringify(v);
151
+ function saveTemplate(name, seats, dir = join(ledgeDir(), "teams")) {
152
+ const path = join(dir, `${templateId(name)}.yaml`);
153
+ const lines = [`name: ${yamlScalar(name)}`, "seats:", ...seats.map((s) => ` - { ${Object.entries(s).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}: ${yamlScalar(String(v))}`).join(", ")} }`), ""];
154
+ mkdirSync(dir, { recursive: true });
155
+ const tmp = `${path}.tmp`;
156
+ writeFileSync(tmp, lines.join("\n"));
157
+ renameSync(tmp, path);
158
+ return path;
159
+ }
160
+
161
+ export {
162
+ resolveSeats,
163
+ listDefinitions,
164
+ saveTemplate
165
+ };
@@ -0,0 +1,145 @@
1
+ import {
2
+ ROLE_LABEL_MAX,
3
+ ROLE_PROMPT_MAX,
4
+ RoleSchema
5
+ } from "./chunk-HP3G2TYJ.js";
6
+ import {
7
+ log
8
+ } from "./chunk-43D6NMYH.js";
9
+ import {
10
+ ledgeDir
11
+ } from "./chunk-7ZAA75HG.js";
12
+
13
+ // src/daemon/roles.ts
14
+ import { existsSync, readdirSync, readFileSync, statSync } from "fs";
15
+ import { dirname, join, resolve } from "path";
16
+ var FILE_MAX = 64 * 1024;
17
+ function repoRoot(cwd) {
18
+ const start = resolve(cwd);
19
+ for (let dir = start; ; dir = dirname(dir)) {
20
+ if (existsSync(join(dir, ".git"))) return dir;
21
+ if (dirname(dir) === dir) return start;
22
+ }
23
+ }
24
+ function definitionDirs(kind, cwd) {
25
+ const home = join(ledgeDir(), kind);
26
+ const repo = cwd ? join(repoRoot(cwd), ".ledge", kind) : void 0;
27
+ return [...repo && resolve(repo) !== resolve(home) ? [{ dir: repo, source: "repo" }] : [], { dir: home, source: "home" }];
28
+ }
29
+ function definitionFiles(dir, ext) {
30
+ let names;
31
+ try {
32
+ names = readdirSync(dir);
33
+ } catch {
34
+ return [];
35
+ }
36
+ return names.filter((n) => ext.some((e) => n.endsWith(e)) && !n.startsWith(".")).sort().map((n) => join(dir, n)).filter((p) => {
37
+ try {
38
+ return statSync(p).isFile();
39
+ } catch {
40
+ return false;
41
+ }
42
+ });
43
+ }
44
+ function readSmall(path) {
45
+ if (statSync(path).size > FILE_MAX) throw new Error(`file over ${FILE_MAX / 1024} KB`);
46
+ return readFileSync(path, "utf8");
47
+ }
48
+ function frontMatter(text) {
49
+ const src = text.replace(/^/, "").replace(/\r\n/g, "\n");
50
+ const m = /^---\n([\s\S]*?)\n---[ \t]*(?:\n|$)/.exec(src);
51
+ if (!m) return { fields: {}, body: src.trim() };
52
+ const fields = {};
53
+ for (const raw of m[1].split("\n")) {
54
+ const line = raw.replace(/\s+#.*$/, "").trim();
55
+ if (!line || line.startsWith("#")) continue;
56
+ const i = line.indexOf(":");
57
+ if (i <= 0) throw new Error(`front matter line without a key: ${line.slice(0, 60)}`);
58
+ fields[line.slice(0, i).trim()] = unquote(line.slice(i + 1).trim());
59
+ }
60
+ return { fields, body: src.slice(m[0].length).trim() };
61
+ }
62
+ var unquote = (v) => v.length >= 2 && (v[0] === '"' || v[0] === "'") && v.at(-1) === v[0] ? v.slice(1, -1) : v;
63
+ var ROLE_KEYS = /* @__PURE__ */ new Set(["label", "tool", "model", "effort", "autonomy"]);
64
+ function parseRole(id, text, source, path) {
65
+ let parsed;
66
+ try {
67
+ parsed = frontMatter(text);
68
+ } catch (e) {
69
+ return e.message;
70
+ }
71
+ const { fields, body } = parsed;
72
+ const unknown = Object.keys(fields).find((k) => !ROLE_KEYS.has(k));
73
+ if (unknown) return `unknown key ${unknown}`;
74
+ if (body.length > ROLE_PROMPT_MAX) return `prompt over ${ROLE_PROMPT_MAX} characters`;
75
+ const label = (fields.label ?? id).slice(0, ROLE_LABEL_MAX);
76
+ const r = RoleSchema.safeParse({
77
+ id,
78
+ label,
79
+ prompt: body,
80
+ source,
81
+ path,
82
+ ...fields.tool ? { tool: fields.tool } : {},
83
+ ...fields.model ? { model: fields.model } : {},
84
+ ...fields.effort ? { effort: fields.effort } : {},
85
+ ...fields.autonomy ? { autonomy: fields.autonomy } : {}
86
+ });
87
+ if (!r.success) {
88
+ const issue = r.error.issues[0];
89
+ return `${issue.path.join(".") || "role"}: ${issue.message}`;
90
+ }
91
+ return r.data;
92
+ }
93
+ function listRoles(cwd) {
94
+ const roles = /* @__PURE__ */ new Map();
95
+ const problems = [];
96
+ for (const { dir, source } of definitionDirs("roles", cwd)) {
97
+ for (const path of definitionFiles(dir, [".md"])) {
98
+ const id = path.slice(dir.length + 1, -".md".length);
99
+ if (roles.has(id)) continue;
100
+ let r;
101
+ try {
102
+ r = parseRole(id, readSmall(path), source, path);
103
+ } catch (e) {
104
+ r = e.message;
105
+ }
106
+ if (typeof r === "string") {
107
+ log(`role ${path} skipped: ${r}`);
108
+ problems.push({ path, reason: r });
109
+ continue;
110
+ }
111
+ roles.set(id, r);
112
+ }
113
+ }
114
+ return { roles: [...roles.values()].sort((a, b) => a.id.localeCompare(b.id)), problems };
115
+ }
116
+ function findRole(id, cwd) {
117
+ return listRoles(cwd).roles.find((r) => r.id === id);
118
+ }
119
+ function autonomyArgs(tool, autonomy) {
120
+ if (!autonomy || autonomy === "ask") return [];
121
+ switch (tool) {
122
+ case "claude":
123
+ return ["--permission-mode", autonomy === "edits" ? "acceptEdits" : "auto"];
124
+ case "codex":
125
+ return autonomy === "edits" ? ["--ask-for-approval", "on-request"] : ["--ask-for-approval", "never", "--sandbox", "workspace-write"];
126
+ case "cursor":
127
+ return autonomy === "auto" ? ["--force"] : [];
128
+ case "omp":
129
+ return [];
130
+ }
131
+ }
132
+ function ompApprovalFor(autonomy) {
133
+ return autonomy === "auto" ? "yolo" : autonomy ? "always-ask" : void 0;
134
+ }
135
+
136
+ export {
137
+ definitionDirs,
138
+ definitionFiles,
139
+ readSmall,
140
+ unquote,
141
+ listRoles,
142
+ findRole,
143
+ autonomyArgs,
144
+ ompApprovalFor
145
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  paths
3
- } from "./chunk-R5462SQM.js";
3
+ } from "./chunk-7ZAA75HG.js";
4
4
 
5
5
  // src/log.ts
6
6
  import { appendFileSync } from "fs";