niahere 0.2.57 → 0.2.59

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.
@@ -8,7 +8,15 @@ import { randomUUID } from "crypto";
8
8
  import { buildSystemPrompt, getSessionContext } from "./identity";
9
9
  import { getAgentDefinitions } from "../core/agents";
10
10
  import { Session, Message, ActiveEngine } from "../db/models";
11
- import type { Attachment, SendResult, StreamCallback, ActivityCallback, SendCallbacks, ChatEngine, EngineOptions } from "../types";
11
+ import type {
12
+ Attachment,
13
+ SendResult,
14
+ StreamCallback,
15
+ ActivityCallback,
16
+ SendCallbacks,
17
+ ChatEngine,
18
+ EngineOptions,
19
+ } from "../types";
12
20
  import { truncate, formatToolUse } from "../utils/format-activity";
13
21
  import { consolidateSession } from "../core/consolidator";
14
22
  import { summarizeSession } from "../core/summarizer";
@@ -25,10 +33,19 @@ interface SDKUserMessage {
25
33
  }
26
34
 
27
35
  /** Convert provider-agnostic attachments to Anthropic content blocks. */
28
- export function buildContentBlocks(text: string, attachments?: Attachment[]): MessageParam["content"] {
36
+ export function buildContentBlocks(
37
+ text: string,
38
+ attachments?: Attachment[],
39
+ ): MessageParam["content"] {
29
40
  if (!attachments?.length) return text;
30
41
 
31
- const blocks: Array<{ type: "text"; text: string } | { type: "image"; source: { type: "base64"; media_type: string; data: string } }> = [];
42
+ const blocks: Array<
43
+ | { type: "text"; text: string }
44
+ | {
45
+ type: "image";
46
+ source: { type: "base64"; media_type: string; data: string };
47
+ }
48
+ > = [];
32
49
 
33
50
  for (const att of attachments) {
34
51
  if (att.type === "image") {
@@ -94,6 +111,7 @@ class MessageStream {
94
111
 
95
112
  interface PendingResult {
96
113
  userMessage: string;
114
+ userSaved: boolean;
97
115
  onStream: StreamCallback | null;
98
116
  onActivity: ActivityCallback | null;
99
117
  accumulatedText: string;
@@ -103,15 +121,22 @@ interface PendingResult {
103
121
  reject: (error: Error) => void;
104
122
  }
105
123
 
106
-
107
124
  function sessionFileExists(sessionId: string, cwd: string): boolean {
108
125
  // SDK stores sessions at ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl
109
126
  const encoded = cwd.replace(/\//g, "-");
110
- const sessionFile = join(homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
127
+ const sessionFile = join(
128
+ homedir(),
129
+ ".claude",
130
+ "projects",
131
+ encoded,
132
+ `${sessionId}.jsonl`,
133
+ );
111
134
  return existsSync(sessionFile);
112
135
  }
113
136
 
114
- export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine> {
137
+ export async function createChatEngine(
138
+ opts: EngineOptions,
139
+ ): Promise<ChatEngine> {
115
140
  const { room, channel, resume, mcpServers } = opts;
116
141
  let systemPrompt = buildSystemPrompt("chat", channel);
117
142
 
@@ -156,7 +181,10 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
156
181
  idleTimer = setTimeout(async () => {
157
182
  if (pending) {
158
183
  // Don't tear down while a request is in flight
159
- log.warn({ room }, "idle timer fired while request pending, skipping teardown");
184
+ log.warn(
185
+ { room },
186
+ "idle timer fired while request pending, skipping teardown",
187
+ );
160
188
  return;
161
189
  }
162
190
  // Memory consolidation + session summary before "sleep"
@@ -165,7 +193,10 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
165
193
  log.error({ err, room }, "consolidation failed during idle teardown");
166
194
  });
167
195
  summarizeSession(sessionId, room).catch((err) => {
168
- log.error({ err, room }, "session summary failed during idle teardown");
196
+ log.error(
197
+ { err, room },
198
+ "session summary failed during idle teardown",
199
+ );
169
200
  });
170
201
  }
171
202
  teardown();
@@ -185,7 +216,10 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
185
216
  longRunningTimer = setTimeout(() => {
186
217
  if (pending) {
187
218
  longRunningWarned = true;
188
- log.warn({ room, elapsed: LONG_RUNNING_WARN / 1000 }, "engine request running for 30+ minutes");
219
+ log.warn(
220
+ { room, elapsed: LONG_RUNNING_WARN / 1000 },
221
+ "engine request running for 30+ minutes",
222
+ );
189
223
  }
190
224
  }, LONG_RUNNING_WARN);
191
225
  }
@@ -250,7 +284,7 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
250
284
  await Session.create(sessionId, room);
251
285
  }
252
286
 
253
- if (pending) {
287
+ if (pending && !pending.userSaved) {
254
288
  await Message.save({
255
289
  sessionId,
256
290
  room,
@@ -258,6 +292,7 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
258
292
  content: pending.userMessage,
259
293
  isFromAgent: false,
260
294
  });
295
+ pending.userSaved = true;
261
296
  messageCount++;
262
297
  }
263
298
  }
@@ -279,7 +314,10 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
279
314
  if (lines.length > 1) {
280
315
  // Show the last complete line (not the partial one being typed)
281
316
  const completeLine = lines[lines.length - 2]?.trim();
282
- if (completeLine && completeLine !== pending.lastThinkingLine) {
317
+ if (
318
+ completeLine &&
319
+ completeLine !== pending.lastThinkingLine
320
+ ) {
283
321
  pending.lastThinkingLine = completeLine;
284
322
  pending.onActivity?.(truncate(completeLine, 70));
285
323
  }
@@ -344,6 +382,7 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
344
382
  duration_ms: msg.duration_ms,
345
383
  duration_api_ms: msg.duration_api_ms,
346
384
  stop_reason: msg.stop_reason,
385
+ terminal_reason: msg.terminal_reason,
347
386
  session_id: msg.session_id,
348
387
  subtype: msg.subtype,
349
388
  usage: msg.usage,
@@ -364,15 +403,26 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
364
403
  try {
365
404
  messageId = await Message.save(saveParams);
366
405
  } catch {
367
- messageId = await Message.save({ ...saveParams, metadata: undefined });
406
+ messageId = await Message.save({
407
+ ...saveParams,
408
+ metadata: undefined,
409
+ });
368
410
  }
369
411
  await Session.touch(sessionId);
370
- Session.accumulateMetadata(sessionId, { ...metadata, channel }).catch(() => {});
412
+ Session.accumulateMetadata(sessionId, {
413
+ ...metadata,
414
+ channel,
415
+ }).catch(() => {});
371
416
  }
372
417
 
373
418
  await ActiveEngine.unregister(room);
374
419
  clearLongRunningTimer();
375
- pending.resolve({ result: resultText, costUsd, turns, messageId });
420
+ pending.resolve({
421
+ result: resultText,
422
+ costUsd,
423
+ turns,
424
+ messageId,
425
+ });
376
426
  pending = null;
377
427
  resetIdleTimer();
378
428
  } else {
@@ -390,9 +440,16 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
390
440
  // Stream ended without a result — subprocess exited or was killed
391
441
  if (pending) {
392
442
  const partial = pending.accumulatedText;
393
- log.error({ room, partialChars: partial.length }, "query stream ended without result, rejecting pending request");
443
+ log.error(
444
+ { room, partialChars: partial.length },
445
+ "query stream ended without result, rejecting pending request",
446
+ );
394
447
  await ActiveEngine.unregister(room).catch(() => {});
395
- pending.reject(new Error(`stream ended without result (${partial.length} chars accumulated)`));
448
+ pending.reject(
449
+ new Error(
450
+ `stream ended without result (${partial.length} chars accumulated)`,
451
+ ),
452
+ );
396
453
  pending = null;
397
454
  }
398
455
  } catch (err) {
@@ -419,7 +476,11 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
419
476
  return room;
420
477
  },
421
478
 
422
- async send(userMessage: string, callbacks?: SendCallbacks, attachments?: Attachment[]) {
479
+ async send(
480
+ userMessage: string,
481
+ callbacks?: SendCallbacks,
482
+ attachments?: Attachment[],
483
+ ) {
423
484
  // Clear idle timer — engine is not idle while processing a request
424
485
  clearIdleTimer();
425
486
  startLongRunningTimer();
@@ -430,9 +491,26 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
430
491
  startQuery();
431
492
  }
432
493
 
494
+ // Save user message to DB if session already exists (resumed session).
495
+ // For new sessions, the init handler saves it once sessionId is known.
496
+ let userSaved = false;
497
+ if (sessionId) {
498
+ await Message.save({
499
+ sessionId,
500
+ room,
501
+ sender: "user",
502
+ content: userMessage,
503
+ isFromAgent: false,
504
+ });
505
+ await Session.touch(sessionId);
506
+ userSaved = true;
507
+ messageCount++;
508
+ }
509
+
433
510
  return new Promise<SendResult>((resolve, reject) => {
434
511
  pending = {
435
512
  userMessage,
513
+ userSaved,
436
514
  onStream: callbacks?.onStream || null,
437
515
  onActivity: callbacks?.onActivity || null,
438
516
  accumulatedText: "",
package/src/cli/index.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env bun
2
2
  import { existsSync, mkdirSync } from "fs";
3
- import { isRunning, readPid, runDaemon, startDaemon, stopDaemon } from "../core/daemon";
3
+ import {
4
+ isRunning,
5
+ readPid,
6
+ runDaemon,
7
+ startDaemon,
8
+ stopDaemon,
9
+ } from "../core/daemon";
4
10
  import { getConfig } from "../utils/config";
5
11
  import { localTime } from "../utils/time";
6
12
  import { startRepl } from "../chat/repl";
@@ -29,7 +35,12 @@ try {
29
35
  const command = process.argv[2];
30
36
 
31
37
  // Ensure ~/.niahere/ exists for commands that need it
32
- if (command && !["init", "help", "version", "-v", "--version", "-h", "--help"].includes(command)) {
38
+ if (
39
+ command &&
40
+ !["init", "help", "version", "-v", "--version", "-h", "--help"].includes(
41
+ command,
42
+ )
43
+ ) {
33
44
  mkdirSync(getNiaHome(), { recursive: true });
34
45
  }
35
46
 
@@ -45,7 +56,8 @@ async function awaitStartup(timeout = 60_000): Promise<void> {
45
56
  const expecting = new Set<string>();
46
57
  if (config.channels.enabled) {
47
58
  if (config.channels.telegram.bot_token) expecting.add("telegram");
48
- if (config.channels.slack.bot_token && config.channels.slack.app_token) expecting.add("slack");
59
+ if (config.channels.slack.bot_token && config.channels.slack.app_token)
60
+ expecting.add("slack");
49
61
  }
50
62
  expecting.add("scheduler");
51
63
 
@@ -54,13 +66,19 @@ async function awaitStartup(timeout = 60_000): Promise<void> {
54
66
  const { readFileSync } = await import("fs");
55
67
  const ready = new Set<string>();
56
68
  let logOffset = 0;
57
- try { logOffset = readFileSync(daemonLog, "utf8").length; } catch {}
69
+ try {
70
+ logOffset = readFileSync(daemonLog, "utf8").length;
71
+ } catch {}
58
72
 
59
73
  const startTime = Date.now();
60
74
  while (ready.size < expecting.size && Date.now() - startTime < timeout) {
61
75
  await new Promise((r) => setTimeout(r, 500));
62
76
  let content = "";
63
- try { content = readFileSync(daemonLog, "utf8").slice(logOffset); } catch { continue; }
77
+ try {
78
+ content = readFileSync(daemonLog, "utf8").slice(logOffset);
79
+ } catch {
80
+ continue;
81
+ }
64
82
 
65
83
  for (const name of expecting) {
66
84
  if (ready.has(name)) continue;
@@ -125,7 +143,8 @@ switch (command) {
125
143
  }
126
144
 
127
145
  case "restart": {
128
- const { isServiceInstalled, restartService } = await import("../commands/service");
146
+ const { isServiceInstalled, restartService } =
147
+ await import("../commands/service");
129
148
  if (isServiceInstalled()) {
130
149
  // Service-aware: unload (stops KeepAlive respawn), kill, then reload
131
150
  await restartService();
@@ -134,7 +153,9 @@ switch (command) {
134
153
  startDaemon();
135
154
  }
136
155
  const restartPid = readPid();
137
- console.log(`nia restarting${restartPid ? ` (pid: ${restartPid})` : ""}...`);
156
+ console.log(
157
+ `nia restarting${restartPid ? ` (pid: ${restartPid})` : ""}...`,
158
+ );
138
159
  await awaitStartup();
139
160
  console.log("nia restarted");
140
161
  break;
@@ -145,7 +166,12 @@ switch (command) {
145
166
  if (prompt) {
146
167
  const { createChatEngine } = await import("../chat/engine");
147
168
  const { getMcpServers } = await import("../mcp");
148
- const { DIM, RESET: RST, CLEAR_LINE, SPINNER: FRAMES } = await import("../utils/cli");
169
+ const {
170
+ DIM,
171
+ RESET: RST,
172
+ CLEAR_LINE,
173
+ SPINNER: FRAMES,
174
+ } = await import("../utils/cli");
149
175
  let frame = 0;
150
176
  let statusText = "thinking";
151
177
  let spinTimer: ReturnType<typeof setInterval> | null = null;
@@ -153,31 +179,47 @@ switch (command) {
153
179
  let streaming = false;
154
180
 
155
181
  const renderSpinner = () => {
156
- process.stderr.write(`${CLEAR_LINE}${DIM} ${FRAMES[frame]} ${statusText}${RST}`);
182
+ process.stderr.write(
183
+ `${CLEAR_LINE}${DIM} ${FRAMES[frame]} ${statusText}${RST}`,
184
+ );
157
185
  frame = (frame + 1) % FRAMES.length;
158
186
  };
159
187
 
160
188
  await withDb(async () => {
161
- const engine = await createChatEngine({ room: "cli-run", channel: "terminal", resume: false, mcpServers: getMcpServers() });
189
+ const engine = await createChatEngine({
190
+ room: "cli-run",
191
+ channel: "terminal",
192
+ resume: false,
193
+ mcpServers: getMcpServers(),
194
+ });
162
195
  spinTimer = setInterval(renderSpinner, 80);
163
196
  renderSpinner();
164
197
 
165
198
  const { result, costUsd, turns } = await engine.send(prompt, {
166
199
  onStream(textSoFar) {
167
200
  if (!streaming) {
168
- if (spinTimer) { clearInterval(spinTimer); spinTimer = null; }
201
+ if (spinTimer) {
202
+ clearInterval(spinTimer);
203
+ spinTimer = null;
204
+ }
169
205
  process.stderr.write("\x1b[2K\r");
170
206
  streaming = true;
171
207
  }
172
208
  const chunk = textSoFar.slice(streamedLen);
173
- if (chunk) { process.stdout.write(chunk); streamedLen = textSoFar.length; }
209
+ if (chunk) {
210
+ process.stdout.write(chunk);
211
+ streamedLen = textSoFar.length;
212
+ }
174
213
  },
175
214
  onActivity(text) {
176
215
  if (!streaming) statusText = text;
177
216
  },
178
217
  });
179
218
 
180
- if (spinTimer) { clearInterval(spinTimer); spinTimer = null; }
219
+ if (spinTimer) {
220
+ clearInterval(spinTimer);
221
+ spinTimer = null;
222
+ }
181
223
 
182
224
  if (!streaming && result.trim()) {
183
225
  process.stderr.write("\x1b[2K\r");
@@ -190,13 +232,15 @@ switch (command) {
190
232
  }
191
233
 
192
234
  const costStr = costUsd > 0 ? `$${costUsd.toFixed(4)}` : "";
193
- const turnsStr = turns > 0 ? `${turns} turn${turns !== 1 ? "s" : ""}` : "";
235
+ const turnsStr =
236
+ turns > 0 ? `${turns} turn${turns !== 1 ? "s" : ""}` : "";
194
237
  const meta = [costStr, turnsStr].filter(Boolean).join(" · ");
195
238
  if (meta) process.stderr.write(`\n${DIM}${meta}${RST}`);
196
239
  process.stdout.write("\n");
197
240
 
198
241
  engine.close();
199
242
  });
243
+ process.exit(0);
200
244
  } else {
201
245
  await runDaemon();
202
246
  }
@@ -235,8 +279,13 @@ switch (command) {
235
279
  const time = localTime(new Date(m.createdAt));
236
280
  const prefix = m.sender === "user" ? "you" : m.sender;
237
281
  const roomTag = room ? "" : `[${m.room}] `;
238
- const snippet = m.content.length > 120 ? m.content.slice(0, 120) + "..." : m.content;
239
- console.log(` ${roomTag}${time} ${prefix} > ${snippet.replace(/\n/g, " ")}`);
282
+ const snippet =
283
+ m.content.length > 120
284
+ ? m.content.slice(0, 120) + "..."
285
+ : m.content;
286
+ console.log(
287
+ ` ${roomTag}${time} ${prefix} > ${snippet.replace(/\n/g, " ")}`,
288
+ );
240
289
  }
241
290
  }
242
291
  });
@@ -253,16 +302,25 @@ switch (command) {
253
302
  const follow = logArgs.includes("-f") || logArgs.includes("--follow");
254
303
  // --channel <name> filters logs by channel/component via grep
255
304
  const chIdx = logArgs.indexOf("--channel");
256
- const channelFilter = chIdx !== -1 && logArgs[chIdx + 1] ? logArgs[chIdx + 1] : null;
305
+ const channelFilter =
306
+ chIdx !== -1 && logArgs[chIdx + 1] ? logArgs[chIdx + 1] : null;
257
307
 
258
308
  if (channelFilter) {
259
309
  // Pipe through grep to filter by channel name in structured logs
260
- const tailArgs = follow ? ["tail", "-f", daemonLog] : ["tail", "-200", daemonLog];
261
- const tail = Bun.spawn(tailArgs, { stdio: ["ignore", "pipe", "inherit"] });
262
- const grep = Bun.spawn(["grep", "-i", channelFilter], { stdio: [tail.stdout, "inherit", "inherit"] });
310
+ const tailArgs = follow
311
+ ? ["tail", "-f", daemonLog]
312
+ : ["tail", "-200", daemonLog];
313
+ const tail = Bun.spawn(tailArgs, {
314
+ stdio: ["ignore", "pipe", "inherit"],
315
+ });
316
+ const grep = Bun.spawn(["grep", "-i", channelFilter], {
317
+ stdio: [tail.stdout, "inherit", "inherit"],
318
+ });
263
319
  await grep.exited;
264
320
  } else {
265
- const args = follow ? ["tail", "-f", daemonLog] : ["tail", "-50", daemonLog];
321
+ const args = follow
322
+ ? ["tail", "-f", daemonLog]
323
+ : ["tail", "-50", daemonLog];
266
324
  const proc = Bun.spawn(args, { stdio: ["ignore", "inherit", "inherit"] });
267
325
  await proc.exited;
268
326
  }
@@ -276,13 +334,15 @@ switch (command) {
276
334
 
277
335
  case "chat": {
278
336
  const chatArgs = process.argv.slice(3);
279
- const mode = (chatArgs.includes("--continue") || chatArgs.includes("-c"))
280
- ? "continue" as const
281
- : (chatArgs.includes("--resume") || chatArgs.includes("-r"))
282
- ? "pick" as const
283
- : "new" as const;
337
+ const mode =
338
+ chatArgs.includes("--continue") || chatArgs.includes("-c")
339
+ ? ("continue" as const)
340
+ : chatArgs.includes("--resume") || chatArgs.includes("-r")
341
+ ? ("pick" as const)
342
+ : ("new" as const);
284
343
  const chIdx = chatArgs.indexOf("--channel");
285
- const simChannel = chIdx !== -1 && chatArgs[chIdx + 1] ? chatArgs[chIdx + 1] : undefined;
344
+ const simChannel =
345
+ chIdx !== -1 && chatArgs[chIdx + 1] ? chatArgs[chIdx + 1] : undefined;
286
346
  await startRepl(mode, simChannel);
287
347
  break;
288
348
  }
@@ -300,7 +360,9 @@ switch (command) {
300
360
  skills = skills.filter((s) => s.source === filter);
301
361
  }
302
362
  if (skills.length === 0) {
303
- console.log(filter ? `No skills found in "${filter}".` : "No skills found.");
363
+ console.log(
364
+ filter ? `No skills found in "${filter}".` : "No skills found.",
365
+ );
304
366
  } else {
305
367
  for (const s of skills) {
306
368
  const tag = filter ? "" : ` [${s.source}]`;
@@ -354,8 +416,12 @@ switch (command) {
354
416
  const parts = configKey.split(".");
355
417
  let val: unknown = raw;
356
418
  for (const p of parts) {
357
- if (val && typeof val === "object") val = (val as Record<string, unknown>)[p];
358
- else { val = undefined; break; }
419
+ if (val && typeof val === "object")
420
+ val = (val as Record<string, unknown>)[p];
421
+ else {
422
+ val = undefined;
423
+ break;
424
+ }
359
425
  }
360
426
  if (val === undefined) {
361
427
  console.log(`${configKey}: (not set)`);
@@ -389,7 +455,9 @@ switch (command) {
389
455
  process.kill(pid, "SIGHUP");
390
456
  console.log(`channels ${enabled ? "enabled" : "disabled"}`);
391
457
  } else {
392
- console.log(`channels ${enabled ? "enabled" : "disabled"} — start nia to apply`);
458
+ console.log(
459
+ `channels ${enabled ? "enabled" : "disabled"} — start nia to apply`,
460
+ );
393
461
  }
394
462
  } else {
395
463
  console.log(`channels: ${getConfig().channels.enabled ? "on" : "off"}`);
@@ -404,8 +472,11 @@ switch (command) {
404
472
  }
405
473
 
406
474
  case "test": {
407
- const verbose = process.argv.includes("-v") || process.argv.includes("--verbose");
408
- const extraArgs = process.argv.slice(3).filter((a) => a !== "-v" && a !== "--verbose");
475
+ const verbose =
476
+ process.argv.includes("-v") || process.argv.includes("--verbose");
477
+ const extraArgs = process.argv
478
+ .slice(3)
479
+ .filter((a) => a !== "-v" && a !== "--verbose");
409
480
  const proc = Bun.spawn(["bun", "test", ...extraArgs], {
410
481
  stdio: ["ignore", "pipe", "pipe"],
411
482
  cwd: import.meta.dir + "/../..",
@@ -423,7 +494,12 @@ switch (command) {
423
494
  process.stdout.write(output);
424
495
  } else {
425
496
  for (const line of output.split("\n")) {
426
- if (/^\s*\d+ pass/.test(line) || /^\s*\d+ fail/.test(line) || /^Ran \d+ tests/.test(line) || /expect\(\) calls/.test(line)) {
497
+ if (
498
+ /^\s*\d+ pass/.test(line) ||
499
+ /^\s*\d+ fail/.test(line) ||
500
+ /^Ran \d+ tests/.test(line) ||
501
+ /expect\(\) calls/.test(line)
502
+ ) {
427
503
  console.log(line);
428
504
  } else if (/^✗|FAIL|error:/i.test(line.trim())) {
429
505
  console.log(line);
@@ -460,13 +536,18 @@ switch (command) {
460
536
  console.log(`⚠ backup skipped: ${errMsg(err)}`);
461
537
  }
462
538
  console.log("Updating...");
463
- const install = Bun.spawn(["npm", "i", "-g", "niahere@latest"], { stdio: ["ignore", "inherit", "inherit"] });
539
+ const install = Bun.spawn(["npm", "i", "-g", "niahere@latest"], {
540
+ stdio: ["ignore", "inherit", "inherit"],
541
+ });
464
542
  const installExit = await install.exited;
465
543
  if (installExit !== 0) {
466
544
  fail("Update failed.");
467
545
  }
468
546
  // Get new version
469
- const check = Bun.spawn(["npm", "view", "niahere", "version"], { stdout: "pipe", stderr: "pipe" });
547
+ const check = Bun.spawn(["npm", "view", "niahere", "version"], {
548
+ stdout: "pipe",
549
+ stderr: "pipe",
550
+ });
470
551
  const newVersion = (await new Response(check.stdout).text()).trim();
471
552
  await check.exited;
472
553
  if (newVersion === currentVersion) {
@@ -475,7 +556,8 @@ switch (command) {
475
556
  console.log(`Updated: v${currentVersion} → v${newVersion}`);
476
557
  if (isRunning()) {
477
558
  console.log("Restarting daemon...");
478
- const { isServiceInstalled, restartService } = await import("../commands/service");
559
+ const { isServiceInstalled, restartService } =
560
+ await import("../commands/service");
479
561
  if (isServiceInstalled()) {
480
562
  await restartService();
481
563
  } else {
@@ -540,7 +622,11 @@ System:
540
622
 
541
623
  console.log(HELP);
542
624
  // Unknown command → exit 1, help/no command → exit 0
543
- const isHelp = !command || command === "help" || command === "--help" || command === "-h";
625
+ const isHelp =
626
+ !command ||
627
+ command === "help" ||
628
+ command === "--help" ||
629
+ command === "-h";
544
630
  if (!isHelp) console.error(`\nUnknown command: ${command}`);
545
631
  process.exit(isHelp ? 0 : 1);
546
632
  }