agent-dag 1.15.0 → 1.15.4
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/LICENSE +21 -21
- package/README.md +52 -52
- package/bin/agent-dag.js +199 -199
- package/dist/web/assets/index-BPyjn8Kd.js +62 -0
- package/dist/web/index.html +13 -13
- package/hook/hook.js +96 -96
- package/package.json +68 -66
- package/src/server/index.mjs +643 -627
- package/src/server/installer.mjs +137 -137
- package/dist/web/assets/index-DZeh3MHD.js +0 -62
package/src/server/installer.mjs
CHANGED
|
@@ -1,137 +1,137 @@
|
|
|
1
|
-
// Idempotent hook installer for ~/.claude/settings.json.
|
|
2
|
-
// Adds a single command-hook entry per CC hook event pointing at our forwarder.
|
|
3
|
-
// Re-runs are safe (entries are tagged with __agent-dag and de-duped).
|
|
4
|
-
import { readFile, writeFile, mkdir, copyFile, unlink } from "node:fs/promises";
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
6
|
-
import { homedir, platform } from "node:os";
|
|
7
|
-
import { join, resolve, dirname } from "node:path";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
9
|
-
|
|
10
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
const PKG_ROOT = resolve(__dirname, "..", "..");
|
|
12
|
-
|
|
13
|
-
const HOME = homedir();
|
|
14
|
-
const CLAUDE_DIR = join(HOME, ".claude");
|
|
15
|
-
const SETTINGS = join(CLAUDE_DIR, "settings.json");
|
|
16
|
-
const AGENT_DAG_DIR = join(CLAUDE_DIR, "agent-dag");
|
|
17
|
-
|
|
18
|
-
export const HOOK_EVENTS = [
|
|
19
|
-
"SessionStart",
|
|
20
|
-
"UserPromptSubmit",
|
|
21
|
-
"PreToolUse",
|
|
22
|
-
"PostToolUse",
|
|
23
|
-
"PostToolUseFailure",
|
|
24
|
-
"SubagentStart",
|
|
25
|
-
"SubagentStop",
|
|
26
|
-
"Stop",
|
|
27
|
-
"SessionEnd",
|
|
28
|
-
"Notification",
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
const MARK_KEY = "__agent-dag";
|
|
32
|
-
// Legacy marks from earlier names — purged on every install/uninstall so
|
|
33
|
-
// duplicate forwarders don't pile up when the project gets renamed.
|
|
34
|
-
const LEGACY_MARKS = ["__ccgraph", "__agent-flow"];
|
|
35
|
-
// ~/.claude/<name>/hook.js paths used by old releases. Any unmarked entry
|
|
36
|
-
// pointing into one of these is recognised as ours and removed.
|
|
37
|
-
const LEGACY_DIRS = ["ccgraph", "agent-flow", "agent-dag"];
|
|
38
|
-
|
|
39
|
-
function hookCommand(installedHookPath) {
|
|
40
|
-
// process.execPath = absolute path to current node (works on Win + *nix).
|
|
41
|
-
const node = process.execPath;
|
|
42
|
-
return `"${node}" "${installedHookPath}"`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function isOurEntry(g) {
|
|
46
|
-
if (!g || typeof g !== "object") return false;
|
|
47
|
-
if (g[MARK_KEY] === true) return true;
|
|
48
|
-
for (const k of LEGACY_MARKS) if (g[k] === true) return true;
|
|
49
|
-
// Heuristic: command points at ~/.claude/<legacy-dir>/hook.js.
|
|
50
|
-
const cmds = Array.isArray(g.hooks) ? g.hooks : [];
|
|
51
|
-
for (const h of cmds) {
|
|
52
|
-
const c = typeof h?.command === "string" ? h.command : "";
|
|
53
|
-
for (const dir of LEGACY_DIRS) {
|
|
54
|
-
if (c.includes(`.claude/${dir}/hook.js`) || c.includes(`.claude\\${dir}\\hook.js`)) return true;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function ensureDir(p) {
|
|
61
|
-
if (!existsSync(p)) await mkdir(p, { recursive: true });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function readJsonSafe(p) {
|
|
65
|
-
try { return JSON.parse(await readFile(p, "utf8")); } catch { return null; }
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function installHookScript() {
|
|
69
|
-
await ensureDir(AGENT_DAG_DIR);
|
|
70
|
-
const src = join(PKG_ROOT, "hook", "hook.js");
|
|
71
|
-
const dst = join(AGENT_DAG_DIR, "hook.js");
|
|
72
|
-
await copyFile(src, dst);
|
|
73
|
-
return dst;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function buildHookEntry(command) {
|
|
77
|
-
return {
|
|
78
|
-
[MARK_KEY]: true,
|
|
79
|
-
hooks: [{ type: "command", command, timeout: 2 }],
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function dedupeOurEntries(group) {
|
|
84
|
-
if (!Array.isArray(group)) return [];
|
|
85
|
-
return group.filter(g => !isOurEntry(g));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export async function installHooks() {
|
|
89
|
-
const hookPath = await installHookScript();
|
|
90
|
-
const command = hookCommand(hookPath);
|
|
91
|
-
await ensureDir(CLAUDE_DIR);
|
|
92
|
-
|
|
93
|
-
const current = (await readJsonSafe(SETTINGS)) ?? {};
|
|
94
|
-
current.hooks = current.hooks ?? {};
|
|
95
|
-
|
|
96
|
-
for (const evt of HOOK_EVENTS) {
|
|
97
|
-
const cleaned = dedupeOurEntries(current.hooks[evt]);
|
|
98
|
-
cleaned.push(buildHookEntry(command));
|
|
99
|
-
current.hooks[evt] = cleaned;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
await writeFile(SETTINGS, JSON.stringify(current, null, 2) + "\n", "utf8");
|
|
103
|
-
return { settingsPath: SETTINGS, hookPath, events: HOOK_EVENTS };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export async function uninstallHooks() {
|
|
107
|
-
const current = await readJsonSafe(SETTINGS);
|
|
108
|
-
if (!current?.hooks) return { changed: false };
|
|
109
|
-
let changed = false;
|
|
110
|
-
for (const evt of Object.keys(current.hooks)) {
|
|
111
|
-
const cleaned = dedupeOurEntries(current.hooks[evt]);
|
|
112
|
-
if (cleaned.length !== (current.hooks[evt]?.length ?? 0)) changed = true;
|
|
113
|
-
if (cleaned.length === 0) delete current.hooks[evt];
|
|
114
|
-
else current.hooks[evt] = cleaned;
|
|
115
|
-
}
|
|
116
|
-
if (changed) await writeFile(SETTINGS, JSON.stringify(current, null, 2) + "\n", "utf8");
|
|
117
|
-
return { changed };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export async function writeDiscovery({ port, workspace }) {
|
|
121
|
-
await ensureDir(AGENT_DAG_DIR);
|
|
122
|
-
const file = join(AGENT_DAG_DIR, `${process.pid}.json`);
|
|
123
|
-
const data = {
|
|
124
|
-
pid: process.pid,
|
|
125
|
-
port,
|
|
126
|
-
workspace: workspace ?? "",
|
|
127
|
-
startedAt: new Date().toISOString(),
|
|
128
|
-
};
|
|
129
|
-
await writeFile(file, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
130
|
-
return file;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export async function removeDiscovery(file) {
|
|
134
|
-
try { await unlink(file); } catch {}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export { AGENT_DAG_DIR, SETTINGS };
|
|
1
|
+
// Idempotent hook installer for ~/.claude/settings.json.
|
|
2
|
+
// Adds a single command-hook entry per CC hook event pointing at our forwarder.
|
|
3
|
+
// Re-runs are safe (entries are tagged with __agent-dag and de-duped).
|
|
4
|
+
import { readFile, writeFile, mkdir, copyFile, unlink } from "node:fs/promises";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { homedir, platform } from "node:os";
|
|
7
|
+
import { join, resolve, dirname } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const PKG_ROOT = resolve(__dirname, "..", "..");
|
|
12
|
+
|
|
13
|
+
const HOME = homedir();
|
|
14
|
+
const CLAUDE_DIR = join(HOME, ".claude");
|
|
15
|
+
const SETTINGS = join(CLAUDE_DIR, "settings.json");
|
|
16
|
+
const AGENT_DAG_DIR = join(CLAUDE_DIR, "agent-dag");
|
|
17
|
+
|
|
18
|
+
export const HOOK_EVENTS = [
|
|
19
|
+
"SessionStart",
|
|
20
|
+
"UserPromptSubmit",
|
|
21
|
+
"PreToolUse",
|
|
22
|
+
"PostToolUse",
|
|
23
|
+
"PostToolUseFailure",
|
|
24
|
+
"SubagentStart",
|
|
25
|
+
"SubagentStop",
|
|
26
|
+
"Stop",
|
|
27
|
+
"SessionEnd",
|
|
28
|
+
"Notification",
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const MARK_KEY = "__agent-dag";
|
|
32
|
+
// Legacy marks from earlier names — purged on every install/uninstall so
|
|
33
|
+
// duplicate forwarders don't pile up when the project gets renamed.
|
|
34
|
+
const LEGACY_MARKS = ["__ccgraph", "__agent-flow"];
|
|
35
|
+
// ~/.claude/<name>/hook.js paths used by old releases. Any unmarked entry
|
|
36
|
+
// pointing into one of these is recognised as ours and removed.
|
|
37
|
+
const LEGACY_DIRS = ["ccgraph", "agent-flow", "agent-dag"];
|
|
38
|
+
|
|
39
|
+
function hookCommand(installedHookPath) {
|
|
40
|
+
// process.execPath = absolute path to current node (works on Win + *nix).
|
|
41
|
+
const node = process.execPath;
|
|
42
|
+
return `"${node}" "${installedHookPath}"`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isOurEntry(g) {
|
|
46
|
+
if (!g || typeof g !== "object") return false;
|
|
47
|
+
if (g[MARK_KEY] === true) return true;
|
|
48
|
+
for (const k of LEGACY_MARKS) if (g[k] === true) return true;
|
|
49
|
+
// Heuristic: command points at ~/.claude/<legacy-dir>/hook.js.
|
|
50
|
+
const cmds = Array.isArray(g.hooks) ? g.hooks : [];
|
|
51
|
+
for (const h of cmds) {
|
|
52
|
+
const c = typeof h?.command === "string" ? h.command : "";
|
|
53
|
+
for (const dir of LEGACY_DIRS) {
|
|
54
|
+
if (c.includes(`.claude/${dir}/hook.js`) || c.includes(`.claude\\${dir}\\hook.js`)) return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function ensureDir(p) {
|
|
61
|
+
if (!existsSync(p)) await mkdir(p, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function readJsonSafe(p) {
|
|
65
|
+
try { return JSON.parse(await readFile(p, "utf8")); } catch { return null; }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function installHookScript() {
|
|
69
|
+
await ensureDir(AGENT_DAG_DIR);
|
|
70
|
+
const src = join(PKG_ROOT, "hook", "hook.js");
|
|
71
|
+
const dst = join(AGENT_DAG_DIR, "hook.js");
|
|
72
|
+
await copyFile(src, dst);
|
|
73
|
+
return dst;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildHookEntry(command) {
|
|
77
|
+
return {
|
|
78
|
+
[MARK_KEY]: true,
|
|
79
|
+
hooks: [{ type: "command", command, timeout: 2 }],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function dedupeOurEntries(group) {
|
|
84
|
+
if (!Array.isArray(group)) return [];
|
|
85
|
+
return group.filter(g => !isOurEntry(g));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function installHooks() {
|
|
89
|
+
const hookPath = await installHookScript();
|
|
90
|
+
const command = hookCommand(hookPath);
|
|
91
|
+
await ensureDir(CLAUDE_DIR);
|
|
92
|
+
|
|
93
|
+
const current = (await readJsonSafe(SETTINGS)) ?? {};
|
|
94
|
+
current.hooks = current.hooks ?? {};
|
|
95
|
+
|
|
96
|
+
for (const evt of HOOK_EVENTS) {
|
|
97
|
+
const cleaned = dedupeOurEntries(current.hooks[evt]);
|
|
98
|
+
cleaned.push(buildHookEntry(command));
|
|
99
|
+
current.hooks[evt] = cleaned;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await writeFile(SETTINGS, JSON.stringify(current, null, 2) + "\n", "utf8");
|
|
103
|
+
return { settingsPath: SETTINGS, hookPath, events: HOOK_EVENTS };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function uninstallHooks() {
|
|
107
|
+
const current = await readJsonSafe(SETTINGS);
|
|
108
|
+
if (!current?.hooks) return { changed: false };
|
|
109
|
+
let changed = false;
|
|
110
|
+
for (const evt of Object.keys(current.hooks)) {
|
|
111
|
+
const cleaned = dedupeOurEntries(current.hooks[evt]);
|
|
112
|
+
if (cleaned.length !== (current.hooks[evt]?.length ?? 0)) changed = true;
|
|
113
|
+
if (cleaned.length === 0) delete current.hooks[evt];
|
|
114
|
+
else current.hooks[evt] = cleaned;
|
|
115
|
+
}
|
|
116
|
+
if (changed) await writeFile(SETTINGS, JSON.stringify(current, null, 2) + "\n", "utf8");
|
|
117
|
+
return { changed };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function writeDiscovery({ port, workspace }) {
|
|
121
|
+
await ensureDir(AGENT_DAG_DIR);
|
|
122
|
+
const file = join(AGENT_DAG_DIR, `${process.pid}.json`);
|
|
123
|
+
const data = {
|
|
124
|
+
pid: process.pid,
|
|
125
|
+
port,
|
|
126
|
+
workspace: workspace ?? "",
|
|
127
|
+
startedAt: new Date().toISOString(),
|
|
128
|
+
};
|
|
129
|
+
await writeFile(file, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
130
|
+
return file;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function removeDiscovery(file) {
|
|
134
|
+
try { await unlink(file); } catch {}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export { AGENT_DAG_DIR, SETTINGS };
|