claude-synapse 1.0.1 → 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.
Files changed (2) hide show
  1. package/bin/synapse.mjs +116 -40
  2. package/package.json +1 -1
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-synapse",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Neural network visualization for Claude Code agent sessions",
5
5
  "keywords": [
6
6
  "claude",