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 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 start() {
42
- let entryPoint;
43
- let dashboardDir;
44
-
53
+ function getEntryAndDashboard() {
45
54
  if (isBundled) {
46
- // npm-installed mode: use pre-bundled files
47
- entryPoint = bundledCollector;
48
- dashboardDir = bundledDashboard;
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
- // Dev mode: build from source if needed
51
- if (!isDevBuilt()) build();
52
- entryPoint = resolve(ROOT, 'apps/collector/dist/index.js');
53
- dashboardDir = resolve(ROOT, 'apps/dashboard/dist');
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
- console.log(`[synapse] Starting on http://localhost:${PORT}`);
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
- collector.on('close', (code) => {
69
- process.exit(code ?? 0);
70
- });
71
-
72
- // Open browser after a short delay
73
- setTimeout(() => {
74
- const url = `http://localhost:${PORT}`;
75
- try {
76
- const platform = process.platform;
77
- if (platform === 'darwin') execSync(`open ${url}`);
78
- else if (platform === 'linux') execSync(`xdg-open ${url}`);
79
- else if (platform === 'win32') execSync(`start ${url}`);
80
- } catch {
81
- // Silently ignore if browser can't be opened
82
- }
83
- }, 1500);
84
-
85
- process.on('SIGINT', () => {
86
- collector.kill('SIGINT');
87
- process.exit(0);
88
- });
89
-
90
- process.on('SIGTERM', () => {
91
- collector.kill('SIGTERM');
92
- process.exit(0);
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();
@@ -1,111 +1,111 @@
1
1
  {
2
2
  "hooks": {
3
- "PreToolUse": [
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/PreToolUse -H 'Content-Type: application/json' -d \"$(echo '{}' | jq --arg hook PreToolUse --arg sid $CLAUDE_SESSION_ID --arg tn $TOOL_NAME --argjson ti \"${TOOL_INPUT:-{}}\" '. + {hook_event_name: $hook, session_id: $sid, tool_name: $tn, tool_input: $ti, tool_use_id: env.TOOL_USE_ID, permission_mode: env.CLAUDE_PERMISSION_MODE, cwd: env.CLAUDE_CWD, transcript_path: env.CLAUDE_TRANSCRIPT_PATH}')\""
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
- "PostToolUse": [
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/PostToolUse -H 'Content-Type: application/json' -d \"$(echo '{}' | jq --arg hook PostToolUse --arg sid $CLAUDE_SESSION_ID --arg tn $TOOL_NAME '. + {hook_event_name: $hook, session_id: $sid, tool_name: $tn, tool_use_id: env.TOOL_USE_ID, tool_response: env.TOOL_RESPONSE, permission_mode: env.CLAUDE_PERMISSION_MODE, cwd: env.CLAUDE_CWD, transcript_path: env.CLAUDE_TRANSCRIPT_PATH}')\""
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
- "Notification": [
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/Notification -H 'Content-Type: application/json' -d \"$(echo '{}' | jq --arg hook Notification --arg sid $CLAUDE_SESSION_ID --arg msg \"$NOTIFICATION_MESSAGE\" --arg title \"$NOTIFICATION_TITLE\" '. + {hook_event_name: $hook, session_id: $sid, message: $msg, title: $title, permission_mode: env.CLAUDE_PERMISSION_MODE, cwd: env.CLAUDE_CWD, transcript_path: env.CLAUDE_TRANSCRIPT_PATH}')\""
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
- "Stop": [
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/Stop -H 'Content-Type: application/json' -d \"$(echo '{}' | jq --arg hook Stop --arg sid $CLAUDE_SESSION_ID '. + {hook_event_name: $hook, session_id: $sid, last_assistant_message: env.LAST_ASSISTANT_MESSAGE, permission_mode: env.CLAUDE_PERMISSION_MODE, cwd: env.CLAUDE_CWD, transcript_path: env.CLAUDE_TRANSCRIPT_PATH}')\""
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
- "SubagentStart": [
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/SubagentStart -H 'Content-Type: application/json' -d \"$(echo '{}' | jq --arg hook SubagentStart --arg sid $CLAUDE_SESSION_ID '. + {hook_event_name: $hook, session_id: $sid, agent_id: env.AGENT_ID, agent_type: env.AGENT_TYPE, permission_mode: env.CLAUDE_PERMISSION_MODE, cwd: env.CLAUDE_CWD, transcript_path: env.CLAUDE_TRANSCRIPT_PATH}')\""
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
- "SubagentStop": [
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/SubagentStop -H 'Content-Type: application/json' -d \"$(echo '{}' | jq --arg hook SubagentStop --arg sid $CLAUDE_SESSION_ID '. + {hook_event_name: $hook, session_id: $sid, agent_id: env.AGENT_ID, agent_type: env.AGENT_TYPE, last_assistant_message: env.LAST_ASSISTANT_MESSAGE, permission_mode: env.CLAUDE_PERMISSION_MODE, cwd: env.CLAUDE_CWD, transcript_path: env.CLAUDE_TRANSCRIPT_PATH}')\""
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
- "SessionStart": [
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/SessionStart -H 'Content-Type: application/json' -d \"$(echo '{}' | jq --arg hook SessionStart --arg sid $CLAUDE_SESSION_ID '. + {hook_event_name: $hook, session_id: $sid, source: env.SESSION_SOURCE, model: env.CLAUDE_MODEL, permission_mode: env.CLAUDE_PERMISSION_MODE, cwd: env.CLAUDE_CWD, transcript_path: env.CLAUDE_TRANSCRIPT_PATH}')\""
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
- "SessionEnd": [
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/SessionEnd -H 'Content-Type: application/json' -d \"$(echo '{}' | jq --arg hook SessionEnd --arg sid $CLAUDE_SESSION_ID '. + {hook_event_name: $hook, session_id: $sid, reason: env.SESSION_END_REASON, permission_mode: env.CLAUDE_PERMISSION_MODE, cwd: env.CLAUDE_CWD, transcript_path: env.CLAUDE_TRANSCRIPT_PATH}')\""
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
- "UserPromptSubmit": [
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/UserPromptSubmit -H 'Content-Type: application/json' -d \"$(echo '{}' | jq --arg hook UserPromptSubmit --arg sid $CLAUDE_SESSION_ID --arg prompt \"$USER_PROMPT\" '. + {hook_event_name: $hook, session_id: $sid, prompt: $prompt, permission_mode: env.CLAUDE_PERMISSION_MODE, cwd: env.CLAUDE_CWD, transcript_path: env.CLAUDE_TRANSCRIPT_PATH}')\""
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
- "PreCompact": [
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/PreCompact -H 'Content-Type: application/json' -d \"$(echo '{}' | jq --arg hook PreCompact --arg sid $CLAUDE_SESSION_ID '. + {hook_event_name: $hook, session_id: $sid, trigger: env.COMPACT_TRIGGER, permission_mode: env.CLAUDE_PERMISSION_MODE, cwd: env.CLAUDE_CWD, transcript_path: env.CLAUDE_TRANSCRIPT_PATH}')\""
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.0",
3
+ "version": "1.0.2",
4
4
  "description": "Neural network visualization for Claude Code agent sessions",
5
- "keywords": ["claude", "claude-code", "agent", "visualization", "neural-network", "hooks"],
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": {