arceus-s 1.6.4 → 1.6.5

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/dist/cli/index.js CHANGED
@@ -99,6 +99,7 @@ Commands:
99
99
  analyze [path] Index a repository (full analysis)
100
100
  index [path...] Register an existing .arc/ folder into the global registry
101
101
  serve Start local HTTP server for web UI connection
102
+ stop Stop the local HTTP server on a port (default: 4747)
102
103
  mcp Start MCP server (stdio) — serves all indexed repos
103
104
  list List all indexed repositories
104
105
  status Show index status for current repo
@@ -175,6 +176,14 @@ Options:
175
176
  -p, --port <port> Port number (default: 4747)
176
177
  --host <host> Bind address (default: 127.0.0.1)`);
177
178
  break;
179
+ case 'stop':
180
+ console.log(`Usage: ${binName} stop [options]
181
+
182
+ Stop the local HTTP server on a port
183
+
184
+ Options:
185
+ -p, --port <port> Port number (default: 4747)`);
186
+ break;
178
187
  case 'clean':
179
188
  console.log(`Usage: ${binName} clean [options]
180
189
 
@@ -369,6 +378,13 @@ async function runCLI() {
369
378
  await serveCommand(options);
370
379
  break;
371
380
  }
381
+ case 'stop': {
382
+ if (options.port === undefined)
383
+ options.port = '4747';
384
+ const { stopCommand } = await import('./stop.js');
385
+ await stopCommand(options);
386
+ break;
387
+ }
372
388
  case 'mcp': {
373
389
  const { mcpCommand } = await import('./mcp.js');
374
390
  await mcpCommand();
@@ -0,0 +1,3 @@
1
+ export declare const stopCommand: (options?: {
2
+ port?: string;
3
+ }) => Promise<void>;
@@ -0,0 +1,122 @@
1
+ import { exec } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { logger } from '../core/logger.js';
4
+ const execAsync = promisify(exec);
5
+ export const stopCommand = async (options) => {
6
+ const port = Number(options?.port ?? 4747);
7
+ if (isNaN(port) || port <= 0 || port > 65535) {
8
+ console.error(`Error: Invalid port number: ${options?.port}`);
9
+ process.exitCode = 1;
10
+ return;
11
+ }
12
+ console.log(`Stopping process on port ${port}...`);
13
+ try {
14
+ const pids = await findPidsOnPort(port);
15
+ if (pids.size === 0) {
16
+ console.log(`No active process found using port ${port}.`);
17
+ return;
18
+ }
19
+ const killedPids = [];
20
+ const failedPids = [];
21
+ for (const pid of pids) {
22
+ if (pid === process.pid) {
23
+ // Don't kill ourselves
24
+ continue;
25
+ }
26
+ try {
27
+ await killPid(pid);
28
+ killedPids.push(pid);
29
+ }
30
+ catch (err) {
31
+ logger.error({ err, pid }, 'Failed to kill process');
32
+ failedPids.push(pid);
33
+ }
34
+ }
35
+ if (killedPids.length > 0) {
36
+ console.log(`Successfully stopped process(es) on port ${port} (PID: ${killedPids.join(', ')}).`);
37
+ }
38
+ if (failedPids.length > 0) {
39
+ console.error(`Failed to stop process(es) on port ${port} (PID: ${failedPids.join(', ')}).`);
40
+ process.exitCode = 1;
41
+ }
42
+ }
43
+ catch (err) {
44
+ console.error(`Error while scanning port ${port}:`, err.message || err);
45
+ process.exitCode = 1;
46
+ }
47
+ };
48
+ async function findPidsOnPort(port) {
49
+ const pids = new Set();
50
+ if (process.platform === 'win32') {
51
+ try {
52
+ const { stdout } = await execAsync('netstat -ano');
53
+ const lines = stdout.split('\n');
54
+ for (const line of lines) {
55
+ const trimmed = line.trim();
56
+ if (!trimmed)
57
+ continue;
58
+ const parts = trimmed.split(/\s+/);
59
+ if (parts.length < 4)
60
+ continue;
61
+ // Local address is the second column (index 1)
62
+ const localAddress = parts[1];
63
+ if (localAddress &&
64
+ (localAddress.endsWith(`:${port}`) || localAddress.endsWith(`]:${port}`))) {
65
+ const pidStr = parts[parts.length - 1];
66
+ const pid = parseInt(pidStr, 10);
67
+ if (!isNaN(pid) && pid > 0) {
68
+ pids.add(pid);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ catch (err) {
74
+ logger.debug({ err }, 'netstat execution failed');
75
+ throw new Error(`Failed to list connections via netstat: ${err.message}`);
76
+ }
77
+ }
78
+ else {
79
+ // macOS / Linux
80
+ try {
81
+ const { stdout } = await execAsync(`lsof -t -i :${port}`);
82
+ const lines = stdout.trim().split('\n');
83
+ for (const line of lines) {
84
+ const pid = parseInt(line.trim(), 10);
85
+ if (!isNaN(pid) && pid > 0) {
86
+ pids.add(pid);
87
+ }
88
+ }
89
+ }
90
+ catch (err) {
91
+ // lsof returns exit code 1 if no matches are found, which is not a real failure
92
+ if (err.code !== 1) {
93
+ // Try fallback to fuser
94
+ try {
95
+ const { stdout } = await execAsync(`fuser ${port}/tcp`);
96
+ const lines = stdout.trim().split(/\s+/);
97
+ for (const line of lines) {
98
+ const pid = parseInt(line.trim(), 10);
99
+ if (!isNaN(pid) && pid > 0) {
100
+ pids.add(pid);
101
+ }
102
+ }
103
+ }
104
+ catch (fuserErr) {
105
+ logger.debug({ fuserErr }, 'fuser execution failed');
106
+ if (fuserErr.code !== 1) {
107
+ throw new Error(`Failed to list connections via lsof/fuser: ${err.message}`);
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ return pids;
114
+ }
115
+ async function killPid(pid) {
116
+ if (process.platform === 'win32') {
117
+ await execAsync(`taskkill /F /PID ${pid}`);
118
+ }
119
+ else {
120
+ await execAsync(`kill -9 ${pid}`);
121
+ }
122
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arceus-s",
3
- "version": "1.6.4",
3
+ "version": "1.6.5",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Chandan Kumar Behera",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -119,4 +119,4 @@
119
119
  "engines": {
120
120
  "node": ">=22.0.0"
121
121
  }
122
- }
122
+ }