niahere 0.2.13 → 0.2.14
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 +7 -2
- package/package.json +1 -1
- package/skills/github-link-repo-explorer/SKILL.md +104 -0
- package/skills/image-generation/SKILL.md +121 -0
- package/skills/image-generation/scripts/generate_image.py +401 -0
- package/skills/llms-txt/SKILL.md +141 -0
- package/skills/pr-reviewer/SKILL.md +187 -0
- package/src/chat/engine.ts +73 -10
- package/src/chat/repl.ts +176 -11
- package/src/cli/index.ts +112 -5
- package/src/db/models/session.ts +38 -0
- package/src/types/engine.ts +2 -1
package/src/cli/index.ts
CHANGED
|
@@ -136,10 +136,58 @@ switch (command) {
|
|
|
136
136
|
if (prompt) {
|
|
137
137
|
const { createChatEngine } = await import("../chat/engine");
|
|
138
138
|
const { getMcpServers } = await import("../mcp");
|
|
139
|
+
const DIM = "\x1b[2m";
|
|
140
|
+
const RST = "\x1b[0m";
|
|
141
|
+
const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
142
|
+
let frame = 0;
|
|
143
|
+
let statusText = "thinking";
|
|
144
|
+
let spinTimer: ReturnType<typeof setInterval> | null = null;
|
|
145
|
+
let streamedLen = 0;
|
|
146
|
+
let streaming = false;
|
|
147
|
+
|
|
148
|
+
const renderSpinner = () => {
|
|
149
|
+
process.stderr.write(`\x1b[2K\r${DIM} ${FRAMES[frame]} ${statusText}${RST}`);
|
|
150
|
+
frame = (frame + 1) % FRAMES.length;
|
|
151
|
+
};
|
|
152
|
+
|
|
139
153
|
await withDb(async () => {
|
|
140
154
|
const engine = await createChatEngine({ room: "cli-run", channel: "terminal", resume: false, mcpServers: getMcpServers() });
|
|
141
|
-
|
|
142
|
-
|
|
155
|
+
spinTimer = setInterval(renderSpinner, 80);
|
|
156
|
+
renderSpinner();
|
|
157
|
+
|
|
158
|
+
const { result, costUsd, turns } = await engine.send(prompt, {
|
|
159
|
+
onStream(textSoFar) {
|
|
160
|
+
if (!streaming) {
|
|
161
|
+
if (spinTimer) { clearInterval(spinTimer); spinTimer = null; }
|
|
162
|
+
process.stderr.write("\x1b[2K\r");
|
|
163
|
+
streaming = true;
|
|
164
|
+
}
|
|
165
|
+
const chunk = textSoFar.slice(streamedLen);
|
|
166
|
+
if (chunk) { process.stdout.write(chunk); streamedLen = textSoFar.length; }
|
|
167
|
+
},
|
|
168
|
+
onActivity(text) {
|
|
169
|
+
if (!streaming) statusText = text;
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (spinTimer) { clearInterval(spinTimer); spinTimer = null; }
|
|
174
|
+
|
|
175
|
+
if (!streaming && result.trim()) {
|
|
176
|
+
process.stderr.write("\x1b[2K\r");
|
|
177
|
+
process.stdout.write(result.trim());
|
|
178
|
+
} else if (streaming) {
|
|
179
|
+
const rest = result.slice(streamedLen);
|
|
180
|
+
if (rest.trim()) process.stdout.write(rest);
|
|
181
|
+
} else {
|
|
182
|
+
process.stderr.write("\x1b[2K\r");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const costStr = costUsd > 0 ? `$${costUsd.toFixed(4)}` : "";
|
|
186
|
+
const turnsStr = turns > 0 ? `${turns} turn${turns !== 1 ? "s" : ""}` : "";
|
|
187
|
+
const meta = [costStr, turnsStr].filter(Boolean).join(" · ");
|
|
188
|
+
if (meta) process.stderr.write(`\n${DIM}${meta}${RST}`);
|
|
189
|
+
process.stdout.write("\n");
|
|
190
|
+
|
|
143
191
|
engine.close();
|
|
144
192
|
});
|
|
145
193
|
} else {
|
|
@@ -192,8 +240,13 @@ switch (command) {
|
|
|
192
240
|
}
|
|
193
241
|
|
|
194
242
|
case "chat": {
|
|
195
|
-
const
|
|
196
|
-
|
|
243
|
+
const arg = process.argv[3];
|
|
244
|
+
const mode = (arg === "--new" || arg === "-n")
|
|
245
|
+
? "new" as const
|
|
246
|
+
: (arg === "--resume" || arg === "-r")
|
|
247
|
+
? "pick" as const
|
|
248
|
+
: "continue" as const;
|
|
249
|
+
await startRepl(mode);
|
|
197
250
|
break;
|
|
198
251
|
}
|
|
199
252
|
|
|
@@ -223,6 +276,59 @@ switch (command) {
|
|
|
223
276
|
break;
|
|
224
277
|
}
|
|
225
278
|
|
|
279
|
+
case "config": {
|
|
280
|
+
const configSub = process.argv[3];
|
|
281
|
+
const configKey = process.argv[4];
|
|
282
|
+
const configVal = process.argv.slice(5).join(" ");
|
|
283
|
+
const { readRawConfig, updateRawConfig } = await import("../utils/config");
|
|
284
|
+
|
|
285
|
+
if (configSub === "set" && configKey) {
|
|
286
|
+
if (!configVal) fail("Usage: nia config set <key> <value>");
|
|
287
|
+
// Support dot notation for nested keys (e.g. channels.default)
|
|
288
|
+
const parts = configKey.split(".");
|
|
289
|
+
let obj: Record<string, unknown> = {};
|
|
290
|
+
let cursor = obj;
|
|
291
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
292
|
+
cursor[parts[i]] = {};
|
|
293
|
+
cursor = cursor[parts[i]] as Record<string, unknown>;
|
|
294
|
+
}
|
|
295
|
+
// Auto-detect booleans and numbers
|
|
296
|
+
let parsed: unknown = configVal;
|
|
297
|
+
if (configVal === "true") parsed = true;
|
|
298
|
+
else if (configVal === "false") parsed = false;
|
|
299
|
+
else if (/^\d+$/.test(configVal)) parsed = Number(configVal);
|
|
300
|
+
cursor[parts[parts.length - 1]] = parsed;
|
|
301
|
+
updateRawConfig(obj);
|
|
302
|
+
console.log(`${configKey} = ${configVal}`);
|
|
303
|
+
} else if (configSub === "get" && configKey) {
|
|
304
|
+
const raw = readRawConfig();
|
|
305
|
+
const parts = configKey.split(".");
|
|
306
|
+
let val: unknown = raw;
|
|
307
|
+
for (const p of parts) {
|
|
308
|
+
if (val && typeof val === "object") val = (val as Record<string, unknown>)[p];
|
|
309
|
+
else { val = undefined; break; }
|
|
310
|
+
}
|
|
311
|
+
if (val === undefined) {
|
|
312
|
+
console.log(`${configKey}: (not set)`);
|
|
313
|
+
} else if (typeof val === "object") {
|
|
314
|
+
const yaml = (await import("js-yaml")).default;
|
|
315
|
+
console.log(yaml.dump(val, { lineWidth: -1 }).trim());
|
|
316
|
+
} else {
|
|
317
|
+
console.log(`${configKey} = ${val}`);
|
|
318
|
+
}
|
|
319
|
+
} else if (!configSub || configSub === "list") {
|
|
320
|
+
const raw = readRawConfig();
|
|
321
|
+
const yaml = (await import("js-yaml")).default;
|
|
322
|
+
console.log(yaml.dump(raw, { lineWidth: -1 }).trim());
|
|
323
|
+
} else {
|
|
324
|
+
console.log("Usage: nia config <set|get|list>");
|
|
325
|
+
console.log(" nia config set <key> <value> — set a config value");
|
|
326
|
+
console.log(" nia config get <key> — get a config value");
|
|
327
|
+
console.log(" nia config list — show all config");
|
|
328
|
+
}
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
|
|
226
332
|
case "channels": {
|
|
227
333
|
const sub = process.argv[3];
|
|
228
334
|
const { updateRawConfig } = await import("../utils/config");
|
|
@@ -286,13 +392,14 @@ switch (command) {
|
|
|
286
392
|
console.log(" start / stop — daemon + service control");
|
|
287
393
|
console.log(" restart — restart daemon");
|
|
288
394
|
console.log(" status [--json --rooms N --all] — show daemon, jobs, channels");
|
|
289
|
-
console.log(" chat
|
|
395
|
+
console.log(" chat — interactive chat (auto-continues last session)");
|
|
290
396
|
console.log(" run <prompt> — one-shot execution");
|
|
291
397
|
console.log(" history [room] — recent messages");
|
|
292
398
|
console.log(" logs [-f] — daemon logs");
|
|
293
399
|
console.log(" job <sub> — manage jobs");
|
|
294
400
|
console.log(" db <sub> — database setup/status/migrate");
|
|
295
401
|
console.log(" skills — list available skills");
|
|
402
|
+
console.log(" config <sub> — get/set/list config values");
|
|
296
403
|
console.log(" send [-c ch] <msg> — send a message via channel");
|
|
297
404
|
console.log(" telegram <token> — configure telegram");
|
|
298
405
|
console.log(" slack <bot> <app> — configure slack");
|
package/src/db/models/session.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { getSql } from "../connection";
|
|
2
2
|
|
|
3
|
+
export interface SessionSummary {
|
|
4
|
+
id: string;
|
|
5
|
+
room: string;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
updatedAt: string;
|
|
8
|
+
preview: string | null;
|
|
9
|
+
messageCount: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
export async function getLatest(room: string): Promise<string | null> {
|
|
4
13
|
const sql = getSql();
|
|
5
14
|
const rows = await sql`
|
|
@@ -11,6 +20,35 @@ export async function getLatest(room: string): Promise<string | null> {
|
|
|
11
20
|
return rows.length > 0 ? rows[0].id : null;
|
|
12
21
|
}
|
|
13
22
|
|
|
23
|
+
export async function getRecent(room: string, limit = 10): Promise<SessionSummary[]> {
|
|
24
|
+
const sql = getSql();
|
|
25
|
+
const rows = await sql`
|
|
26
|
+
SELECT
|
|
27
|
+
s.id,
|
|
28
|
+
s.room,
|
|
29
|
+
s.created_at,
|
|
30
|
+
s.updated_at,
|
|
31
|
+
(
|
|
32
|
+
SELECT content FROM messages m
|
|
33
|
+
WHERE m.session_id = s.id AND m.sender = 'user'
|
|
34
|
+
ORDER BY m.created_at ASC LIMIT 1
|
|
35
|
+
) AS preview,
|
|
36
|
+
(SELECT COUNT(*)::int FROM messages m WHERE m.session_id = s.id) AS message_count
|
|
37
|
+
FROM sessions s
|
|
38
|
+
WHERE s.room = ${room}
|
|
39
|
+
ORDER BY s.updated_at DESC
|
|
40
|
+
LIMIT ${limit}
|
|
41
|
+
`;
|
|
42
|
+
return rows.map((r) => ({
|
|
43
|
+
id: r.id,
|
|
44
|
+
room: r.room,
|
|
45
|
+
createdAt: String(r.created_at),
|
|
46
|
+
updatedAt: String(r.updated_at),
|
|
47
|
+
preview: r.preview ? String(r.preview) : null,
|
|
48
|
+
messageCount: r.message_count,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
14
52
|
export async function create(id: string, room: string): Promise<void> {
|
|
15
53
|
const sql = getSql();
|
|
16
54
|
await sql`INSERT INTO sessions (id, room) VALUES (${id}, ${room})`;
|
package/src/types/engine.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface ChatEngine {
|
|
|
22
22
|
export interface EngineOptions {
|
|
23
23
|
room: string;
|
|
24
24
|
channel: string;
|
|
25
|
-
resume
|
|
25
|
+
/** true = resume latest session, or pass a specific session ID */
|
|
26
|
+
resume: boolean | string;
|
|
26
27
|
mcpServers?: Record<string, unknown>;
|
|
27
28
|
}
|