@paradow/ledge 0.17.0 → 0.19.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 +7 -1
- package/assets/ledge.ico +0 -0
- package/dist/cli/{attach-EYIX7FZN.js → attach-3BKQSEWC.js} +12 -7
- package/dist/cli/{bridge-6UHCD76N.js → bridge-APNFBPNE.js} +2 -2
- package/dist/cli/chunk-3EIRKAX7.js +165 -0
- package/dist/cli/chunk-3HCID6NW.js +145 -0
- package/dist/cli/{chunk-JQZHXVIZ.js → chunk-43D6NMYH.js} +1 -1
- package/dist/cli/chunk-4RD7OJ2J.js +6 -0
- package/dist/cli/{chunk-ELLXOV4Q.js → chunk-7ZAA75HG.js} +1 -1
- package/dist/cli/{chunk-5ZCHFGCV.js → chunk-BVBX222F.js} +9 -7
- package/dist/cli/{chunk-OVJL2QTN.js → chunk-FQZ3IX6E.js} +2 -2
- package/dist/cli/{chunk-VVK4I6B5.js → chunk-GXFU4XAX.js} +1 -1
- package/dist/cli/chunk-HP3G2TYJ.js +1772 -0
- package/dist/cli/{chunk-NC5YAEJB.js → chunk-KKNE7BFZ.js} +55 -3
- package/dist/cli/{chunk-36KJUV62.js → chunk-LOOOHYZR.js} +38 -12
- package/dist/cli/{chunk-VE4LGNXX.js → chunk-TRTS24PF.js} +23 -5
- package/dist/cli/{chunk-Q7YNLP66.js → chunk-WCS24ICX.js} +5 -3
- package/dist/cli/{chunk-LAUNHDTX.js → chunk-WJ6YMZTT.js} +2 -2
- package/dist/cli/{daemon-QEJLAIBT.js → chunk-WP77O5LC.js} +6045 -7228
- package/dist/cli/chunk-Y5DJ5INP.js +60 -0
- package/dist/cli/{chunk-AQ25HFBP.js → chunk-ZUYOA55W.js} +5 -4
- package/dist/cli/daemon-66EUIZ3J.js +2240 -0
- package/dist/cli/handover-UXISKYPS.js +16 -0
- package/dist/cli/{hook-AE5HC6AS.js → hook-5UAARBWU.js} +86 -25
- package/dist/cli/index.js +30 -16
- package/dist/cli/launch-YQYCDZ7N.js +34 -0
- package/dist/cli/{send-2OZ3YZRV.js → send-NLVR6PL2.js} +5 -5
- package/dist/cli/{setup-2VM4GCDJ.js → setup-EEIXRAMU.js} +4 -4
- package/dist/cli/{status-3VJAO6UL.js → status-2OCYHOWZ.js} +3 -3
- package/dist/cli/{task-PMKNYP6M.js → task-AOOWHPMN.js} +5 -5
- package/dist/cli/team-TVNSVYWW.js +101 -0
- package/dist/cli/{tui-DWA2EDC2.js → tui-6JY7IS4D.js} +985 -125
- package/dist/cli/{uninstall-AKUWE33D.js → uninstall-UUN7LIIT.js} +4 -4
- package/dist/cli/{version-WOGCIZY3.js → version-XSEI3V2C.js} +1 -1
- package/dist/cli/who-52MENZTT.js +11 -0
- package/package.json +2 -2
- package/dist/cli/chunk-QD27W6W5.js +0 -1474
- package/dist/cli/chunk-V2H7UD2N.js +0 -6
- package/dist/cli/launch-B4G5OFBV.js +0 -27
- package/dist/cli/who-37TNDYVA.js +0 -11
package/README.md
CHANGED
|
@@ -42,11 +42,17 @@ 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
|
|
45
|
+
ledge tui # follow and answer every session (--notify bell|osc9|off); see below for Windows Terminal
|
|
46
46
|
ledge daemon # run the daemon in the foreground (normally run by the systemd service)
|
|
47
47
|
ledge uninstall # undo ledge setup: hooks, service and ~/.local/bin/ledge (--purge: ~/.ledge too)
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
From a WSL tab of Windows Terminal, `ledge tui` opens in a tab of its own with the Ledge mascot as its icon. A tab's
|
|
51
|
+
icon comes only from its profile, so Ledge installs a `Ledge` profile as a fragment under
|
|
52
|
+
`%LOCALAPPDATA%\Microsoft\Windows Terminal\Fragments\Ledge` (no admin right needed). That profile runs
|
|
53
|
+
`~/.ledge/tui.sh`, which each `ledge tui` rewrites with its node and CLI. `ledge tui --here`, tmux, SSH and any other
|
|
54
|
+
terminal keep the TUI where it was started.
|
|
55
|
+
|
|
50
56
|
When a Claude it launched exits, Ledge appends a `custom-title` « name - topic » to its transcript, so `claude --resume` lists the conversation that way, unless the user named it (`claude -n`, `/rename`).
|
|
51
57
|
|
|
52
58
|
`ledge setup` installs `ledge-daemon.service` as a systemd user unit, starts it and enables linger, so the daemon runs from WSL boot and restarts after a crash. Rerun it after a node upgrade or a repository move: the unit is rewritten and the service restarted. Useful commands:
|
package/assets/ledge.ico
ADDED
|
Binary file
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
attachEnv
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LOOOHYZR.js";
|
|
4
|
+
import "./chunk-WP77O5LC.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-
|
|
7
|
-
import "./chunk-VE4LGNXX.js";
|
|
12
|
+
} from "./chunk-GXFU4XAX.js";
|
|
8
13
|
import {
|
|
9
14
|
PROTOCOL_PORT,
|
|
10
15
|
backendOf
|
|
11
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-HP3G2TYJ.js";
|
|
12
17
|
import {
|
|
13
18
|
realTmux
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
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";
|
|
@@ -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
|
+
};
|
|
@@ -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"), 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") };
|
|
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"), teams: join(dir, "teams-live.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();
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
connect
|
|
3
|
-
} from "./chunk-VVK4I6B5.js";
|
|
4
1
|
import {
|
|
5
2
|
CODEX_HOOK_VAR
|
|
6
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-TRTS24PF.js";
|
|
4
|
+
import {
|
|
5
|
+
connect
|
|
6
|
+
} from "./chunk-GXFU4XAX.js";
|
|
7
7
|
import {
|
|
8
|
+
LEDGE_GIVER,
|
|
8
9
|
TASK_TEXT_MAX,
|
|
9
10
|
TASK_TITLE_MAX
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-HP3G2TYJ.js";
|
|
11
12
|
|
|
12
13
|
// src/cli/task.ts
|
|
13
14
|
import { randomUUID } from "crypto";
|
|
14
|
-
var TASK_USAGE = `ledge task give <nom> "<titre>" ["<d\xE9tail>"] confier un travail \xE0 une session
|
|
15
|
+
var TASK_USAGE = `ledge task give <nom|@r\xF4le> "<titre>" ["<d\xE9tail>"] confier un travail \xE0 une session (@r\xF4le : dans ton \xE9quipe)
|
|
15
16
|
ledge task list [--all] tes t\xE2ches (tenues ou confi\xE9es), ouvertes ; --all : closes aussi
|
|
16
17
|
ledge task show <id> une t\xE2che, son d\xE9tail et son historique
|
|
17
18
|
ledge task take <id> la prendre
|
|
18
19
|
ledge task done <id> ["<r\xE9sultat>"] la terminer (la session qui l'a confi\xE9e est pr\xE9venue)
|
|
19
20
|
ledge task refuse <id> "<raison>" la refuser, ou l'annuler si tu l'as confi\xE9e
|
|
20
|
-
ledge task pass <id> <nom> ["<note>"]
|
|
21
|
+
ledge task pass <id> <nom|@r\xF4le> ["<note>"] la passer \xE0 une autre session`;
|
|
21
22
|
var TASK_MS = 4e3;
|
|
22
23
|
var ID = /^t[0-9a-f]{6}$/;
|
|
23
24
|
function taskArgs(args) {
|
|
@@ -58,6 +59,7 @@ function formatTask(t) {
|
|
|
58
59
|
async function taskOnClient(client, args, by, ms) {
|
|
59
60
|
const op = taskArgs(args);
|
|
60
61
|
if (!op) return { line: TASK_USAGE, code: 1 };
|
|
62
|
+
if (by === LEDGE_GIVER) return { line: "refus\xE9 : seul Ledge confie une t\xE2che en son nom", code: 1 };
|
|
61
63
|
try {
|
|
62
64
|
client.send({ type: "task.cmd", rid: randomUUID(), by, ...op });
|
|
63
65
|
const r = await client.next("task.result", ms);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
run
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-WJ6YMZTT.js";
|
|
4
4
|
import {
|
|
5
5
|
ensureLedgeDir,
|
|
6
6
|
paths
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-7ZAA75HG.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";
|