@jait/gateway 0.1.396 → 0.1.397

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/jait.mjs +111 -29
  2. package/package.json +1 -1
package/bin/jait.mjs CHANGED
@@ -44,6 +44,7 @@ const ENV_PATH = join(JAIT_DIR, ".env");
44
44
  const LOG_PATH = join(JAIT_DIR, "gateway.log");
45
45
  const ERR_LOG_PATH = join(JAIT_DIR, "gateway.err.log");
46
46
  const PID_PATH = join(JAIT_DIR, "jait.pid");
47
+ const LEGACY_PID_PATH = join(JAIT_DIR, "gateway.pid");
47
48
 
48
49
  function systemdUnitDir() {
49
50
  return join(homedir(), ".config", "systemd", "user");
@@ -122,6 +123,30 @@ function runSilent(cmd) {
122
123
  return run(cmd, { silent: true }).trim();
123
124
  }
124
125
 
126
+ function cleanupPidFile(pidPath) {
127
+ try { unlinkSync(pidPath); } catch {}
128
+ }
129
+
130
+ function getTrackedProcess() {
131
+ for (const pidPath of [PID_PATH, LEGACY_PID_PATH]) {
132
+ if (!existsSync(pidPath)) continue;
133
+
134
+ const pid = readFileSync(pidPath, "utf8").trim();
135
+ if (!pid) {
136
+ cleanupPidFile(pidPath);
137
+ continue;
138
+ }
139
+
140
+ if (isProcessRunning(pid)) {
141
+ return { pid, pidPath };
142
+ }
143
+
144
+ cleanupPidFile(pidPath);
145
+ }
146
+
147
+ return null;
148
+ }
149
+
125
150
  // ── Cross-platform commands ─────────────────────────────────────────
126
151
 
127
152
  function healthCheck(port) {
@@ -146,21 +171,53 @@ function healthCheck(port) {
146
171
  });
147
172
  }
148
173
 
174
+ function isPortReachable(port) {
175
+ return new Promise((resolveP) => {
176
+ const socket = createConnection({ host: "127.0.0.1", port: Number(port) }, () => {
177
+ socket.destroy();
178
+ resolveP(true);
179
+ });
180
+
181
+ socket.on("error", () => resolveP(false));
182
+ socket.setTimeout(1000, () => {
183
+ socket.destroy();
184
+ resolveP(false);
185
+ });
186
+ });
187
+ }
188
+
189
+ function sleep(ms) {
190
+ return new Promise((resolveP) => setTimeout(resolveP, ms));
191
+ }
192
+
193
+ async function waitForBackgroundStart(pid, port, { timeoutMs = 5000, pollMs = 250 } = {}) {
194
+ const deadline = Date.now() + timeoutMs;
195
+ while (Date.now() < deadline) {
196
+ if (!isProcessRunning(pid)) {
197
+ return { ok: false, reason: "exit" };
198
+ }
199
+
200
+ const health = await healthCheck(port);
201
+ if (health) {
202
+ return { ok: true, health };
203
+ }
204
+
205
+ await sleep(pollMs);
206
+ }
207
+
208
+ if (!isProcessRunning(pid)) {
209
+ return { ok: false, reason: "exit" };
210
+ }
211
+
212
+ return { ok: false, reason: "timeout" };
213
+ }
214
+
149
215
  async function cmdStatus(port) {
150
216
  printBanner();
151
217
  port = port || process.env.PORT || "8000";
152
218
 
153
- // Check PID file
154
- let pid = null;
155
- if (existsSync(PID_PATH)) {
156
- pid = readFileSync(PID_PATH, "utf8").trim();
157
- const alive = isProcessRunning(pid);
158
- if (!alive) {
159
- // Stale PID file
160
- try { unlinkSync(PID_PATH); } catch {}
161
- pid = null;
162
- }
163
- }
219
+ const tracked = getTrackedProcess();
220
+ const pid = tracked?.pid ?? null;
164
221
 
165
222
  const health = await healthCheck(port);
166
223
  if (health) {
@@ -171,10 +228,14 @@ async function cmdStatus(port) {
171
228
  console.log(` Healthy: ${health.healthy ? "yes" : "no"}`);
172
229
  console.log(` Uptime: ${health.uptime}s`);
173
230
  } else {
231
+ const reachable = await isPortReachable(port);
174
232
  console.log(` Status: not running`);
175
233
  console.log(` Port: ${port} (checked)`);
176
234
  if (pid) {
177
235
  console.log(` PID: ${pid} (process exists but not responding)`);
236
+ } else if (reachable) {
237
+ console.log(` Health: timed out on /health`);
238
+ console.log(` Note: another process is listening on port ${port}`);
178
239
  }
179
240
  }
180
241
  console.log("");
@@ -237,19 +298,32 @@ function isProcessRunning(pid) {
237
298
  }
238
299
  }
239
300
 
240
- function cmdStart(cliFlags) {
301
+ async function cmdStart(cliFlags) {
241
302
  printBanner();
303
+ const port = cliFlags.port || process.env.PORT || "8000";
304
+
242
305
  // Check if already running
243
- if (existsSync(PID_PATH)) {
244
- const pid = readFileSync(PID_PATH, "utf8").trim();
245
- if (isProcessRunning(pid)) {
246
- console.log(` Jait is already running (PID ${pid}).`);
306
+ const tracked = getTrackedProcess();
307
+ if (tracked) {
308
+ if (await healthCheck(port)) {
309
+ console.log(` Jait is already running (PID ${tracked.pid}).`);
247
310
  console.log(` Run 'jait stop' first, or 'jait status' for details.`);
248
311
  console.log("");
249
312
  process.exit(1);
250
313
  }
251
- // Stale PID file
252
- try { unlinkSync(PID_PATH); } catch {}
314
+
315
+ console.error(` A tracked Jait process already exists (PID ${tracked.pid}) but is not healthy.`);
316
+ console.error(` Run 'jait stop' or inspect ${LOG_PATH} before starting a new instance.`);
317
+ console.log("");
318
+ process.exit(1);
319
+ }
320
+
321
+ if (await isPortReachable(port)) {
322
+ console.error(` Port ${port} is already in use.`);
323
+ console.error(` A gateway or another process is listening but not responding to /health.`);
324
+ console.error(` Stop the existing process or start Jait on a different port with 'jait start --port <port>'.`);
325
+ console.log("");
326
+ process.exit(1);
253
327
  }
254
328
 
255
329
  mkdirSync(JAIT_DIR, { recursive: true });
@@ -273,6 +347,20 @@ function cmdStart(cliFlags) {
273
347
  writeFileSync(PID_PATH, String(child.pid), "utf8");
274
348
  child.unref();
275
349
 
350
+ const started = await waitForBackgroundStart(child.pid, port);
351
+ if (!started.ok) {
352
+ cleanupPidFile(PID_PATH);
353
+ console.error(` Jait failed to become healthy on port ${port}.`);
354
+ if (started.reason === "exit") {
355
+ console.error(` The background process exited during startup.`);
356
+ } else {
357
+ console.error(` The background process did not answer /health within 5 seconds.`);
358
+ }
359
+ console.error(` Logs: ${LOG_PATH}`);
360
+ console.log("");
361
+ process.exit(1);
362
+ }
363
+
276
364
  console.log(` Jait started in background (PID ${child.pid}).`);
277
365
  console.log(` Logs: ${LOG_PATH}`);
278
366
  console.log(` Run 'jait status' to check health.`);
@@ -282,20 +370,14 @@ function cmdStart(cliFlags) {
282
370
 
283
371
  function cmdStop() {
284
372
  printBanner();
285
- if (!existsSync(PID_PATH)) {
373
+ const tracked = getTrackedProcess();
374
+ if (!tracked) {
286
375
  console.log(" Jait is not running (no PID file found).");
287
376
  console.log("");
288
377
  process.exit(1);
289
378
  }
290
379
 
291
- const pid = readFileSync(PID_PATH, "utf8").trim();
292
-
293
- if (!isProcessRunning(pid)) {
294
- console.log(` Process ${pid} is not running (stale PID file).`);
295
- try { unlinkSync(PID_PATH); } catch {}
296
- console.log("");
297
- process.exit(0);
298
- }
380
+ const { pid, pidPath } = tracked;
299
381
 
300
382
  try {
301
383
  if (platform() === "win32") {
@@ -308,7 +390,7 @@ function cmdStop() {
308
390
  console.error(` Failed to stop process ${pid}: ${err.message}`);
309
391
  }
310
392
 
311
- try { unlinkSync(PID_PATH); } catch {}
393
+ cleanupPidFile(pidPath);
312
394
  console.log("");
313
395
  }
314
396
 
@@ -596,7 +678,7 @@ if (args[0] === "status") {
596
678
 
597
679
  if (args[0] === "start") {
598
680
  parseSubcommandFlags(1);
599
- cmdStart(flags);
681
+ await cmdStart(flags);
600
682
  process.exit(0);
601
683
  }
602
684
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jait/gateway",
3
- "version": "0.1.396",
3
+ "version": "0.1.397",
4
4
  "description": "Jait AI gateway — local-first AI coding agent with terminal, filesystem, and browser control",
5
5
  "author": "JakobWl",
6
6
  "type": "module",