claude-synapse 1.0.0 → 1.0.2
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/bin/synapse.mjs +116 -40
- package/hooks/claude-hooks.json +30 -30
- package/package.json +9 -2
package/bin/synapse.mjs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { execSync, spawn } from 'node:child_process';
|
|
4
|
-
import { existsSync } from 'node:fs';
|
|
4
|
+
import { existsSync, writeFileSync, readFileSync, unlinkSync } from 'node:fs';
|
|
5
5
|
import { resolve, dirname } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
7
8
|
|
|
8
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const ROOT = resolve(__dirname, '..');
|
|
10
11
|
const PORT = process.env.PORT || 4800;
|
|
12
|
+
const PID_FILE = resolve(tmpdir(), `claude-synapse-${PORT}.pid`);
|
|
11
13
|
|
|
12
14
|
// Detect whether we're running from the npm-published bundle or from source
|
|
13
15
|
const bundledCollector = resolve(ROOT, 'dist', 'collector.mjs');
|
|
@@ -24,6 +26,16 @@ if (command === 'init') {
|
|
|
24
26
|
process.exit(0);
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
if (command === 'stop') {
|
|
30
|
+
stop();
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (command === 'status') {
|
|
35
|
+
status();
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
function isDevBuilt() {
|
|
28
40
|
return (
|
|
29
41
|
existsSync(resolve(ROOT, 'packages/protocol/dist/index.js')) &&
|
|
@@ -38,26 +50,68 @@ function build() {
|
|
|
38
50
|
console.log('[synapse] Build complete.');
|
|
39
51
|
}
|
|
40
52
|
|
|
41
|
-
function
|
|
42
|
-
let entryPoint;
|
|
43
|
-
let dashboardDir;
|
|
44
|
-
|
|
53
|
+
function getEntryAndDashboard() {
|
|
45
54
|
if (isBundled) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
return { entryPoint: bundledCollector, dashboardDir: bundledDashboard };
|
|
56
|
+
}
|
|
57
|
+
if (!isDevBuilt()) build();
|
|
58
|
+
return {
|
|
59
|
+
entryPoint: resolve(ROOT, 'apps/collector/dist/index.js'),
|
|
60
|
+
dashboardDir: resolve(ROOT, 'apps/dashboard/dist'),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isRunning() {
|
|
65
|
+
if (!existsSync(PID_FILE)) return false;
|
|
66
|
+
const pid = parseInt(readFileSync(PID_FILE, 'utf-8').trim(), 10);
|
|
67
|
+
try {
|
|
68
|
+
process.kill(pid, 0); // signal 0 = check if alive
|
|
69
|
+
return pid;
|
|
70
|
+
} catch {
|
|
71
|
+
// Stale pid file
|
|
72
|
+
unlinkSync(PID_FILE);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function stop() {
|
|
78
|
+
const pid = isRunning();
|
|
79
|
+
if (!pid) {
|
|
80
|
+
console.log(`[synapse] Not running on port ${PORT}.`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
process.kill(pid, 'SIGTERM');
|
|
85
|
+
unlinkSync(PID_FILE);
|
|
86
|
+
console.log(`[synapse] Stopped (pid ${pid}).`);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error(`[synapse] Failed to stop pid ${pid}:`, err.message);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function status() {
|
|
93
|
+
const pid = isRunning();
|
|
94
|
+
if (pid) {
|
|
95
|
+
console.log(`[synapse] Running on http://localhost:${PORT} (pid ${pid})`);
|
|
49
96
|
} else {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
97
|
+
console.log(`[synapse] Not running.`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function start() {
|
|
102
|
+
const runningPid = isRunning();
|
|
103
|
+
if (runningPid) {
|
|
104
|
+
console.log(`[synapse] Already running on http://localhost:${PORT} (pid ${runningPid})`);
|
|
105
|
+
process.exit(0);
|
|
54
106
|
}
|
|
55
107
|
|
|
56
|
-
|
|
108
|
+
const { entryPoint, dashboardDir } = getEntryAndDashboard();
|
|
109
|
+
const isDaemon = args.includes('--daemon') || args.includes('-d');
|
|
57
110
|
|
|
58
111
|
const collector = spawn('node', [entryPoint], {
|
|
59
112
|
cwd: ROOT,
|
|
60
|
-
stdio: 'inherit',
|
|
113
|
+
stdio: isDaemon ? 'ignore' : 'inherit',
|
|
114
|
+
detached: isDaemon,
|
|
61
115
|
env: {
|
|
62
116
|
...process.env,
|
|
63
117
|
PORT: String(PORT),
|
|
@@ -65,32 +119,54 @@ function start() {
|
|
|
65
119
|
},
|
|
66
120
|
});
|
|
67
121
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
122
|
+
if (isDaemon) {
|
|
123
|
+
// Write PID file and detach
|
|
124
|
+
writeFileSync(PID_FILE, String(collector.pid));
|
|
125
|
+
collector.unref();
|
|
126
|
+
console.log(`[synapse] Started in background on http://localhost:${PORT} (pid ${collector.pid})`);
|
|
127
|
+
console.log(`[synapse] Stop with: npx claude-synapse stop`);
|
|
128
|
+
|
|
129
|
+
// Open browser
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
const url = `http://localhost:${PORT}`;
|
|
132
|
+
try {
|
|
133
|
+
if (process.platform === 'darwin') execSync(`open ${url}`);
|
|
134
|
+
else if (process.platform === 'linux') execSync(`xdg-open ${url}`);
|
|
135
|
+
else if (process.platform === 'win32') execSync(`start ${url}`);
|
|
136
|
+
} catch {}
|
|
137
|
+
process.exit(0);
|
|
138
|
+
}, 1500);
|
|
139
|
+
} else {
|
|
140
|
+
// Foreground mode
|
|
141
|
+
console.log(`[synapse] Starting on http://localhost:${PORT}`);
|
|
142
|
+
writeFileSync(PID_FILE, String(collector.pid));
|
|
143
|
+
|
|
144
|
+
collector.on('close', (code) => {
|
|
145
|
+
try { unlinkSync(PID_FILE); } catch {}
|
|
146
|
+
process.exit(code ?? 0);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
setTimeout(() => {
|
|
150
|
+
const url = `http://localhost:${PORT}`;
|
|
151
|
+
try {
|
|
152
|
+
if (process.platform === 'darwin') execSync(`open ${url}`);
|
|
153
|
+
else if (process.platform === 'linux') execSync(`xdg-open ${url}`);
|
|
154
|
+
else if (process.platform === 'win32') execSync(`start ${url}`);
|
|
155
|
+
} catch {}
|
|
156
|
+
}, 1500);
|
|
157
|
+
|
|
158
|
+
process.on('SIGINT', () => {
|
|
159
|
+
collector.kill('SIGINT');
|
|
160
|
+
try { unlinkSync(PID_FILE); } catch {}
|
|
161
|
+
process.exit(0);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
process.on('SIGTERM', () => {
|
|
165
|
+
collector.kill('SIGTERM');
|
|
166
|
+
try { unlinkSync(PID_FILE); } catch {}
|
|
167
|
+
process.exit(0);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
94
170
|
}
|
|
95
171
|
|
|
96
172
|
start();
|
package/hooks/claude-hooks.json
CHANGED
|
@@ -1,111 +1,111 @@
|
|
|
1
1
|
{
|
|
2
2
|
"hooks": {
|
|
3
|
-
"
|
|
3
|
+
"SessionStart": [
|
|
4
4
|
{
|
|
5
|
-
"matcher": "",
|
|
6
5
|
"hooks": [
|
|
7
6
|
{
|
|
8
7
|
"type": "command",
|
|
9
|
-
"command": "curl -s -X POST http://localhost:4800/api/hooks/
|
|
8
|
+
"command": "curl -s -X POST http://localhost:4800/api/hooks/SessionStart -H 'Content-Type: application/json' --data-binary @- > /dev/null 2>&1 || true"
|
|
10
9
|
}
|
|
11
10
|
]
|
|
12
11
|
}
|
|
13
12
|
],
|
|
14
|
-
"
|
|
13
|
+
"UserPromptSubmit": [
|
|
15
14
|
{
|
|
16
|
-
"matcher": "",
|
|
17
15
|
"hooks": [
|
|
18
16
|
{
|
|
19
17
|
"type": "command",
|
|
20
|
-
"command": "curl -s -X POST http://localhost:4800/api/hooks/
|
|
18
|
+
"command": "curl -s -X POST http://localhost:4800/api/hooks/UserPromptSubmit -H 'Content-Type: application/json' --data-binary @- > /dev/null 2>&1 || true"
|
|
21
19
|
}
|
|
22
20
|
]
|
|
23
21
|
}
|
|
24
22
|
],
|
|
25
|
-
"
|
|
23
|
+
"SubagentStart": [
|
|
26
24
|
{
|
|
27
|
-
"matcher": "",
|
|
28
25
|
"hooks": [
|
|
29
26
|
{
|
|
30
27
|
"type": "command",
|
|
31
|
-
"command": "curl -s -X POST http://localhost:4800/api/hooks/
|
|
28
|
+
"command": "curl -s -X POST http://localhost:4800/api/hooks/SubagentStart -H 'Content-Type: application/json' --data-binary @- > /dev/null 2>&1 || true"
|
|
32
29
|
}
|
|
33
30
|
]
|
|
34
31
|
}
|
|
35
32
|
],
|
|
36
|
-
"
|
|
33
|
+
"SubagentStop": [
|
|
37
34
|
{
|
|
38
|
-
"matcher": "",
|
|
39
35
|
"hooks": [
|
|
40
36
|
{
|
|
41
37
|
"type": "command",
|
|
42
|
-
"command": "curl -s -X POST http://localhost:4800/api/hooks/
|
|
38
|
+
"command": "curl -s -X POST http://localhost:4800/api/hooks/SubagentStop -H 'Content-Type: application/json' --data-binary @- > /dev/null 2>&1 || true"
|
|
43
39
|
}
|
|
44
40
|
]
|
|
45
41
|
}
|
|
46
42
|
],
|
|
47
|
-
"
|
|
43
|
+
"PreToolUse": [
|
|
48
44
|
{
|
|
49
|
-
"matcher": "",
|
|
50
45
|
"hooks": [
|
|
51
46
|
{
|
|
52
47
|
"type": "command",
|
|
53
|
-
"command": "curl -s -X POST http://localhost:4800/api/hooks/
|
|
48
|
+
"command": "curl -s -X POST http://localhost:4800/api/hooks/PreToolUse -H 'Content-Type: application/json' --data-binary @- > /dev/null 2>&1 || true"
|
|
54
49
|
}
|
|
55
50
|
]
|
|
56
51
|
}
|
|
57
52
|
],
|
|
58
|
-
"
|
|
53
|
+
"PostToolUse": [
|
|
59
54
|
{
|
|
60
|
-
"matcher": "",
|
|
61
55
|
"hooks": [
|
|
62
56
|
{
|
|
63
57
|
"type": "command",
|
|
64
|
-
"command": "curl -s -X POST http://localhost:4800/api/hooks/
|
|
58
|
+
"command": "curl -s -X POST http://localhost:4800/api/hooks/PostToolUse -H 'Content-Type: application/json' --data-binary @- > /dev/null 2>&1 || true"
|
|
65
59
|
}
|
|
66
60
|
]
|
|
67
61
|
}
|
|
68
62
|
],
|
|
69
|
-
"
|
|
63
|
+
"PostToolUseFailure": [
|
|
70
64
|
{
|
|
71
|
-
"matcher": "",
|
|
72
65
|
"hooks": [
|
|
73
66
|
{
|
|
74
67
|
"type": "command",
|
|
75
|
-
"command": "curl -s -X POST http://localhost:4800/api/hooks/
|
|
68
|
+
"command": "curl -s -X POST http://localhost:4800/api/hooks/PostToolUseFailure -H 'Content-Type: application/json' --data-binary @- > /dev/null 2>&1 || true"
|
|
76
69
|
}
|
|
77
70
|
]
|
|
78
71
|
}
|
|
79
72
|
],
|
|
80
|
-
"
|
|
73
|
+
"PreCompact": [
|
|
81
74
|
{
|
|
82
|
-
"matcher": "",
|
|
83
75
|
"hooks": [
|
|
84
76
|
{
|
|
85
77
|
"type": "command",
|
|
86
|
-
"command": "curl -s -X POST http://localhost:4800/api/hooks/
|
|
78
|
+
"command": "curl -s -X POST http://localhost:4800/api/hooks/PreCompact -H 'Content-Type: application/json' --data-binary @- > /dev/null 2>&1 || true"
|
|
87
79
|
}
|
|
88
80
|
]
|
|
89
81
|
}
|
|
90
82
|
],
|
|
91
|
-
"
|
|
83
|
+
"Notification": [
|
|
92
84
|
{
|
|
93
|
-
"matcher": "",
|
|
94
85
|
"hooks": [
|
|
95
86
|
{
|
|
96
87
|
"type": "command",
|
|
97
|
-
"command": "curl -s -X POST http://localhost:4800/api/hooks/
|
|
88
|
+
"command": "curl -s -X POST http://localhost:4800/api/hooks/Notification -H 'Content-Type: application/json' --data-binary @- > /dev/null 2>&1 || true"
|
|
98
89
|
}
|
|
99
90
|
]
|
|
100
91
|
}
|
|
101
92
|
],
|
|
102
|
-
"
|
|
93
|
+
"Stop": [
|
|
94
|
+
{
|
|
95
|
+
"hooks": [
|
|
96
|
+
{
|
|
97
|
+
"type": "command",
|
|
98
|
+
"command": "curl -s -X POST http://localhost:4800/api/hooks/Stop -H 'Content-Type: application/json' --data-binary @- > /dev/null 2>&1 || true"
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
"SessionEnd": [
|
|
103
104
|
{
|
|
104
|
-
"matcher": "",
|
|
105
105
|
"hooks": [
|
|
106
106
|
{
|
|
107
107
|
"type": "command",
|
|
108
|
-
"command": "curl -s -X POST http://localhost:4800/api/hooks/
|
|
108
|
+
"command": "curl -s -X POST http://localhost:4800/api/hooks/SessionEnd -H 'Content-Type: application/json' --data-binary @- > /dev/null 2>&1 || true"
|
|
109
109
|
}
|
|
110
110
|
]
|
|
111
111
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-synapse",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Neural network visualization for Claude Code agent sessions",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"agent",
|
|
9
|
+
"visualization",
|
|
10
|
+
"neural-network",
|
|
11
|
+
"hooks"
|
|
12
|
+
],
|
|
6
13
|
"license": "MIT",
|
|
7
14
|
"author": "Iulian Leon",
|
|
8
15
|
"repository": {
|