cicy-code 2.1.47 → 2.1.49

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/cicy-code.js +94 -13
  2. package/package.json +1 -1
package/bin/cicy-code.js CHANGED
@@ -1,18 +1,24 @@
1
1
  #!/usr/bin/env node
2
- // Thin launcher: resolves the prebuilt binary that ships in the
3
- // platform-specific optionalDependency (cicy-code-<os>-<cpu>) and execs it.
4
- // No network, no postinstall download npm installs only the sub-package
5
- // matching the current os/cpu (the others are skipped via their os/cpu
6
- // fields), so a CN user pulls just their ~30MB slice from npmmirror.
7
- const { spawn } = require('child_process');
2
+ // Launcher for the npm distribution. Resolves the prebuilt binary that ships
3
+ // in the platform-specific optionalDependency (cicy-code-<os>-<cpu>) and execs
4
+ // it — no network, no postinstall download. ALL binary args/subcommands are
5
+ // passed straight through (skill <...>, --dev, --hot, --helper=1, --agents=…,
6
+ // --public, --cn, etc.), so `npx cicy-code <anything>` == the binary.
7
+ //
8
+ // Only when actually starting the server, it mirrors dev.py's startup: if PORT
9
+ // (default 8008) is held by a *cicy-code* process it kills it and waits, so a
10
+ // stale instance never blocks the new one. A non-cicy occupant is left alone
11
+ // and we abort. Utility invocations (--help/--version, `skill …`) skip the
12
+ // port dance entirely — `npx cicy-code --version` must never touch :8008.
13
+ const { spawn, execSync } = require('child_process');
8
14
  const fs = require('fs');
9
15
 
10
- const platformPkg = `cicy-code-${process.platform}-${process.arch}`;
16
+ const args = process.argv.slice(2);
17
+ const PORT = process.env.PORT || '8008';
11
18
 
19
+ const platformPkg = `cicy-code-${process.platform}-${process.arch}`;
12
20
  let binPath;
13
21
  try {
14
- // require.resolve finds the binary inside the installed sub-package,
15
- // wherever the package manager hoisted it.
16
22
  binPath = require.resolve(`${platformPkg}/cicy-code`);
17
23
  } catch {
18
24
  console.error(`cicy-code: no prebuilt binary for ${process.platform}-${process.arch}.`);
@@ -22,13 +28,88 @@ try {
22
28
  ` (in China add --registry=https://registry.npmmirror.com)`);
23
29
  process.exit(1);
24
30
  }
25
-
26
- // npm restores the 0755 mode from the tarball, but chmod defensively in case
27
- // a mirror or extraction stripped the exec bit.
28
31
  try { fs.chmodSync(binPath, 0o755); } catch {}
29
32
 
30
- const child = spawn(binPath, process.argv.slice(2), { stdio: 'inherit', env: process.env });
33
+ // Utility invocations don't start the server, so they must NOT kill :8008.
34
+ const isUtility =
35
+ args.some((a) => a === '-h' || a === '--help' || a === '-v' || a === '--version') ||
36
+ args[0] === 'skill';
37
+
38
+ if (!isUtility) ensurePortFree(PORT);
39
+
40
+ const child = spawn(binPath, args, {
41
+ stdio: 'inherit',
42
+ env: { ...process.env, PORT },
43
+ });
31
44
  child.on('exit', (code, signal) => {
32
45
  if (signal) process.kill(process.pid, signal);
33
46
  else process.exit(code == null ? 0 : code);
34
47
  });
48
+
49
+ // --- dev.py-style port hygiene --------------------------------------------
50
+
51
+ function ensurePortFree(port) {
52
+ const existing = pidOnPort(port);
53
+ if (!existing) return;
54
+ const cmd = processCommand(existing);
55
+ if (/cicy-code/.test(cmd)) {
56
+ console.log(`cicy-code: stopping existing instance on :${port} (pid=${existing})`);
57
+ if (!killPid(existing) || pidOnPort(port)) {
58
+ console.error(`cicy-code: port ${port} still in use after kill — aborting`);
59
+ process.exit(1);
60
+ }
61
+ } else {
62
+ console.error(`cicy-code: port ${port} is held by a non-cicy process (pid=${existing}): ${cmd}`);
63
+ console.error(`cicy-code: free it or set PORT=<other> — aborting`);
64
+ process.exit(1);
65
+ }
66
+ }
67
+
68
+ function pidOnPort(port) {
69
+ // lsof first (macOS + Linux), then ss (Linux without lsof).
70
+ try {
71
+ const out = execSync(`lsof -ti TCP:${port} -sTCP:LISTEN`, {
72
+ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
73
+ }).trim();
74
+ if (out) return out.split('\n')[0].trim();
75
+ } catch {}
76
+ try {
77
+ const out = execSync(`ss -tlnp 'sport = :${port}'`, {
78
+ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
79
+ });
80
+ const m = out.match(/pid=(\d+)/);
81
+ if (m) return m[1];
82
+ } catch {}
83
+ return null;
84
+ }
85
+
86
+ function processCommand(pid) {
87
+ try {
88
+ return execSync(`ps -p ${pid} -o command=`, {
89
+ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
90
+ }).trim();
91
+ } catch { return ''; }
92
+ }
93
+
94
+ function isAlive(pid) {
95
+ try { process.kill(Number(pid), 0); return true; }
96
+ catch (e) { return e.code === 'EPERM'; }
97
+ }
98
+
99
+ function waitExit(pid, timeoutMs) {
100
+ const start = Date.now();
101
+ while (Date.now() - start < timeoutMs) {
102
+ if (!isAlive(pid)) return true;
103
+ try { execSync('sleep 0.2'); } catch {}
104
+ }
105
+ return !isAlive(pid);
106
+ }
107
+
108
+ function killPid(pid) {
109
+ try { process.kill(Number(pid), 'SIGTERM'); }
110
+ catch (e) { if (e.code === 'ESRCH') return true; }
111
+ if (waitExit(pid, 6000)) return true;
112
+ try { process.kill(Number(pid), 'SIGKILL'); }
113
+ catch (e) { if (e.code === 'ESRCH') return true; }
114
+ return waitExit(pid, 2000);
115
+ }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "2.1.47",
6
+ "version": "2.1.49",
7
7
  "description": "CiCy Code - AI-powered development environment",
8
8
  "author": {
9
9
  "name": "cicybot",