@moskala/oneagent-core 0.2.3 → 0.2.4

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": "@moskala/oneagent-core",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "description": "Core library for oneagent — one source of truth for AI agent rules",
6
6
  "license": "MIT",
@@ -1,5 +1,9 @@
1
1
  import path from "path";
2
2
  import fs from "fs/promises";
3
+ import { execFile } from "child_process";
4
+ import { promisify } from "util";
5
+
6
+ const execFileAsync = promisify(execFile);
3
7
 
4
8
  export interface TemplateDefinition {
5
9
  name: string;
@@ -17,10 +21,10 @@ export async function applyTemplateFiles(root: string, template: TemplateDefinit
17
21
  await fs.mkdir(path.join(oneagentDir, "rules"), { recursive: true });
18
22
  await fs.mkdir(path.join(oneagentDir, "skills"), { recursive: true });
19
23
 
20
- await Bun.write(path.join(oneagentDir, "instructions.md"), template.instructions);
24
+ await fs.writeFile(path.join(oneagentDir, "instructions.md"), template.instructions);
21
25
 
22
26
  for (const rule of template.rules) {
23
- await Bun.write(path.join(oneagentDir, "rules", `${rule.name}.md`), rule.content);
27
+ await fs.writeFile(path.join(oneagentDir, "rules", `${rule.name}.md`), rule.content);
24
28
  }
25
29
  }
26
30
 
@@ -33,7 +37,7 @@ export async function installTemplateSkills(
33
37
  ): Promise<void> {
34
38
  for (const identifier of template.skills) {
35
39
  try {
36
- await Bun.$`bunx skills add ${identifier} --agent universal --yes`.cwd(root).quiet();
40
+ await execFileAsync("npx", ["skills", "add", identifier, "--agent", "universal", "--yes"], { cwd: root });
37
41
  onSkillInstalled?.(identifier);
38
42
  } catch (err) {
39
43
  const message = err instanceof Error ? err.message : String(err);
package/src/config.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { parse, stringify } from "yaml";
2
2
  import path from "path";
3
+ import fs from "fs/promises";
3
4
  import type { AgentTarget, Config } from "./types.ts";
4
5
 
5
6
  const CONFIG_REL = ".oneagent/config.yml";
@@ -15,15 +16,16 @@ export function makeTargets(...enabled: AgentTarget[]): Record<AgentTarget, bool
15
16
  }
16
17
 
17
18
  export async function configExists(root: string): Promise<boolean> {
18
- return Bun.file(path.join(root, CONFIG_REL)).exists();
19
+ return fs.access(path.join(root, CONFIG_REL)).then(() => true, () => false);
19
20
  }
20
21
 
21
22
  export async function readConfig(root: string): Promise<Config> {
22
- const content = await Bun.file(path.join(root, CONFIG_REL)).text();
23
+ const content = await fs.readFile(path.join(root, CONFIG_REL), "utf-8");
23
24
  return parse(content) as Config;
24
25
  }
25
26
 
26
27
  export async function writeConfig(root: string, config: Config): Promise<void> {
27
28
  const filePath = path.join(root, CONFIG_REL);
28
- await Bun.write(filePath, stringify(config));
29
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
30
+ await fs.writeFile(filePath, stringify(config));
29
31
  }
package/src/copilot.ts CHANGED
@@ -13,7 +13,7 @@ export function copilotFilePath(root: string, ruleName: string): string {
13
13
  export async function generateCopilotRule(root: string, rule: RuleFile): Promise<void> {
14
14
  const filePath = copilotFilePath(root, rule.name);
15
15
  await fs.mkdir(path.dirname(filePath), { recursive: true });
16
- await Bun.write(filePath, buildCopilotContent(rule));
16
+ await fs.writeFile(filePath, buildCopilotContent(rule));
17
17
  }
18
18
 
19
19
  export async function generateCopilotRules(root: string, rules: RuleFile[]): Promise<void> {
@@ -34,7 +34,7 @@ export function copilotPromptFilePath(root: string, skillName: string): string {
34
34
  export async function generateCopilotSkill(root: string, skill: SkillFile): Promise<void> {
35
35
  const filePath = copilotPromptFilePath(root, skill.name);
36
36
  await fs.mkdir(path.dirname(filePath), { recursive: true });
37
- await Bun.write(filePath, buildCopilotPromptContent(skill));
37
+ await fs.writeFile(filePath, buildCopilotPromptContent(skill));
38
38
  }
39
39
 
40
40
  export async function generateCopilotSkills(root: string, skills: SkillFile[]): Promise<void> {
package/src/detect.ts CHANGED
@@ -22,7 +22,7 @@ export async function readDetectedFile(root: string, rel: string): Promise<Detec
22
22
  if (resolved.startsWith(path.join(root, ".one"))) return null;
23
23
  }
24
24
 
25
- const content = await Bun.file(absolutePath).text();
25
+ const content = await fs.readFile(absolutePath, "utf-8");
26
26
  return {
27
27
  relativePath: rel,
28
28
  absolutePath,
package/src/generate.ts CHANGED
@@ -23,7 +23,7 @@ export async function detectGenerateCollisions(root: string, config: Config): Pr
23
23
  // 2. Rule/skill symlink paths
24
24
  const ruleSkillEntries = [
25
25
  ...buildRulesSymlinks(root, targets, rules),
26
- ...buildSkillSymlinks(root, targets, skills),
26
+ ...buildSkillSymlinks(root, targets),
27
27
  // .agents/skills skipped — handled by migrateAgentsSkillsDir
28
28
  ];
29
29
 
@@ -41,7 +41,7 @@ export async function detectGenerateCollisions(root: string, config: Config): Pr
41
41
  ...rules.map(async (rule): Promise<DetectedFile | null> => {
42
42
  const filePath = copilotFilePath(root, rule.name);
43
43
  try {
44
- const content = await Bun.file(filePath).text();
44
+ const content = await fs.readFile(filePath, "utf-8");
45
45
  if (content === buildCopilotContent(rule)) return null;
46
46
  const stat = await fs.lstat(filePath);
47
47
  return { relativePath: path.relative(root, filePath), absolutePath: filePath, sizeBytes: stat.size, modifiedAt: stat.mtime, content };
@@ -50,7 +50,7 @@ export async function detectGenerateCollisions(root: string, config: Config): Pr
50
50
  ...skills.map(async (skill): Promise<DetectedFile | null> => {
51
51
  const filePath = copilotPromptFilePath(root, skill.name);
52
52
  try {
53
- const content = await Bun.file(filePath).text();
53
+ const content = await fs.readFile(filePath, "utf-8");
54
54
  if (content === buildCopilotPromptContent(skill)) return null;
55
55
  const stat = await fs.lstat(filePath);
56
56
  return { relativePath: path.relative(root, filePath), absolutePath: filePath, sizeBytes: stat.size, modifiedAt: stat.mtime, content };
@@ -70,10 +70,11 @@ export async function generate(root: string, config: Config): Promise<void> {
70
70
  const [rules, skills] = await Promise.all([readRules(root), readSkills(root)]);
71
71
  const targets = activeTargets(config);
72
72
 
73
+ await migrateRuleAndSkillFiles(root);
74
+
73
75
  const mainSymlinks = buildMainSymlinks(root, targets);
74
76
  const rulesSymlinks = buildRulesSymlinks(root, targets, rules);
75
- const skillSymlinks = buildSkillSymlinks(root, targets, skills);
76
- await migrateRuleAndSkillFiles(root);
77
+ const skillSymlinks = await buildSkillSymlinks(root, targets);
77
78
  await createAllSymlinks([...mainSymlinks, ...rulesSymlinks, ...skillSymlinks, ...buildAgentsDirSymlinks(root)]);
78
79
 
79
80
  if (targets.includes("copilot")) {
package/src/opencode.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import path from "path";
2
+ import fs from "fs/promises";
2
3
  import type { RuleFile } from "./types.ts";
3
4
 
4
5
  export async function readOpencode(root: string): Promise<Record<string, unknown> | null> {
5
6
  try {
6
- const content = await Bun.file(path.join(root, "opencode.json")).text();
7
+ const content = await fs.readFile(path.join(root, "opencode.json"), "utf-8");
7
8
  return JSON.parse(content) as Record<string, unknown>;
8
9
  } catch {
9
10
  return null;
@@ -20,5 +21,5 @@ export function buildOpencodeConfig(existing: Record<string, unknown> | null): o
20
21
  export async function writeOpencode(root: string, _rules: RuleFile[]): Promise<void> {
21
22
  const existing = await readOpencode(root);
22
23
  const config = buildOpencodeConfig(existing);
23
- await Bun.write(path.join(root, "opencode.json"), JSON.stringify(config, null, 2) + "\n");
24
+ await fs.writeFile(path.join(root, "opencode.json"), JSON.stringify(config, null, 2) + "\n");
24
25
  }
package/src/rules.ts CHANGED
@@ -16,7 +16,7 @@ export function parseFrontmatter(raw: string): { applyTo: string; content: strin
16
16
  }
17
17
 
18
18
  export async function readRuleFile(filePath: string): Promise<RuleFile> {
19
- const raw = await Bun.file(filePath).text();
19
+ const raw = await fs.readFile(filePath, "utf-8");
20
20
  const { applyTo, content } = parseFrontmatter(raw);
21
21
  return { name: path.basename(filePath, ".md"), path: filePath, applyTo, content };
22
22
  }
package/src/skills.ts CHANGED
@@ -23,7 +23,7 @@ export function parseSkillFrontmatter(raw: string): { description: string; mode:
23
23
  }
24
24
 
25
25
  export async function readSkillFile(filePath: string): Promise<SkillFile> {
26
- const raw = await Bun.file(filePath).text();
26
+ const raw = await fs.readFile(filePath, "utf-8");
27
27
  const { description, mode, content } = parseSkillFrontmatter(raw);
28
28
  return { name: path.basename(filePath, ".md"), path: filePath, description, mode, content };
29
29
  }
package/src/status.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import fs from "fs/promises";
1
2
  import type { Config, GeneratedFileCheck, OpenCodeCheck, RuleFile, StatusResult } from "./types.ts";
2
3
  import { activeTargets } from "./config.ts";
3
4
  import { readRules } from "./rules.ts";
@@ -10,7 +11,7 @@ export async function checkGeneratedFile(root: string, rule: RuleFile): Promise<
10
11
  const filePath = copilotFilePath(root, rule.name);
11
12
  const expected = buildCopilotContent(rule);
12
13
  try {
13
- const content = await Bun.file(filePath).text();
14
+ const content = await fs.readFile(filePath, "utf-8");
14
15
  return { path: filePath, exists: true, upToDate: content === expected };
15
16
  } catch {
16
17
  return { path: filePath, exists: false, upToDate: false };
@@ -30,7 +31,7 @@ export async function checkCopilotPrompt(root: string, skill: import("./types.ts
30
31
  const filePath = copilotPromptFilePath(root, skill.name);
31
32
  const expected = buildCopilotPromptContent(skill);
32
33
  try {
33
- const content = await Bun.file(filePath).text();
34
+ const content = await fs.readFile(filePath, "utf-8");
34
35
  return { path: filePath, exists: true, upToDate: content === expected };
35
36
  } catch {
36
37
  return { path: filePath, exists: false, upToDate: false };
@@ -44,7 +45,7 @@ export async function checkStatus(root: string, config: Config): Promise<StatusR
44
45
  const allEntries = [
45
46
  ...buildMainSymlinks(root, targets),
46
47
  ...buildRulesSymlinks(root, targets, rules),
47
- ...buildSkillSymlinks(root, targets, skills),
48
+ ...buildSkillSymlinks(root, targets),
48
49
  ...buildAgentsDirSymlinks(root),
49
50
  ];
50
51
 
package/src/symlinks.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from "path";
2
2
  import fs from "fs/promises";
3
- import type { AgentTarget, RuleFile, SkillFile, SymlinkCheck, SymlinkEntry } from "./types.ts";
3
+ import type { AgentTarget, RuleFile, SymlinkCheck, SymlinkEntry } from "./types.ts";
4
4
 
5
5
  export async function ensureDir(dirPath: string): Promise<void> {
6
6
  await fs.mkdir(dirPath, { recursive: true });
@@ -98,18 +98,23 @@ export function buildRulesSymlinks(
98
98
  return entries;
99
99
  }
100
100
 
101
- export function buildSkillSymlinks(root: string, targets: AgentTarget[], skills: SkillFile[]): SymlinkEntry[] {
102
- if (!targets.includes("claude")) return [];
103
-
104
- const commandsDir = path.join(root, ".claude/commands");
105
- return skills.map((skill) => {
106
- const symlinkPath = path.join(commandsDir, `${skill.name}.md`);
107
- return {
108
- symlinkPath,
109
- target: relativeTarget(symlinkPath, skill.path),
110
- label: path.relative(root, symlinkPath),
111
- };
112
- });
101
+ // Creates whole-directory symlinks: .claude/skills .oneagent/skills, .cursor/skills .oneagent/skills
102
+ export function buildSkillSymlinks(root: string, targets: AgentTarget[]): SymlinkEntry[] {
103
+ const targetAbs = path.join(root, ".oneagent/skills");
104
+ const agentDirs: Partial<Record<AgentTarget, string>> = {
105
+ claude: path.join(root, ".claude/skills"),
106
+ cursor: path.join(root, ".cursor/skills"),
107
+ windsurf: path.join(root, ".windsurf/skills"),
108
+ copilot: path.join(root, ".github/skills"),
109
+ };
110
+
111
+ return (Object.entries(agentDirs) as [AgentTarget, string][])
112
+ .filter(([target]) => targets.includes(target))
113
+ .map(([, dir]) => ({
114
+ symlinkPath: dir,
115
+ target: relativeTarget(dir, targetAbs),
116
+ label: path.relative(root, dir),
117
+ }));
113
118
  }
114
119
 
115
120
  export function buildAgentsDirSymlinks(root: string): SymlinkEntry[] {
@@ -142,15 +147,15 @@ async function migrateFilesFromDir(srcDir: string, destDir: string, root: string
142
147
  if (destExists) {
143
148
  // dest exists — compare content before deleting source
144
149
  const [srcContent, destContent] = await Promise.all([
145
- Bun.file(srcFile).text(),
146
- Bun.file(destFile).text(),
150
+ fs.readFile(srcFile, "utf-8"),
151
+ fs.readFile(destFile, "utf-8"),
147
152
  ]);
148
153
  if (srcContent !== destContent) {
149
154
  // Different content — backup source before deleting
150
155
  const backupDir = path.join(root, ".oneagent/backup");
151
156
  await fs.mkdir(backupDir, { recursive: true });
152
157
  const safeName = path.relative(root, srcFile).replace(/\//g, "_");
153
- await Bun.write(path.join(backupDir, safeName), srcContent);
158
+ await fs.writeFile(path.join(backupDir, safeName), srcContent);
154
159
  }
155
160
  await fs.unlink(srcFile); // safe to delete — dest has the content (or backup was created)
156
161
  } else {