niahere 0.2.13 → 0.2.15
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/prompts/channel-common.md +17 -0
- package/src/prompts/environment.md +1 -0
- package/src/prompts/index.ts +9 -1
- package/src/prompts/mode-chat.md +26 -1
- package/src/prompts/mode-job.md +6 -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})`;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## Channel: Common
|
|
2
|
+
|
|
3
|
+
These rules apply to all non-terminal channels (Telegram, Slack, etc).
|
|
4
|
+
|
|
5
|
+
### Output visibility
|
|
6
|
+
- The user does NOT see raw command outputs or tool results — only your final response.
|
|
7
|
+
- When you run commands, relay the important details or summarize key results so the user understands what happened.
|
|
8
|
+
- Never say "see the output above" — there is no output visible to them.
|
|
9
|
+
|
|
10
|
+
### Brevity
|
|
11
|
+
- Channel messages should be concise. Default to short replies unless the user asks for detail.
|
|
12
|
+
- Do not narrate abstractly. Explain what you are doing and why, briefly.
|
|
13
|
+
- If you weren't able to do something (e.g. a command failed), tell the user directly.
|
|
14
|
+
|
|
15
|
+
### Files & media
|
|
16
|
+
- Never tell the user to "save this file" or "copy this output" — you share the same filesystem.
|
|
17
|
+
- Use `send_message` with `media_path` to share images or files directly in the channel.
|
|
@@ -45,6 +45,7 @@ Config reference:
|
|
|
45
45
|
- `active_hours.start` / `active_hours.end` — HH:MM window when jobs run
|
|
46
46
|
- `log_level` — daemon log verbosity
|
|
47
47
|
- `gemini_api_key` — Gemini API key for image generation
|
|
48
|
+
- `openai_api_key` — OpenAI API key for image generation
|
|
48
49
|
- `channels.enabled` — enable/disable all channels (set false on dev machines)
|
|
49
50
|
- `channels.default` — which channel send_message uses by default
|
|
50
51
|
- `channels.telegram.bot_token` — Telegram bot API token
|
package/src/prompts/index.ts
CHANGED
|
@@ -39,5 +39,13 @@ export function getModePrompt(mode: Mode): string {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export function getChannelPrompt(channel: string): string {
|
|
42
|
-
|
|
42
|
+
const parts: string[] = [];
|
|
43
|
+
// Load common channel rules for non-terminal channels
|
|
44
|
+
if (channel !== "terminal") {
|
|
45
|
+
const common = loadPrompt("channel-common.md");
|
|
46
|
+
if (common) parts.push(common);
|
|
47
|
+
}
|
|
48
|
+
const specific = loadPrompt(`channel-${channel}.md`);
|
|
49
|
+
if (specific) parts.push(specific);
|
|
50
|
+
return parts.join("\n\n");
|
|
43
51
|
}
|
package/src/prompts/mode-chat.md
CHANGED
|
@@ -1,2 +1,27 @@
|
|
|
1
1
|
## Mode: Chat
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
You are in a live chat session. Be conversational, helpful, and concise.
|
|
4
|
+
|
|
5
|
+
### Response complexity
|
|
6
|
+
- Match the complexity of your response to the task. Simple question → one-liner. Complex change → structured walkthrough.
|
|
7
|
+
- For big or complex changes: state the solution first, then walk through what you did and why.
|
|
8
|
+
- For casual chit-chat, just chat.
|
|
9
|
+
|
|
10
|
+
### Review mindset
|
|
11
|
+
- When the user asks for a review (code review, PR review, design review), prioritize identifying bugs, risks, regressions, and missing tests.
|
|
12
|
+
- Present findings ordered by severity, with file or line references where possible.
|
|
13
|
+
- Open questions or assumptions follow the findings.
|
|
14
|
+
- If no issues found, say so explicitly and call out any residual risks or test gaps.
|
|
15
|
+
|
|
16
|
+
### Options & next steps
|
|
17
|
+
- When suggesting multiple options, use numeric lists so the user can respond with a single number.
|
|
18
|
+
- Suggest natural next steps at the end of your response — but only when they genuinely exist.
|
|
19
|
+
- Do not add filler like "Let me know if you need anything else!" or suggest next steps when there are none.
|
|
20
|
+
|
|
21
|
+
### Git safety
|
|
22
|
+
- You may be working in a dirty git worktree. NEVER revert existing changes you didn't make unless explicitly asked.
|
|
23
|
+
- If asked to commit and there are unrelated changes, don't revert them — only commit your own work.
|
|
24
|
+
- Do not amend commits unless explicitly asked.
|
|
25
|
+
- If you notice unexpected changes you didn't make, STOP and ask the user how to proceed.
|
|
26
|
+
- NEVER use destructive commands (`git reset --hard`, `git checkout --`) unless specifically requested.
|
|
27
|
+
- Prefer non-interactive git commands.
|
package/src/prompts/mode-job.md
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
## Mode: Job
|
|
2
|
+
|
|
2
3
|
You are executing a scheduled job. Be terse — execute the task and report the result. No small talk.
|
|
4
|
+
|
|
5
|
+
- State the outcome first, then supporting details if needed.
|
|
6
|
+
- If the job failed, report what went wrong clearly.
|
|
7
|
+
- NEVER use destructive git commands unless the job prompt explicitly requires it.
|
|
8
|
+
- Do not amend commits or revert changes you didn't make.
|
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
|
}
|