deltara 0.30.54 → 0.31.0

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "deltara": "sha256:9517a21c066e5bd376d0f970e0bfc0412da298d4520421b93950d7eb3576289a",
3
- "generated_at": "2026-05-05T18:37:53Z",
4
- "version": "0.30.54"
2
+ "deltara": "sha256:b6abe5779edcfdc9dfea0d27eb4cb3250d7b21a2929e17d4f1d6e3af7f4bbb8c",
3
+ "generated_at": "2026-05-05T19:23:55Z",
4
+ "version": "0.31.0"
5
5
  }
@@ -1,18 +1,27 @@
1
1
  #!/usr/bin/env node
2
- // postinstall.js — runs `deltara setup --auto` with output wired directly to
3
- // the controlling terminal, bypassing npm's stdout pipe.
2
+ // postinstall.js — npm lifecycle hook (runs after `npm install -g deltara`).
4
3
  //
5
- // npm (v7+) runs lifecycle scripts with stdout piped — not connected to the
6
- // terminal. Even `stdio: "inherit"` inherits npm's pipe, not /dev/tty.
7
- // Opening /dev/tty directly gives us the actual terminal fd, so all setup
8
- // output (header, steps, privacy lines) is visible to the user.
4
+ // Three phases:
5
+ // 1. Stop any running deltara server (old binary in memory).
6
+ // 2. Run `deltara setup --auto` for auth/MCP config (idempotent).
7
+ // 3. Start a fresh deltara server detached (new binary in memory).
8
+ //
9
+ // Why all three matter:
10
+ // The deltara server is a long-running standalone daemon — NO MCP host
11
+ // respawns it on demand. After `npm install -g deltara@<new>`, the new
12
+ // binary is on disk but the old server keeps running with the old binary
13
+ // loaded in memory. The release also rotates _CLI_SECRET, so the old
14
+ // server hits 403 forever and pipelines silently fail. Killing without
15
+ // restart just leaves the user with a dead system. Both halves required.
16
+ //
17
+ // All steps are best-effort — never fail npm install over upgrade hygiene.
9
18
 
10
- const { spawnSync } = require("child_process");
19
+ const { spawnSync, spawn } = require("child_process");
11
20
  const path = require("path");
12
21
  const fs = require("fs");
13
22
  const os = require("os");
14
23
 
15
- // Skip in CI or non-interactive environments where /dev/tty won't exist.
24
+ // Skip in CI or non-interactive environments.
16
25
  if (process.env.CI || process.env.DELTARA_NO_SETUP) {
17
26
  process.exit(0);
18
27
  }
@@ -22,32 +31,97 @@ const arch = os.arch();
22
31
  const platformKey = `${platform}-${arch}`;
23
32
  const binName = platform === "win32" ? "deltara.exe" : "deltara";
24
33
  const binPath = path.join(__dirname, "..", "vendor", platformKey, binName);
34
+ const homeDir = os.homedir();
35
+ const deltaraDir = path.join(homeDir, ".deltara");
36
+ const pidFile = path.join(deltaraDir, "server.pid");
37
+ const upgradePendingFile = path.join(deltaraDir, "mcp_upgrade_pending");
25
38
 
26
39
  if (!fs.existsSync(binPath)) {
27
- // Unsupported platform — not an error, setup just won't run.
40
+ // Unsupported platform — not an error.
28
41
  process.exit(0);
29
42
  }
30
43
 
31
44
  // Ensure executable bit (may be missing on some npm extract paths).
32
45
  try { fs.chmodSync(binPath, 0o755); } catch (_) {}
33
46
 
34
- // Open /dev/tty as the output fd — this is the controlling terminal regardless
35
- // of how npm has wired our stdout/stderr.
47
+ // ─────────────────────────────────────────────────────────────────────
48
+ // Phase 1: Stop any running deltara server.
49
+ // ─────────────────────────────────────────────────────────────────────
50
+ function killOldServer() {
51
+ let killed = false;
52
+
53
+ // 1a. Read PID file and try graceful then forceful kill.
54
+ if (fs.existsSync(pidFile)) {
55
+ try {
56
+ const pidStr = fs.readFileSync(pidFile, "utf8").trim();
57
+ const pid = parseInt(pidStr, 10);
58
+ if (pid > 0) {
59
+ try {
60
+ process.kill(pid, "SIGTERM");
61
+ // Give the server up to ~2.5s to exit gracefully.
62
+ const start = Date.now();
63
+ while (Date.now() - start < 2500) {
64
+ try {
65
+ process.kill(pid, 0); // signal 0 = check existence
66
+ // Still alive — sleep a beat
67
+ const wait = spawnSync(process.execPath, ["-e", "setTimeout(()=>{},250)"], { stdio: "ignore" });
68
+ if (wait.status === null) break;
69
+ } catch (_) {
70
+ // ESRCH — process is gone
71
+ killed = true;
72
+ break;
73
+ }
74
+ }
75
+ // Forceful if still alive.
76
+ if (!killed) {
77
+ try { process.kill(pid, "SIGKILL"); killed = true; } catch (_) {}
78
+ }
79
+ } catch (e) {
80
+ // EPERM (different user) or ESRCH (already gone) — both OK.
81
+ }
82
+ }
83
+ } catch (_) {}
84
+ }
85
+
86
+ // 1b. Belt-and-suspenders: pkill by command-line match (Unix only).
87
+ // Catches PIDs from stale lock files where pidFile is missing.
88
+ if (platform !== "win32") {
89
+ try {
90
+ spawnSync("pkill", ["-9", "-f", "deltara server"], { stdio: "ignore" });
91
+ } catch (_) {}
92
+ }
93
+
94
+ // 1c. Clean lock files.
95
+ for (const f of [pidFile, upgradePendingFile]) {
96
+ try { if (fs.existsSync(f)) fs.unlinkSync(f); } catch (_) {}
97
+ }
98
+
99
+ return killed;
100
+ }
101
+
102
+ const wasRunning = killOldServer();
103
+ if (wasRunning) {
104
+ // Print to stdout (postinstall) so user sees the action.
105
+ console.log(" Deltara: stopped previous server (upgrade).");
106
+ }
107
+
108
+ // ─────────────────────────────────────────────────────────────────────
109
+ // Phase 2: Run `deltara setup --auto` with TTY wired up.
110
+ // ─────────────────────────────────────────────────────────────────────
36
111
  let ttyFd;
37
112
  if (platform !== "win32") {
38
113
  try {
39
114
  ttyFd = fs.openSync("/dev/tty", "w");
40
115
  } catch (_) {
41
- // No controlling terminal (e.g. SSH without TTY allocation, Docker).
42
- // Run silently rather than crashing the install.
116
+ // No controlling terminal (SSH no-TTY, Docker, etc.).
117
+ // Skip both setup and server start — user can run `deltara setup` manually.
43
118
  process.exit(0);
44
119
  }
45
120
  } else {
46
- // Windows: stdout is already fine (npm behaviour differs).
47
121
  ttyFd = process.stdout.fd;
48
122
  }
49
123
 
50
- const result = spawnSync(binPath, ["setup", "--auto"], {
124
+ const setupResult = spawnSync(binPath, ["setup", "--auto"], {
51
125
  stdio: ["inherit", ttyFd, ttyFd],
52
126
  env: process.env,
53
127
  });
@@ -56,5 +130,62 @@ if (ttyFd !== process.stdout.fd) {
56
130
  try { fs.closeSync(ttyFd); } catch (_) {}
57
131
  }
58
132
 
59
- // Non-zero exits are non-fatal — a failed setup shouldn't block npm install.
133
+ // Setup non-zero exit is non-fatal — but don't try to start the server
134
+ // if setup failed; auth config may be incomplete.
135
+ if (setupResult.status !== 0) {
136
+ process.exit(0);
137
+ }
138
+
139
+ // ─────────────────────────────────────────────────────────────────────
140
+ // Phase 3: Start fresh deltara server detached.
141
+ // ─────────────────────────────────────────────────────────────────────
142
+ function startNewServer() {
143
+ try {
144
+ const child = spawn(binPath, ["server", "start"], {
145
+ detached: true,
146
+ stdio: "ignore",
147
+ env: process.env,
148
+ });
149
+ child.unref();
150
+ // Brief poll for confirmation: server.pid should be rewritten with the
151
+ // new process's PID within a couple of seconds.
152
+ const start = Date.now();
153
+ let confirmed = false;
154
+ while (Date.now() - start < 4000) {
155
+ if (fs.existsSync(pidFile)) {
156
+ try {
157
+ const newPid = parseInt(fs.readFileSync(pidFile, "utf8").trim(), 10);
158
+ if (newPid > 0) {
159
+ try {
160
+ process.kill(newPid, 0); // alive check
161
+ confirmed = true;
162
+ break;
163
+ } catch (_) {}
164
+ }
165
+ } catch (_) {}
166
+ }
167
+ // Short busy-wait — child_process.spawnSync(setTimeout) trick avoids
168
+ // requiring async/await in this script.
169
+ const _w = spawnSync(process.execPath, ["-e", "setTimeout(()=>{},250)"], { stdio: "ignore" });
170
+ if (_w.status === null) break;
171
+ }
172
+ if (confirmed) {
173
+ console.log(" Deltara: new server started.");
174
+ } else {
175
+ console.log(
176
+ " Deltara: server did not confirm within 4s. " +
177
+ "Run 'deltara server start' to see the error live."
178
+ );
179
+ }
180
+ } catch (e) {
181
+ console.log(
182
+ " Deltara: failed to spawn new server: " + e.message + "\n" +
183
+ " Run 'deltara server start' to start it manually."
184
+ );
185
+ }
186
+ }
187
+
188
+ startNewServer();
189
+
190
+ // Always exit 0 — npm install must never fail because of upgrade-restart.
60
191
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deltara",
3
- "version": "0.30.54",
3
+ "version": "0.31.0",
4
4
  "description": "Multi-agent research pipelines \u2014 run deep research from your terminal",
5
5
  "bin": {
6
6
  "deltara": "bin/deltara.js"
Binary file