niahere 0.2.31 → 0.2.32

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niahere",
3
- "version": "0.2.31",
3
+ "version": "0.2.32",
4
4
  "description": "A personal AI assistant daemon — scheduled jobs, chat across Telegram and Slack, persona system, and visual identity.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,14 +1,12 @@
1
- import { existsSync, readFileSync, readdirSync } from "fs";
2
- import { join, resolve } from "path";
3
- import { homedir } from "os";
4
- import yaml from "js-yaml";
5
- import { getNiaHome, getPaths } from "../utils/paths";
1
+ import { existsSync, readFileSync } from "fs";
2
+ import { join } from "path";
3
+ import { getPaths } from "../utils/paths";
6
4
  import { getEnvironmentPrompt, getModePrompt, getChannelPrompt } from "../prompts";
7
- import { log } from "../utils/log";
5
+ import { getSkillsSummary } from "../core/skills";
8
6
  import type { Mode } from "../types";
9
7
 
10
- // niahere project root (resolved from this file's location)
11
- const PROJECT_ROOT = resolve(import.meta.dir, "../..");
8
+ // Re-export for backwards compat
9
+ export { scanSkills as loadSkills, getSkillNames as loadSkillNames, type SkillInfo } from "../core/skills";
12
10
 
13
11
  function loadFile(dir: string, name: string): string {
14
12
  const filePath = join(dir, name);
@@ -22,71 +20,6 @@ export function loadIdentity(): string {
22
20
  return files.map((f) => loadFile(selfDir, f)).filter(Boolean).join("\n\n");
23
21
  }
24
22
 
25
- function scanSkills(): { name: string; description: string }[] {
26
- const home = homedir();
27
- const cwd = process.cwd();
28
- const niaHome = getNiaHome();
29
- const skillDirs = [
30
- join(cwd, "skills"),
31
- join(PROJECT_ROOT, "skills"),
32
- join(niaHome, "skills"),
33
- join(home, ".shared", "skills"),
34
- join(home, ".claude", "skills"),
35
- join(home, ".codex", "skills"),
36
- ];
37
-
38
- const skills: { name: string; description: string }[] = [];
39
- const seen = new Set<string>();
40
-
41
- for (const dir of skillDirs) {
42
- if (!existsSync(dir)) continue;
43
-
44
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
45
- if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
46
-
47
- const skillFile = join(dir, entry.name, "SKILL.md");
48
- if (!existsSync(skillFile)) continue;
49
-
50
- const content = readFileSync(skillFile, "utf8");
51
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
52
- if (!fmMatch) continue;
53
-
54
- let meta: Record<string, unknown> = {};
55
- try {
56
- meta = (yaml.load(fmMatch[1]) as Record<string, unknown>) || {};
57
- } catch (err) {
58
- log.warn({ err, skill: entry.name, path: skillFile }, "failed to parse skill metadata, skipping");
59
- continue;
60
- }
61
- const name = (typeof meta.name === "string" ? meta.name : "") || entry.name;
62
-
63
- if (seen.has(name)) continue;
64
- seen.add(name);
65
-
66
- skills.push({
67
- name,
68
- description: typeof meta.description === "string" ? meta.description : "",
69
- });
70
- }
71
- }
72
-
73
- return skills;
74
- }
75
-
76
- export function loadSkillNames(): string[] {
77
- return scanSkills().map((s) => s.name);
78
- }
79
-
80
- export function loadSkillsSummary(): string {
81
- const skills = scanSkills();
82
- if (skills.length === 0) return "";
83
-
84
- const lines = skills.map((s) =>
85
- s.description ? `- /${s.name}: ${s.description}` : `- /${s.name}`,
86
- );
87
- return `Available skills:\n${lines.join("\n")}`;
88
- }
89
-
90
23
  export function buildSystemPrompt(mode: Mode = "chat", channel: string = "terminal"): string {
91
24
  const parts: string[] = [];
92
25
 
@@ -101,7 +34,7 @@ export function buildSystemPrompt(mode: Mode = "chat", channel: string = "termin
101
34
  const channelPrompt = getChannelPrompt(channel);
102
35
  if (channelPrompt) parts.push(channelPrompt);
103
36
 
104
- const skills = loadSkillsSummary();
37
+ const skills = getSkillsSummary();
105
38
  if (skills) parts.push(skills);
106
39
 
107
40
  return parts.join("\n\n");
package/src/cli/index.ts CHANGED
@@ -287,12 +287,19 @@ switch (command) {
287
287
  }
288
288
 
289
289
  case "skills": {
290
- const { loadSkillNames } = await import("../chat/identity");
291
- const names = loadSkillNames();
292
- if (names.length === 0) {
293
- console.log("No skills found.");
290
+ const { scanSkills: loadSkills } = await import("../core/skills");
291
+ const filter = process.argv[3]; // e.g. "project", "nia", "shared", "claude"
292
+ let skills = loadSkills();
293
+ if (filter) {
294
+ skills = skills.filter((s) => s.source === filter);
295
+ }
296
+ if (skills.length === 0) {
297
+ console.log(filter ? `No skills found in "${filter}".` : "No skills found.");
294
298
  } else {
295
- for (const name of names) console.log(` ${name}`);
299
+ for (const s of skills) {
300
+ const tag = filter ? "" : ` [${s.source}]`;
301
+ console.log(` ${s.name}${tag}`);
302
+ }
296
303
  }
297
304
  break;
298
305
  }
@@ -0,0 +1,67 @@
1
+ import { existsSync, readFileSync, readdirSync } from "fs";
2
+ import { join, resolve } from "path";
3
+ import { homedir } from "os";
4
+ import yaml from "js-yaml";
5
+ import { getNiaHome } from "../utils/paths";
6
+ import { log } from "../utils/log";
7
+
8
+ // niahere project root (resolved from this file's location)
9
+ const PROJECT_ROOT = resolve(import.meta.dir, "../..");
10
+
11
+ export type SkillInfo = { name: string; description: string; source: string };
12
+
13
+ const SKILL_DIRS: { dir: string; source: string }[] = [
14
+ { dir: join(process.cwd(), "skills"), source: "cwd" },
15
+ { dir: join(PROJECT_ROOT, "skills"), source: "project" },
16
+ { dir: join(getNiaHome(), "skills"), source: "nia" },
17
+ { dir: join(homedir(), ".shared", "skills"), source: "shared" },
18
+ { dir: join(homedir(), ".claude", "skills"), source: "claude" },
19
+ { dir: join(homedir(), ".codex", "skills"), source: "codex" },
20
+ ];
21
+
22
+ export function scanSkills(): SkillInfo[] {
23
+ const skills: SkillInfo[] = [];
24
+ const seen = new Set<string>();
25
+
26
+ for (const { dir, source } of SKILL_DIRS) {
27
+ if (!existsSync(dir)) continue;
28
+
29
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
30
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
31
+
32
+ const skillFile = join(dir, entry.name, "SKILL.md");
33
+ if (!existsSync(skillFile)) continue;
34
+
35
+ const content = readFileSync(skillFile, "utf8");
36
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
37
+ if (!fmMatch) continue;
38
+
39
+ let meta: Record<string, unknown> = {};
40
+ try {
41
+ meta = (yaml.load(fmMatch[1]) as Record<string, unknown>) || {};
42
+ } catch (err) {
43
+ log.warn({ err, skill: entry.name, path: skillFile }, "failed to parse skill metadata, skipping");
44
+ continue;
45
+ }
46
+ const name = (typeof meta.name === "string" ? meta.name : "") || entry.name;
47
+
48
+ if (seen.has(name)) continue;
49
+ seen.add(name);
50
+
51
+ skills.push({ name, description: typeof meta.description === "string" ? meta.description : "", source });
52
+ }
53
+ }
54
+
55
+ return skills;
56
+ }
57
+
58
+ export function getSkillNames(): string[] {
59
+ return scanSkills().map((s) => s.name);
60
+ }
61
+
62
+ export function getSkillsSummary(): string {
63
+ const skills = scanSkills();
64
+ if (skills.length === 0) return "";
65
+ const lines = skills.map((s) => s.description ? `- /${s.name}: ${s.description}` : `- /${s.name}`);
66
+ return `Available skills:\n${lines.join("\n")}`;
67
+ }