@oxecli/oxe 1.0.0

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/dist/skills.js ADDED
@@ -0,0 +1,141 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import YAML from "yaml";
4
+ import { SKILLS_DIR } from "./config.js";
5
+ const FRONTMATTER_RE = /^---[ \t]*\r?\n(.*?)\r?\n---[ \t]*\r?\n?([\s\S]*)$/;
6
+ function naturalSortKey(s) {
7
+ return s.replace(/\d+/g, (m) => m.padStart(12, "0")).toLowerCase();
8
+ }
9
+ export function parseSkillFrontmatter(text) {
10
+ const match = FRONTMATTER_RE.exec(text);
11
+ if (!match)
12
+ return [{}, text];
13
+ let parsed;
14
+ try {
15
+ parsed = YAML.parse(match[1]);
16
+ }
17
+ catch {
18
+ return [{}, text];
19
+ }
20
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
21
+ return [{}, text];
22
+ const p = parsed;
23
+ const name = String(p["name"] ?? "").trim();
24
+ const description = String(p["description"] ?? "").trim();
25
+ return [{ name, description }, match[2]];
26
+ }
27
+ function scanSkills() {
28
+ const skills = [];
29
+ const seen = new Set();
30
+ if (!fs.existsSync(SKILLS_DIR) || !fs.statSync(SKILLS_DIR).isDirectory())
31
+ return skills;
32
+ const subs = fs
33
+ .readdirSync(SKILLS_DIR, { withFileTypes: true })
34
+ .filter((d) => d.isDirectory())
35
+ .sort((a, b) => naturalSortKey(a.name).localeCompare(naturalSortKey(b.name)));
36
+ for (const sub of subs) {
37
+ const mdPath = path.join(SKILLS_DIR, sub.name, "SKILL.md");
38
+ if (!fs.existsSync(mdPath))
39
+ continue;
40
+ let text;
41
+ try {
42
+ text = fs.readFileSync(mdPath, "utf-8");
43
+ }
44
+ catch {
45
+ continue;
46
+ }
47
+ const [meta] = parseSkillFrontmatter(text);
48
+ const name = meta["name"];
49
+ if (!name)
50
+ continue;
51
+ if (seen.has(name))
52
+ continue;
53
+ seen.add(name);
54
+ skills.push({ name, description: meta["description"] ?? "", path: mdPath, dir: sub.name });
55
+ }
56
+ return skills;
57
+ }
58
+ let availableSkills = null;
59
+ export function getSkills() {
60
+ if (availableSkills === null)
61
+ availableSkills = scanSkills();
62
+ return availableSkills;
63
+ }
64
+ export function refreshSkills() {
65
+ availableSkills = scanSkills();
66
+ }
67
+ function listSkillAssets(skillDir) {
68
+ const scripts = [];
69
+ const references = [];
70
+ for (const [folder, bucket] of [
71
+ ["scripts", scripts],
72
+ ["references", references],
73
+ ]) {
74
+ const base = path.join(skillDir, folder);
75
+ if (!fs.existsSync(base))
76
+ continue;
77
+ const walk = (dir) => {
78
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
79
+ const full = path.join(dir, entry.name);
80
+ if (entry.isDirectory()) {
81
+ walk(full);
82
+ }
83
+ else if (entry.isFile()) {
84
+ const fname = entry.name;
85
+ if (fname.startsWith(".") ||
86
+ fname.startsWith("~") ||
87
+ /\.(tmp|swp|bak)$/.test(fname)) {
88
+ continue;
89
+ }
90
+ bucket.push(path.resolve(full));
91
+ }
92
+ }
93
+ };
94
+ walk(base);
95
+ bucket.sort((a, b) => naturalSortKey(a).localeCompare(naturalSortKey(b)));
96
+ }
97
+ return { scripts, references };
98
+ }
99
+ export function toolListSkills() {
100
+ const skills = getSkills();
101
+ if (!skills.length)
102
+ return "No skills available.";
103
+ const lines = ["Available skills:"];
104
+ for (const skill of skills) {
105
+ lines.push(skill.description ? `- ${skill.name}: ${skill.description}` : `- ${skill.name}`);
106
+ }
107
+ return lines.join("\n");
108
+ }
109
+ export function toolLoadSkill(skillName) {
110
+ if (typeof skillName !== "string" || !skillName.trim()) {
111
+ return "Error: skill_name must be a non-empty string.";
112
+ }
113
+ const skills = getSkills();
114
+ const skill = skills.find((s) => s.name === skillName);
115
+ if (!skill) {
116
+ const available = skills.map((s) => s.name).join(", ") || "none";
117
+ return `Error: unknown skill '${skillName}'. Available skills: ${available}`;
118
+ }
119
+ let text;
120
+ try {
121
+ text = fs.readFileSync(skill.path, "utf-8");
122
+ }
123
+ catch (err) {
124
+ return `Error: could not read skill '${skillName}': ${err}`;
125
+ }
126
+ const [, body] = parseSkillFrontmatter(text);
127
+ const assets = listSkillAssets(skill.dir);
128
+ const lines = [body.replace(/\s+$/, "")];
129
+ if (assets.scripts.length) {
130
+ lines.push("\nScripts available in this skill (run via bash as needed):");
131
+ lines.push(...assets.scripts.map((s) => `- ${s}`));
132
+ }
133
+ if (assets.references.length) {
134
+ lines.push("\nReference files available in this skill (read via read_file as needed):");
135
+ lines.push(...assets.references.map((r) => `- ${r}`));
136
+ }
137
+ if (!assets.scripts.length && !assets.references.length) {
138
+ lines.push("\nThis skill has no scripts/ or references/ subfolders.");
139
+ }
140
+ return lines.join("\n");
141
+ }
package/dist/system.js ADDED
@@ -0,0 +1,19 @@
1
+ import { osPrefix, SYSTEM_PROMPT_BODY } from "./config.js";
2
+ import { getSkills } from "./skills.js";
3
+ function buildSystemPrompt() {
4
+ const skills = getSkills();
5
+ let skillsText = "";
6
+ if (skills.length) {
7
+ const lines = [
8
+ "Available skills (call load_skill(name) to load the full SKILL.md for details):",
9
+ ];
10
+ for (const s of skills) {
11
+ lines.push(s.description ? `- ${s.name}: ${s.description}` : `- ${s.name}`);
12
+ }
13
+ lines.push("If a user task requires specialized domain knowledge matching an available skill, " +
14
+ "call load_skill(name) before proceeding.");
15
+ skillsText = "\n\n" + lines.join("\n") + "\n";
16
+ }
17
+ return osPrefix() + SYSTEM_PROMPT_BODY + skillsText;
18
+ }
19
+ export const SYSTEM_PROMPT = buildSystemPrompt();