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.
- package/README.md +9 -3
- package/package.json +2 -2
- package/skills/beads-tasks/SKILL.md +91 -13
- package/skills/optimization-loop/SKILL.md +230 -0
- package/skills/optimize/SKILL.md +238 -0
- package/src/chat/engine.ts +95 -17
- package/src/cli/index.ts +124 -38
- package/src/cli/job.ts +160 -43
- package/src/commands/backup.ts +26 -4
- package/src/core/agents.ts +22 -8
- package/src/core/consolidator.ts +52 -14
- package/src/core/health.ts +92 -23
- package/src/core/runner.ts +74 -15
- package/src/core/skills.ts +18 -6
- package/src/core/summarizer.ts +33 -8
- package/src/db/migrations/013_jobs_model.ts +7 -0
- package/src/db/models/active_engine.ts +5 -3
- package/src/db/models/job.ts +40 -13
- package/src/mcp/server.ts +202 -37
- package/src/mcp/tools.ts +116 -29
- package/src/types/audit.ts +1 -0
- package/src/types/job.ts +2 -0
- package/src/utils/retry.ts +18 -0
package/src/chat/engine.ts
CHANGED
|
@@ -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 {
|
|
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(
|
|
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<
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 (
|
|
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({
|
|
406
|
+
messageId = await Message.save({
|
|
407
|
+
...saveParams,
|
|
408
|
+
metadata: undefined,
|
|
409
|
+
});
|
|
368
410
|
}
|
|
369
411
|
await Session.touch(sessionId);
|
|
370
|
-
Session.accumulateMetadata(sessionId, {
|
|
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({
|
|
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(
|
|
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(
|
|
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(
|
|
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 {
|
|
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 (
|
|
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)
|
|
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 {
|
|
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 {
|
|
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 } =
|
|
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(
|
|
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 {
|
|
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(
|
|
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({
|
|
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) {
|
|
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) {
|
|
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) {
|
|
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 =
|
|
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 =
|
|
239
|
-
|
|
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 =
|
|
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
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
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 =
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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 =
|
|
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(
|
|
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")
|
|
358
|
-
|
|
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(
|
|
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 =
|
|
408
|
-
|
|
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 (
|
|
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"], {
|
|
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"], {
|
|
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 } =
|
|
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 =
|
|
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
|
}
|