@omnidev-ai/core 0.5.2 → 0.5.3

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/index.js CHANGED
@@ -8,6 +8,7 @@ import { buildCommand, buildRouteMap } from "@stricli/core";
8
8
 
9
9
  // src/capability/commands.ts
10
10
  import { existsSync, readdirSync } from "node:fs";
11
+ import { readFile } from "node:fs/promises";
11
12
  import { join } from "node:path";
12
13
 
13
14
  // src/capability/yaml-parser.ts
@@ -63,7 +64,7 @@ async function loadCommands(capabilityPath, capabilityId) {
63
64
  return commands;
64
65
  }
65
66
  async function parseCommandFile(filePath, capabilityId) {
66
- const content = await Bun.file(filePath).text();
67
+ const content = await readFile(filePath, "utf-8");
67
68
  const parsed = parseFrontmatterWithMarkdown(content);
68
69
  if (!parsed) {
69
70
  throw new Error(`Invalid COMMAND.md format at ${filePath}: missing YAML frontmatter`);
@@ -86,12 +87,13 @@ async function parseCommandFile(filePath, capabilityId) {
86
87
  }
87
88
  // src/capability/docs.ts
88
89
  import { existsSync as existsSync2, readdirSync as readdirSync2 } from "node:fs";
90
+ import { readFile as readFile2 } from "node:fs/promises";
89
91
  import { basename, join as join2 } from "node:path";
90
92
  async function loadDocs(capabilityPath, capabilityId) {
91
93
  const docs = [];
92
94
  const definitionPath = join2(capabilityPath, "definition.md");
93
95
  if (existsSync2(definitionPath)) {
94
- const content = await Bun.file(definitionPath).text();
96
+ const content = await readFile2(definitionPath, "utf-8");
95
97
  docs.push({
96
98
  name: "definition",
97
99
  content: content.trim(),
@@ -104,7 +106,7 @@ async function loadDocs(capabilityPath, capabilityId) {
104
106
  for (const entry of entries) {
105
107
  if (entry.isFile() && entry.name.endsWith(".md")) {
106
108
  const docPath = join2(docsDir, entry.name);
107
- const content = await Bun.file(docPath).text();
109
+ const content = await readFile2(docPath, "utf-8");
108
110
  docs.push({
109
111
  name: basename(entry.name, ".md"),
110
112
  content: content.trim(),
@@ -117,15 +119,17 @@ async function loadDocs(capabilityPath, capabilityId) {
117
119
  }
118
120
  // src/capability/loader.ts
119
121
  import { existsSync as existsSync7, readdirSync as readdirSync6 } from "node:fs";
122
+ import { readFile as readFile7 } from "node:fs/promises";
120
123
  import { join as join6 } from "node:path";
121
124
 
122
125
  // src/config/env.ts
123
126
  import { existsSync as existsSync3 } from "node:fs";
127
+ import { readFile as readFile3 } from "node:fs/promises";
124
128
  var ENV_FILE = ".omni/.env";
125
129
  async function loadEnvironment() {
126
130
  const env = {};
127
131
  if (existsSync3(ENV_FILE)) {
128
- const content = await Bun.file(ENV_FILE).text();
132
+ const content = await readFile3(ENV_FILE, "utf-8");
129
133
  for (const line of content.split(`
130
134
  `)) {
131
135
  const trimmed = line.trim();
@@ -205,6 +209,7 @@ function parseCapabilityConfig(tomlContent) {
205
209
 
206
210
  // src/capability/rules.ts
207
211
  import { existsSync as existsSync4, readdirSync as readdirSync3 } from "node:fs";
212
+ import { readFile as readFile4, writeFile } from "node:fs/promises";
208
213
  import { basename as basename2, join as join3 } from "node:path";
209
214
  async function loadRules(capabilityPath, capabilityId) {
210
215
  const rulesDir = join3(capabilityPath, "rules");
@@ -216,7 +221,7 @@ async function loadRules(capabilityPath, capabilityId) {
216
221
  for (const entry of entries) {
217
222
  if (entry.isFile() && entry.name.endsWith(".md")) {
218
223
  const rulePath = join3(rulesDir, entry.name);
219
- const content = await Bun.file(rulePath).text();
224
+ const content = await readFile4(rulePath, "utf-8");
220
225
  rules.push({
221
226
  name: basename2(entry.name, ".md"),
222
227
  content: content.trim(),
@@ -231,7 +236,7 @@ async function writeRules(rules, docs = []) {
231
236
  const rulesContent = generateRulesContent(rules, docs);
232
237
  let content;
233
238
  if (existsSync4(instructionsPath)) {
234
- content = await Bun.file(instructionsPath).text();
239
+ content = await readFile4(instructionsPath, "utf-8");
235
240
  } else {
236
241
  content = `# OmniDev Instructions
237
242
 
@@ -259,7 +264,7 @@ ${endMarker}
259
264
  ` + rulesContent + `
260
265
  ` + content.substring(endIndex);
261
266
  }
262
- await Bun.write(instructionsPath, content);
267
+ await writeFile(instructionsPath, content, "utf-8");
263
268
  }
264
269
  function generateRulesContent(rules, docs = []) {
265
270
  if (rules.length === 0 && docs.length === 0) {
@@ -303,6 +308,7 @@ ${rule.content}
303
308
 
304
309
  // src/capability/skills.ts
305
310
  import { existsSync as existsSync5, readdirSync as readdirSync4 } from "node:fs";
311
+ import { readFile as readFile5 } from "node:fs/promises";
306
312
  import { join as join4 } from "node:path";
307
313
  async function loadSkills(capabilityPath, capabilityId) {
308
314
  const skillsDir = join4(capabilityPath, "skills");
@@ -323,7 +329,7 @@ async function loadSkills(capabilityPath, capabilityId) {
323
329
  return skills;
324
330
  }
325
331
  async function parseSkillFile(filePath, capabilityId) {
326
- const content = await Bun.file(filePath).text();
332
+ const content = await readFile5(filePath, "utf-8");
327
333
  const parsed = parseFrontmatterWithMarkdown(content);
328
334
  if (!parsed) {
329
335
  throw new Error(`Invalid SKILL.md format at ${filePath}: missing YAML frontmatter`);
@@ -343,6 +349,7 @@ async function parseSkillFile(filePath, capabilityId) {
343
349
 
344
350
  // src/capability/subagents.ts
345
351
  import { existsSync as existsSync6, readdirSync as readdirSync5 } from "node:fs";
352
+ import { readFile as readFile6 } from "node:fs/promises";
346
353
  import { join as join5 } from "node:path";
347
354
  async function loadSubagents(capabilityPath, capabilityId) {
348
355
  const subagentsDir = join5(capabilityPath, "subagents");
@@ -363,7 +370,7 @@ async function loadSubagents(capabilityPath, capabilityId) {
363
370
  return subagents;
364
371
  }
365
372
  async function parseSubagentFile(filePath, capabilityId) {
366
- const content = await Bun.file(filePath).text();
373
+ const content = await readFile6(filePath, "utf-8");
367
374
  const parsed = parseFrontmatterWithMarkdown(content);
368
375
  if (!parsed) {
369
376
  throw new Error(`Invalid SUBAGENT.md format at ${filePath}: missing YAML frontmatter`);
@@ -423,7 +430,7 @@ async function discoverCapabilities() {
423
430
  }
424
431
  async function loadCapabilityConfig(capabilityPath) {
425
432
  const configPath = join6(capabilityPath, "capability.toml");
426
- const content = await Bun.file(configPath).text();
433
+ const content = await readFile7(configPath, "utf-8");
427
434
  const config = parseCapabilityConfig(content);
428
435
  return config;
429
436
  }
@@ -452,7 +459,7 @@ async function loadTypeDefinitions(capabilityPath) {
452
459
  if (!existsSync7(typesPath)) {
453
460
  return;
454
461
  }
455
- return Bun.file(typesPath).text();
462
+ return readFile7(typesPath, "utf-8");
456
463
  }
457
464
  function convertSkillExports(skillExports, capabilityId) {
458
465
  return skillExports.map((skillExport) => {
@@ -666,6 +673,7 @@ async function loadCapability(capabilityPath, env) {
666
673
  }
667
674
  // src/config/loader.ts
668
675
  import { existsSync as existsSync8 } from "node:fs";
676
+ import { readFile as readFile8, writeFile as writeFile2 } from "node:fs/promises";
669
677
  var CONFIG_PATH = "omni.toml";
670
678
  var LOCAL_CONFIG = "omni.local.toml";
671
679
  function mergeConfigs(base, override) {
@@ -687,18 +695,18 @@ async function loadConfig() {
687
695
  let baseConfig = {};
688
696
  let localConfig = {};
689
697
  if (existsSync8(CONFIG_PATH)) {
690
- const content = await Bun.file(CONFIG_PATH).text();
698
+ const content = await readFile8(CONFIG_PATH, "utf-8");
691
699
  baseConfig = parseOmniConfig(content);
692
700
  }
693
701
  if (existsSync8(LOCAL_CONFIG)) {
694
- const content = await Bun.file(LOCAL_CONFIG).text();
702
+ const content = await readFile8(LOCAL_CONFIG, "utf-8");
695
703
  localConfig = parseOmniConfig(content);
696
704
  }
697
705
  return mergeConfigs(baseConfig, localConfig);
698
706
  }
699
707
  async function writeConfig(config) {
700
708
  const content = generateConfigToml(config);
701
- await Bun.write(CONFIG_PATH, content);
709
+ await writeFile2(CONFIG_PATH, content, "utf-8");
702
710
  }
703
711
  function generateConfigToml(config) {
704
712
  const lines = [];
@@ -812,6 +820,7 @@ function generateConfigToml(config) {
812
820
 
813
821
  // src/state/active-profile.ts
814
822
  import { existsSync as existsSync9, mkdirSync } from "node:fs";
823
+ import { readFile as readFile9, unlink, writeFile as writeFile3 } from "node:fs/promises";
815
824
  var STATE_DIR = ".omni/state";
816
825
  var ACTIVE_PROFILE_PATH = `${STATE_DIR}/active-profile`;
817
826
  async function readActiveProfileState() {
@@ -819,7 +828,7 @@ async function readActiveProfileState() {
819
828
  return null;
820
829
  }
821
830
  try {
822
- const content = await Bun.file(ACTIVE_PROFILE_PATH).text();
831
+ const content = await readFile9(ACTIVE_PROFILE_PATH, "utf-8");
823
832
  const trimmed = content.trim();
824
833
  return trimmed || null;
825
834
  } catch {
@@ -828,12 +837,11 @@ async function readActiveProfileState() {
828
837
  }
829
838
  async function writeActiveProfileState(profileName) {
830
839
  mkdirSync(STATE_DIR, { recursive: true });
831
- await Bun.write(ACTIVE_PROFILE_PATH, profileName);
840
+ await writeFile3(ACTIVE_PROFILE_PATH, profileName, "utf-8");
832
841
  }
833
842
  async function clearActiveProfileState() {
834
843
  if (existsSync9(ACTIVE_PROFILE_PATH)) {
835
- const file = Bun.file(ACTIVE_PROFILE_PATH);
836
- await file.delete();
844
+ await unlink(ACTIVE_PROFILE_PATH);
837
845
  }
838
846
  }
839
847
 
@@ -929,7 +937,8 @@ async function buildCapabilityRegistry() {
929
937
  }
930
938
  // src/capability/sources.ts
931
939
  import { existsSync as existsSync10 } from "node:fs";
932
- import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
940
+ import { spawn } from "node:child_process";
941
+ import { cp, mkdir, readdir, readFile as readFile10, rm, stat, writeFile as writeFile4 } from "node:fs/promises";
933
942
  import { join as join7 } from "node:path";
934
943
  import { parse as parseToml } from "smol-toml";
935
944
  var OMNI_LOCAL = ".omni";
@@ -941,6 +950,28 @@ var DOC_DIRS = ["docs", "doc", "documentation"];
941
950
  var SKILL_FILES = ["SKILL.md", "skill.md", "Skill.md"];
942
951
  var AGENT_FILES = ["AGENT.md", "agent.md", "Agent.md", "SUBAGENT.md", "subagent.md"];
943
952
  var COMMAND_FILES = ["COMMAND.md", "command.md", "Command.md"];
953
+ async function spawnCapture(command, args, options) {
954
+ return await new Promise((resolve, reject) => {
955
+ const child = spawn(command, args, {
956
+ cwd: options?.cwd,
957
+ stdio: ["ignore", "pipe", "pipe"]
958
+ });
959
+ let stdout = "";
960
+ let stderr = "";
961
+ child.stdout?.setEncoding("utf-8");
962
+ child.stderr?.setEncoding("utf-8");
963
+ child.stdout?.on("data", (chunk) => {
964
+ stdout += chunk;
965
+ });
966
+ child.stderr?.on("data", (chunk) => {
967
+ stderr += chunk;
968
+ });
969
+ child.on("error", (error) => reject(error));
970
+ child.on("close", (exitCode) => {
971
+ resolve({ exitCode: exitCode ?? 0, stdout, stderr });
972
+ });
973
+ });
974
+ }
944
975
  function parseSourceConfig(source) {
945
976
  if (typeof source === "string") {
946
977
  let sourceUrl = source;
@@ -977,7 +1008,7 @@ async function loadLockFile() {
977
1008
  return { capabilities: {} };
978
1009
  }
979
1010
  try {
980
- const content = await readFile(lockPath, "utf-8");
1011
+ const content = await readFile10(lockPath, "utf-8");
981
1012
  const parsed = parseToml(content);
982
1013
  const capabilities = parsed["capabilities"];
983
1014
  return {
@@ -1014,17 +1045,16 @@ async function saveLockFile(lockFile) {
1014
1045
 
1015
1046
  `;
1016
1047
  const content = header + stringifyLockFile(lockFile);
1017
- await writeFile(lockPath, content, "utf-8");
1048
+ await writeFile4(lockPath, content, "utf-8");
1018
1049
  }
1019
1050
  async function getRepoCommit(repoPath) {
1020
- const proc = Bun.spawn(["git", "rev-parse", "HEAD"], {
1021
- cwd: repoPath,
1022
- stdout: "pipe",
1023
- stderr: "pipe"
1051
+ const { exitCode, stdout, stderr } = await spawnCapture("git", ["rev-parse", "HEAD"], {
1052
+ cwd: repoPath
1024
1053
  });
1025
- const output = await new Response(proc.stdout).text();
1026
- await proc.exited;
1027
- return output.trim();
1054
+ if (exitCode !== 0) {
1055
+ throw new Error(`Failed to get commit for ${repoPath}: ${stderr.trim()}`);
1056
+ }
1057
+ return stdout.trim();
1028
1058
  }
1029
1059
  function shortCommit(commit) {
1030
1060
  return commit.substring(0, 7);
@@ -1036,42 +1066,34 @@ async function cloneRepo(gitUrl, targetPath, ref) {
1036
1066
  args.push("--branch", ref);
1037
1067
  }
1038
1068
  args.push(gitUrl, targetPath);
1039
- const proc = Bun.spawn(["git", ...args], {
1040
- stdout: "pipe",
1041
- stderr: "pipe"
1042
- });
1043
- await proc.exited;
1044
- if (proc.exitCode !== 0) {
1045
- const stderr = await new Response(proc.stderr).text();
1046
- throw new Error(`Failed to clone ${gitUrl}: ${stderr}`);
1069
+ const { exitCode, stderr } = await spawnCapture("git", args);
1070
+ if (exitCode !== 0) {
1071
+ throw new Error(`Failed to clone ${gitUrl}: ${stderr.trim()}`);
1047
1072
  }
1048
1073
  }
1049
1074
  async function fetchRepo(repoPath, ref) {
1050
- const fetchProc = Bun.spawn(["git", "fetch", "--depth", "1", "origin"], {
1051
- cwd: repoPath,
1052
- stdout: "pipe",
1053
- stderr: "pipe"
1075
+ const fetchResult = await spawnCapture("git", ["fetch", "--depth", "1", "origin"], {
1076
+ cwd: repoPath
1054
1077
  });
1055
- await fetchProc.exited;
1078
+ if (fetchResult.exitCode !== 0) {
1079
+ throw new Error(`Failed to fetch in ${repoPath}: ${fetchResult.stderr.trim()}`);
1080
+ }
1056
1081
  const currentCommit = await getRepoCommit(repoPath);
1057
1082
  const targetRef = ref || "HEAD";
1058
- const lsProc = Bun.spawn(["git", "ls-remote", "origin", targetRef], {
1059
- cwd: repoPath,
1060
- stdout: "pipe",
1061
- stderr: "pipe"
1083
+ const lsResult = await spawnCapture("git", ["ls-remote", "origin", targetRef], {
1084
+ cwd: repoPath
1062
1085
  });
1063
- const lsOutput = await new Response(lsProc.stdout).text();
1064
- await lsProc.exited;
1065
- const remoteCommit = lsOutput.split("\t")[0];
1086
+ if (lsResult.exitCode !== 0) {
1087
+ throw new Error(`Failed to ls-remote in ${repoPath}: ${lsResult.stderr.trim()}`);
1088
+ }
1089
+ const remoteCommit = lsResult.stdout.split("\t")[0];
1066
1090
  if (currentCommit === remoteCommit) {
1067
1091
  return false;
1068
1092
  }
1069
- const pullProc = Bun.spawn(["git", "pull", "--ff-only"], {
1070
- cwd: repoPath,
1071
- stdout: "pipe",
1072
- stderr: "pipe"
1073
- });
1074
- await pullProc.exited;
1093
+ const pullResult = await spawnCapture("git", ["pull", "--ff-only"], { cwd: repoPath });
1094
+ if (pullResult.exitCode !== 0) {
1095
+ throw new Error(`Failed to pull in ${repoPath}: ${pullResult.stderr.trim()}`);
1096
+ }
1075
1097
  return true;
1076
1098
  }
1077
1099
  function hasCapabilityToml(dirPath) {
@@ -1141,7 +1163,7 @@ async function parsePluginJson(dirPath) {
1141
1163
  return null;
1142
1164
  }
1143
1165
  try {
1144
- const content = await readFile(pluginJsonPath, "utf-8");
1166
+ const content = await readFile10(pluginJsonPath, "utf-8");
1145
1167
  const data = JSON.parse(content);
1146
1168
  const result = {
1147
1169
  name: data.name,
@@ -1166,7 +1188,7 @@ async function readReadmeDescription(dirPath) {
1166
1188
  return null;
1167
1189
  }
1168
1190
  try {
1169
- const content = await readFile(readmePath, "utf-8");
1191
+ const content = await readFile10(readmePath, "utf-8");
1170
1192
  const lines = content.split(`
1171
1193
  `);
1172
1194
  let description = "";
@@ -1271,7 +1293,7 @@ repository = "${repoUrl}"
1271
1293
  wrapped = true
1272
1294
  commit = "${commit}"
1273
1295
  `;
1274
- await writeFile(join7(repoPath, "capability.toml"), tomlContent, "utf-8");
1296
+ await writeFile4(join7(repoPath, "capability.toml"), tomlContent, "utf-8");
1275
1297
  }
1276
1298
  async function fetchGitCapabilitySource(id, config, options) {
1277
1299
  const gitUrl = sourceToGitUrl(config.source);
@@ -1347,7 +1369,7 @@ async function fetchGitCapabilitySource(id, config, options) {
1347
1369
  const pkgJsonPath = join7(repoPath, "package.json");
1348
1370
  if (existsSync10(pkgJsonPath)) {
1349
1371
  try {
1350
- const pkgJson = JSON.parse(await readFile(pkgJsonPath, "utf-8"));
1372
+ const pkgJson = JSON.parse(await readFile10(pkgJsonPath, "utf-8"));
1351
1373
  if (pkgJson.version) {
1352
1374
  version = pkgJson.version;
1353
1375
  }
@@ -1407,7 +1429,7 @@ command = "${mcpConfig.command}"
1407
1429
  }
1408
1430
  async function generateMcpCapabilityToml(id, mcpConfig, targetPath) {
1409
1431
  const tomlContent = generateMcpCapabilityTomlContent(id, mcpConfig);
1410
- await writeFile(join7(targetPath, "capability.toml"), tomlContent, "utf-8");
1432
+ await writeFile4(join7(targetPath, "capability.toml"), tomlContent, "utf-8");
1411
1433
  }
1412
1434
  async function isGeneratedMcpCapability(capabilityDir) {
1413
1435
  const tomlPath = join7(capabilityDir, "capability.toml");
@@ -1416,7 +1438,7 @@ async function isGeneratedMcpCapability(capabilityDir) {
1416
1438
  return false;
1417
1439
  }
1418
1440
  try {
1419
- const content = await readFile(tomlPath, "utf-8");
1441
+ const content = await readFile10(tomlPath, "utf-8");
1420
1442
  const parsed = parseToml(content);
1421
1443
  const capability = parsed["capability"];
1422
1444
  const metadata = capability?.["metadata"];
@@ -1533,21 +1555,20 @@ async function checkForUpdates(config) {
1533
1555
  });
1534
1556
  continue;
1535
1557
  }
1536
- const fetchProc = Bun.spawn(["git", "fetch", "--depth", "1", "origin"], {
1537
- cwd: targetPath,
1538
- stdout: "pipe",
1539
- stderr: "pipe"
1558
+ const fetchResult = await spawnCapture("git", ["fetch", "--depth", "1", "origin"], {
1559
+ cwd: targetPath
1540
1560
  });
1541
- await fetchProc.exited;
1561
+ if (fetchResult.exitCode !== 0) {
1562
+ throw new Error(`Failed to fetch in ${targetPath}: ${fetchResult.stderr.trim()}`);
1563
+ }
1542
1564
  const targetRef = gitConfig.ref || "HEAD";
1543
- const lsProc = Bun.spawn(["git", "ls-remote", "origin", targetRef], {
1544
- cwd: targetPath,
1545
- stdout: "pipe",
1546
- stderr: "pipe"
1565
+ const lsResult = await spawnCapture("git", ["ls-remote", "origin", targetRef], {
1566
+ cwd: targetPath
1547
1567
  });
1548
- const lsOutput = await new Response(lsProc.stdout).text();
1549
- await lsProc.exited;
1550
- const remoteCommit = lsOutput.split("\t")[0] || "";
1568
+ if (lsResult.exitCode !== 0) {
1569
+ throw new Error(`Failed to ls-remote in ${targetPath}: ${lsResult.stderr.trim()}`);
1570
+ }
1571
+ const remoteCommit = lsResult.stdout.split("\t")[0] || "";
1551
1572
  const currentCommit = existing?.commit || "";
1552
1573
  updates.push({
1553
1574
  id,
@@ -1561,13 +1582,14 @@ async function checkForUpdates(config) {
1561
1582
  }
1562
1583
  // src/config/provider.ts
1563
1584
  import { existsSync as existsSync11 } from "node:fs";
1585
+ import { readFile as readFile11, writeFile as writeFile5 } from "node:fs/promises";
1564
1586
  import { parse as parse2 } from "smol-toml";
1565
1587
  var PROVIDER_CONFIG_PATH = ".omni/provider.toml";
1566
1588
  async function loadProviderConfig() {
1567
1589
  if (!existsSync11(PROVIDER_CONFIG_PATH)) {
1568
1590
  return { provider: "claude" };
1569
1591
  }
1570
- const content = await Bun.file(PROVIDER_CONFIG_PATH).text();
1592
+ const content = await readFile11(PROVIDER_CONFIG_PATH, "utf-8");
1571
1593
  const parsed = parse2(content);
1572
1594
  return parsed;
1573
1595
  }
@@ -1594,9 +1616,9 @@ async function writeProviderConfig(config) {
1594
1616
  lines.push("# Default: Claude");
1595
1617
  lines.push('provider = "claude"');
1596
1618
  }
1597
- await Bun.write(PROVIDER_CONFIG_PATH, `${lines.join(`
1619
+ await writeFile5(PROVIDER_CONFIG_PATH, `${lines.join(`
1598
1620
  `)}
1599
- `);
1621
+ `, "utf-8");
1600
1622
  }
1601
1623
  function parseProviderFlag(flag) {
1602
1624
  const lower = flag.toLowerCase();
@@ -1610,13 +1632,14 @@ function parseProviderFlag(flag) {
1610
1632
  }
1611
1633
  // src/mcp-json/manager.ts
1612
1634
  import { existsSync as existsSync12 } from "node:fs";
1635
+ import { readFile as readFile12, writeFile as writeFile6 } from "node:fs/promises";
1613
1636
  var MCP_JSON_PATH = ".mcp.json";
1614
1637
  async function readMcpJson() {
1615
1638
  if (!existsSync12(MCP_JSON_PATH)) {
1616
1639
  return { mcpServers: {} };
1617
1640
  }
1618
1641
  try {
1619
- const content = await Bun.file(MCP_JSON_PATH).text();
1642
+ const content = await readFile12(MCP_JSON_PATH, "utf-8");
1620
1643
  const parsed = JSON.parse(content);
1621
1644
  return {
1622
1645
  mcpServers: parsed.mcpServers || {}
@@ -1626,7 +1649,8 @@ async function readMcpJson() {
1626
1649
  }
1627
1650
  }
1628
1651
  async function writeMcpJson(config) {
1629
- await Bun.write(MCP_JSON_PATH, JSON.stringify(config, null, 2));
1652
+ await writeFile6(MCP_JSON_PATH, `${JSON.stringify(config, null, 2)}
1653
+ `, "utf-8");
1630
1654
  }
1631
1655
  function buildMcpServerConfig(mcp) {
1632
1656
  const config = {
@@ -1665,6 +1689,7 @@ async function syncMcpJson(capabilities2, previousManifest, options = {}) {
1665
1689
  }
1666
1690
  // src/state/manifest.ts
1667
1691
  import { existsSync as existsSync13, mkdirSync as mkdirSync2, rmSync } from "node:fs";
1692
+ import { readFile as readFile13, writeFile as writeFile7 } from "node:fs/promises";
1668
1693
  var MANIFEST_PATH = ".omni/state/manifest.json";
1669
1694
  var CURRENT_VERSION = 1;
1670
1695
  async function loadManifest() {
@@ -1675,12 +1700,13 @@ async function loadManifest() {
1675
1700
  capabilities: {}
1676
1701
  };
1677
1702
  }
1678
- const content = await Bun.file(MANIFEST_PATH).text();
1703
+ const content = await readFile13(MANIFEST_PATH, "utf-8");
1679
1704
  return JSON.parse(content);
1680
1705
  }
1681
1706
  async function saveManifest(manifest) {
1682
1707
  mkdirSync2(".omni/state", { recursive: true });
1683
- await Bun.write(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
1708
+ await writeFile7(MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}
1709
+ `, "utf-8");
1684
1710
  }
1685
1711
  function buildManifestFromCapabilities(capabilities2) {
1686
1712
  const manifest = {
@@ -1731,6 +1757,7 @@ async function cleanupStaleResources(previousManifest, currentCapabilityIds) {
1731
1757
  }
1732
1758
  // src/state/providers.ts
1733
1759
  import { existsSync as existsSync14, mkdirSync as mkdirSync3 } from "node:fs";
1760
+ import { readFile as readFile14, writeFile as writeFile8 } from "node:fs/promises";
1734
1761
  var STATE_DIR2 = ".omni/state";
1735
1762
  var PROVIDERS_PATH = `${STATE_DIR2}/providers.json`;
1736
1763
  var DEFAULT_PROVIDERS = ["claude-code"];
@@ -1739,7 +1766,7 @@ async function readEnabledProviders() {
1739
1766
  return DEFAULT_PROVIDERS;
1740
1767
  }
1741
1768
  try {
1742
- const content = await Bun.file(PROVIDERS_PATH).text();
1769
+ const content = await readFile14(PROVIDERS_PATH, "utf-8");
1743
1770
  const state = JSON.parse(content);
1744
1771
  return state.enabled.length > 0 ? state.enabled : DEFAULT_PROVIDERS;
1745
1772
  } catch {
@@ -1749,7 +1776,8 @@ async function readEnabledProviders() {
1749
1776
  async function writeEnabledProviders(providers) {
1750
1777
  mkdirSync3(STATE_DIR2, { recursive: true });
1751
1778
  const state = { enabled: providers };
1752
- await Bun.write(PROVIDERS_PATH, JSON.stringify(state, null, 2));
1779
+ await writeFile8(PROVIDERS_PATH, `${JSON.stringify(state, null, 2)}
1780
+ `, "utf-8");
1753
1781
  }
1754
1782
  async function enableProvider(providerId) {
1755
1783
  const current = await readEnabledProviders();
@@ -1767,7 +1795,7 @@ async function isProviderEnabled(providerId) {
1767
1795
  return current.includes(providerId);
1768
1796
  }
1769
1797
  // src/sync.ts
1770
- import { spawn } from "node:child_process";
1798
+ import { spawn as spawn2 } from "node:child_process";
1771
1799
  import { mkdirSync as mkdirSync4 } from "node:fs";
1772
1800
  async function installCapabilityDependencies(silent) {
1773
1801
  const { existsSync: existsSync15, readdirSync: readdirSync7 } = await import("node:fs");
@@ -1777,6 +1805,18 @@ async function installCapabilityDependencies(silent) {
1777
1805
  return;
1778
1806
  }
1779
1807
  const entries = readdirSync7(capabilitiesDir, { withFileTypes: true });
1808
+ async function commandExists(cmd) {
1809
+ return await new Promise((resolve) => {
1810
+ const proc = spawn2(cmd, ["--version"], { stdio: "ignore" });
1811
+ proc.on("error", () => resolve(false));
1812
+ proc.on("close", (code) => resolve(code === 0));
1813
+ });
1814
+ }
1815
+ const hasBun = await commandExists("bun");
1816
+ const hasNpm = hasBun ? false : await commandExists("npm");
1817
+ if (!hasBun && !hasNpm) {
1818
+ throw new Error("Neither Bun nor npm is installed. Install one of them to install capability dependencies.");
1819
+ }
1780
1820
  for (const entry of entries) {
1781
1821
  if (!entry.isDirectory()) {
1782
1822
  continue;
@@ -1790,7 +1830,10 @@ async function installCapabilityDependencies(silent) {
1790
1830
  console.log(`Installing dependencies for ${capabilityPath}...`);
1791
1831
  }
1792
1832
  await new Promise((resolve, reject) => {
1793
- const proc = spawn("bun", ["install"], {
1833
+ const useNpmCi = hasNpm && existsSync15(join8(capabilityPath, "package-lock.json"));
1834
+ const cmd = hasBun ? "bun" : "npm";
1835
+ const args = hasBun ? ["install"] : useNpmCi ? ["ci"] : ["install"];
1836
+ const proc = spawn2(cmd, args, {
1794
1837
  cwd: capabilityPath,
1795
1838
  stdio: silent ? "ignore" : "inherit"
1796
1839
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnidev-ai/core",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,4 +1,5 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
2
3
  import { join } from "node:path";
3
4
  import type { Command } from "../types";
4
5
  import { parseFrontmatterWithMarkdown } from "./yaml-parser";
@@ -42,7 +43,7 @@ export async function loadCommands(
42
43
  }
43
44
 
44
45
  async function parseCommandFile(filePath: string, capabilityId: string): Promise<Command> {
45
- const content = await Bun.file(filePath).text();
46
+ const content = await readFile(filePath, "utf-8");
46
47
  const parsed = parseFrontmatterWithMarkdown<CommandFrontmatter>(content);
47
48
 
48
49
  if (!parsed) {
@@ -1,4 +1,5 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
2
3
  import { basename, join } from "node:path";
3
4
  import type { Doc } from "../types";
4
5
 
@@ -15,7 +16,7 @@ export async function loadDocs(capabilityPath: string, capabilityId: string): Pr
15
16
  // Load definition.md if exists
16
17
  const definitionPath = join(capabilityPath, "definition.md");
17
18
  if (existsSync(definitionPath)) {
18
- const content = await Bun.file(definitionPath).text();
19
+ const content = await readFile(definitionPath, "utf-8");
19
20
  docs.push({
20
21
  name: "definition",
21
22
  content: content.trim(),
@@ -33,7 +34,7 @@ export async function loadDocs(capabilityPath: string, capabilityId: string): Pr
33
34
  for (const entry of entries) {
34
35
  if (entry.isFile() && entry.name.endsWith(".md")) {
35
36
  const docPath = join(docsDir, entry.name);
36
- const content = await Bun.file(docPath).text();
37
+ const content = await readFile(docPath, "utf-8");
37
38
 
38
39
  docs.push({
39
40
  name: basename(entry.name, ".md"),
@@ -1,4 +1,5 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
2
3
  import { join } from "node:path";
3
4
  import { validateEnv } from "../config/env";
4
5
  import { parseCapabilityConfig } from "../config/parser";
@@ -63,7 +64,7 @@ export async function discoverCapabilities(): Promise<string[]> {
63
64
  */
64
65
  export async function loadCapabilityConfig(capabilityPath: string): Promise<CapabilityConfig> {
65
66
  const configPath = join(capabilityPath, "capability.toml");
66
- const content = await Bun.file(configPath).text();
67
+ const content = await readFile(configPath, "utf-8");
67
68
  const config = parseCapabilityConfig(content);
68
69
 
69
70
  return config;
@@ -116,7 +117,7 @@ async function loadTypeDefinitions(capabilityPath: string): Promise<string | und
116
117
  return undefined;
117
118
  }
118
119
 
119
- return Bun.file(typesPath).text();
120
+ return readFile(typesPath, "utf-8");
120
121
  }
121
122
 
122
123
  /**
@@ -1,4 +1,5 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
2
3
  import { basename, join } from "node:path";
3
4
  import type { Doc, Rule } from "../types";
4
5
 
@@ -23,7 +24,7 @@ export async function loadRules(capabilityPath: string, capabilityId: string): P
23
24
  for (const entry of entries) {
24
25
  if (entry.isFile() && entry.name.endsWith(".md")) {
25
26
  const rulePath = join(rulesDir, entry.name);
26
- const content = await Bun.file(rulePath).text();
27
+ const content = await readFile(rulePath, "utf-8");
27
28
 
28
29
  rules.push({
29
30
  name: basename(entry.name, ".md"),
@@ -51,7 +52,7 @@ export async function writeRules(rules: Rule[], docs: Doc[] = []): Promise<void>
51
52
  // Read existing content or create new file
52
53
  let content: string;
53
54
  if (existsSync(instructionsPath)) {
54
- content = await Bun.file(instructionsPath).text();
55
+ content = await readFile(instructionsPath, "utf-8");
55
56
  } else {
56
57
  // Create new file with basic template
57
58
  content = `# OmniDev Instructions
@@ -85,7 +86,7 @@ export async function writeRules(rules: Rule[], docs: Doc[] = []): Promise<void>
85
86
  content.substring(endIndex);
86
87
  }
87
88
 
88
- await Bun.write(instructionsPath, content);
89
+ await writeFile(instructionsPath, content, "utf-8");
89
90
  }
90
91
 
91
92
  function generateRulesContent(rules: Rule[], docs: Doc[] = []): string {
@@ -1,4 +1,5 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
2
3
  import { join } from "node:path";
3
4
  import type { Skill } from "../types";
4
5
  import { parseFrontmatterWithMarkdown } from "./yaml-parser";
@@ -34,7 +35,7 @@ export async function loadSkills(capabilityPath: string, capabilityId: string):
34
35
  }
35
36
 
36
37
  async function parseSkillFile(filePath: string, capabilityId: string): Promise<Skill> {
37
- const content = await Bun.file(filePath).text();
38
+ const content = await readFile(filePath, "utf-8");
38
39
 
39
40
  const parsed = parseFrontmatterWithMarkdown<SkillFrontmatter>(content);
40
41
 
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import { existsSync } from "node:fs";
12
+ import { spawn } from "node:child_process";
12
13
  import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
13
14
  import { join } from "node:path";
14
15
  import { parse as parseToml } from "smol-toml";
@@ -53,6 +54,37 @@ export interface SourceUpdateInfo {
53
54
  hasUpdate: boolean;
54
55
  }
55
56
 
57
+ async function spawnCapture(
58
+ command: string,
59
+ args: string[],
60
+ options?: { cwd?: string },
61
+ ): Promise<{ exitCode: number; stdout: string; stderr: string }> {
62
+ return await new Promise((resolve, reject) => {
63
+ const child = spawn(command, args, {
64
+ cwd: options?.cwd,
65
+ stdio: ["ignore", "pipe", "pipe"],
66
+ });
67
+
68
+ let stdout = "";
69
+ let stderr = "";
70
+
71
+ child.stdout?.setEncoding("utf-8");
72
+ child.stderr?.setEncoding("utf-8");
73
+
74
+ child.stdout?.on("data", (chunk) => {
75
+ stdout += chunk;
76
+ });
77
+ child.stderr?.on("data", (chunk) => {
78
+ stderr += chunk;
79
+ });
80
+
81
+ child.on("error", (error) => reject(error));
82
+ child.on("close", (exitCode) => {
83
+ resolve({ exitCode: exitCode ?? 0, stdout, stderr });
84
+ });
85
+ });
86
+ }
87
+
56
88
  /**
57
89
  * Check if a source string is a git source
58
90
  */
@@ -188,14 +220,13 @@ export async function saveLockFile(lockFile: CapabilitiesLockFile): Promise<void
188
220
  * Get the current commit hash of a git repository
189
221
  */
190
222
  async function getRepoCommit(repoPath: string): Promise<string> {
191
- const proc = Bun.spawn(["git", "rev-parse", "HEAD"], {
223
+ const { exitCode, stdout, stderr } = await spawnCapture("git", ["rev-parse", "HEAD"], {
192
224
  cwd: repoPath,
193
- stdout: "pipe",
194
- stderr: "pipe",
195
225
  });
196
- const output = await new Response(proc.stdout).text();
197
- await proc.exited;
198
- return output.trim();
226
+ if (exitCode !== 0) {
227
+ throw new Error(`Failed to get commit for ${repoPath}: ${stderr.trim()}`);
228
+ }
229
+ return stdout.trim();
199
230
  }
200
231
 
201
232
  /**
@@ -218,16 +249,9 @@ async function cloneRepo(gitUrl: string, targetPath: string, ref?: string): Prom
218
249
  }
219
250
  args.push(gitUrl, targetPath);
220
251
 
221
- const proc = Bun.spawn(["git", ...args], {
222
- stdout: "pipe",
223
- stderr: "pipe",
224
- });
225
-
226
- await proc.exited;
227
-
228
- if (proc.exitCode !== 0) {
229
- const stderr = await new Response(proc.stderr).text();
230
- throw new Error(`Failed to clone ${gitUrl}: ${stderr}`);
252
+ const { exitCode, stderr } = await spawnCapture("git", args);
253
+ if (exitCode !== 0) {
254
+ throw new Error(`Failed to clone ${gitUrl}: ${stderr.trim()}`);
231
255
  }
232
256
  }
233
257
 
@@ -236,39 +260,36 @@ async function cloneRepo(gitUrl: string, targetPath: string, ref?: string): Prom
236
260
  */
237
261
  async function fetchRepo(repoPath: string, ref?: string): Promise<boolean> {
238
262
  // Fetch latest
239
- const fetchProc = Bun.spawn(["git", "fetch", "--depth", "1", "origin"], {
263
+ const fetchResult = await spawnCapture("git", ["fetch", "--depth", "1", "origin"], {
240
264
  cwd: repoPath,
241
- stdout: "pipe",
242
- stderr: "pipe",
243
265
  });
244
- await fetchProc.exited;
266
+ if (fetchResult.exitCode !== 0) {
267
+ throw new Error(`Failed to fetch in ${repoPath}: ${fetchResult.stderr.trim()}`);
268
+ }
245
269
 
246
270
  // Get current and remote commits
247
271
  const currentCommit = await getRepoCommit(repoPath);
248
272
 
249
273
  // Check remote commit
250
274
  const targetRef = ref || "HEAD";
251
- const lsProc = Bun.spawn(["git", "ls-remote", "origin", targetRef], {
275
+ const lsResult = await spawnCapture("git", ["ls-remote", "origin", targetRef], {
252
276
  cwd: repoPath,
253
- stdout: "pipe",
254
- stderr: "pipe",
255
277
  });
256
- const lsOutput = await new Response(lsProc.stdout).text();
257
- await lsProc.exited;
278
+ if (lsResult.exitCode !== 0) {
279
+ throw new Error(`Failed to ls-remote in ${repoPath}: ${lsResult.stderr.trim()}`);
280
+ }
258
281
 
259
- const remoteCommit = lsOutput.split("\t")[0];
282
+ const remoteCommit = lsResult.stdout.split("\t")[0];
260
283
 
261
284
  if (currentCommit === remoteCommit) {
262
285
  return false; // No update
263
286
  }
264
287
 
265
288
  // Pull changes
266
- const pullProc = Bun.spawn(["git", "pull", "--ff-only"], {
267
- cwd: repoPath,
268
- stdout: "pipe",
269
- stderr: "pipe",
270
- });
271
- await pullProc.exited;
289
+ const pullResult = await spawnCapture("git", ["pull", "--ff-only"], { cwd: repoPath });
290
+ if (pullResult.exitCode !== 0) {
291
+ throw new Error(`Failed to pull in ${repoPath}: ${pullResult.stderr.trim()}`);
292
+ }
272
293
 
273
294
  return true; // Updated
274
295
  }
@@ -965,24 +986,23 @@ export async function checkForUpdates(config: OmniConfig): Promise<SourceUpdateI
965
986
  }
966
987
 
967
988
  // Fetch to check for updates (without pulling)
968
- const fetchProc = Bun.spawn(["git", "fetch", "--depth", "1", "origin"], {
989
+ const fetchResult = await spawnCapture("git", ["fetch", "--depth", "1", "origin"], {
969
990
  cwd: targetPath,
970
- stdout: "pipe",
971
- stderr: "pipe",
972
991
  });
973
- await fetchProc.exited;
992
+ if (fetchResult.exitCode !== 0) {
993
+ throw new Error(`Failed to fetch in ${targetPath}: ${fetchResult.stderr.trim()}`);
994
+ }
974
995
 
975
996
  // Get remote commit
976
997
  const targetRef = gitConfig.ref || "HEAD";
977
- const lsProc = Bun.spawn(["git", "ls-remote", "origin", targetRef], {
998
+ const lsResult = await spawnCapture("git", ["ls-remote", "origin", targetRef], {
978
999
  cwd: targetPath,
979
- stdout: "pipe",
980
- stderr: "pipe",
981
1000
  });
982
- const lsOutput = await new Response(lsProc.stdout).text();
983
- await lsProc.exited;
1001
+ if (lsResult.exitCode !== 0) {
1002
+ throw new Error(`Failed to ls-remote in ${targetPath}: ${lsResult.stderr.trim()}`);
1003
+ }
984
1004
 
985
- const remoteCommit = lsOutput.split("\t")[0] || "";
1005
+ const remoteCommit = lsResult.stdout.split("\t")[0] || "";
986
1006
  const currentCommit = existing?.commit || "";
987
1007
 
988
1008
  updates.push({
@@ -1,4 +1,5 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
2
3
  import { join } from "node:path";
3
4
  import type { Subagent, SubagentHooks, SubagentModel, SubagentPermissionMode } from "../types";
4
5
  import { parseFrontmatterWithMarkdown } from "./yaml-parser";
@@ -47,7 +48,7 @@ export async function loadSubagents(
47
48
  }
48
49
 
49
50
  async function parseSubagentFile(filePath: string, capabilityId: string): Promise<Subagent> {
50
- const content = await Bun.file(filePath).text();
51
+ const content = await readFile(filePath, "utf-8");
51
52
 
52
53
  const parsed = parseFrontmatterWithMarkdown<SubagentFrontmatter>(content);
53
54
 
package/src/config/env.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
2
3
  import type { EnvDeclaration } from "../types";
3
4
 
4
5
  const ENV_FILE = ".omni/.env";
@@ -14,7 +15,7 @@ export async function loadEnvironment(): Promise<Record<string, string>> {
14
15
 
15
16
  // Load from .omni/.env
16
17
  if (existsSync(ENV_FILE)) {
17
- const content = await Bun.file(ENV_FILE).text();
18
+ const content = await readFile(ENV_FILE, "utf-8");
18
19
  for (const line of content.split("\n")) {
19
20
  const trimmed = line.trim();
20
21
  // Skip empty lines and comments
@@ -1,4 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
2
3
  import type { OmniConfig } from "../types";
3
4
  import { parseOmniConfig } from "./parser";
4
5
 
@@ -46,12 +47,12 @@ export async function loadConfig(): Promise<OmniConfig> {
46
47
  let localConfig: OmniConfig = {};
47
48
 
48
49
  if (existsSync(CONFIG_PATH)) {
49
- const content = await Bun.file(CONFIG_PATH).text();
50
+ const content = await readFile(CONFIG_PATH, "utf-8");
50
51
  baseConfig = parseOmniConfig(content);
51
52
  }
52
53
 
53
54
  if (existsSync(LOCAL_CONFIG)) {
54
- const content = await Bun.file(LOCAL_CONFIG).text();
55
+ const content = await readFile(LOCAL_CONFIG, "utf-8");
55
56
  localConfig = parseOmniConfig(content);
56
57
  }
57
58
 
@@ -64,7 +65,7 @@ export async function loadConfig(): Promise<OmniConfig> {
64
65
  */
65
66
  export async function writeConfig(config: OmniConfig): Promise<void> {
66
67
  const content = generateConfigToml(config);
67
- await Bun.write(CONFIG_PATH, content);
68
+ await writeFile(CONFIG_PATH, content, "utf-8");
68
69
  }
69
70
 
70
71
  /**
@@ -1,4 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
2
3
  import { parse } from "smol-toml";
3
4
  import type { Provider, ProviderConfig } from "../types/index.js";
4
5
 
@@ -9,7 +10,7 @@ export async function loadProviderConfig(): Promise<ProviderConfig> {
9
10
  return { provider: "claude" };
10
11
  }
11
12
 
12
- const content = await Bun.file(PROVIDER_CONFIG_PATH).text();
13
+ const content = await readFile(PROVIDER_CONFIG_PATH, "utf-8");
13
14
  const parsed = parse(content) as unknown as ProviderConfig;
14
15
  return parsed;
15
16
  }
@@ -40,7 +41,7 @@ export async function writeProviderConfig(config: ProviderConfig): Promise<void>
40
41
  lines.push('provider = "claude"');
41
42
  }
42
43
 
43
- await Bun.write(PROVIDER_CONFIG_PATH, `${lines.join("\n")}\n`);
44
+ await writeFile(PROVIDER_CONFIG_PATH, `${lines.join("\n")}\n`, "utf-8");
44
45
  }
45
46
 
46
47
  export function parseProviderFlag(flag: string): Provider[] {
@@ -1,4 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
2
3
  import type { LoadedCapability, McpConfig } from "../types";
3
4
  import type { ResourceManifest } from "../state/manifest";
4
5
 
@@ -29,7 +30,7 @@ export async function readMcpJson(): Promise<McpJsonConfig> {
29
30
  }
30
31
 
31
32
  try {
32
- const content = await Bun.file(MCP_JSON_PATH).text();
33
+ const content = await readFile(MCP_JSON_PATH, "utf-8");
33
34
  const parsed = JSON.parse(content);
34
35
  return {
35
36
  mcpServers: parsed.mcpServers || {},
@@ -44,7 +45,7 @@ export async function readMcpJson(): Promise<McpJsonConfig> {
44
45
  * Write .mcp.json, preserving non-OmniDev entries
45
46
  */
46
47
  export async function writeMcpJson(config: McpJsonConfig): Promise<void> {
47
- await Bun.write(MCP_JSON_PATH, JSON.stringify(config, null, 2));
48
+ await writeFile(MCP_JSON_PATH, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
48
49
  }
49
50
 
50
51
  /**
@@ -1,4 +1,5 @@
1
1
  import { existsSync, mkdirSync } from "node:fs";
2
+ import { readFile, unlink, writeFile } from "node:fs/promises";
2
3
 
3
4
  const STATE_DIR = ".omni/state";
4
5
  const ACTIVE_PROFILE_PATH = `${STATE_DIR}/active-profile`;
@@ -13,7 +14,7 @@ export async function readActiveProfileState(): Promise<string | null> {
13
14
  }
14
15
 
15
16
  try {
16
- const content = await Bun.file(ACTIVE_PROFILE_PATH).text();
17
+ const content = await readFile(ACTIVE_PROFILE_PATH, "utf-8");
17
18
  const trimmed = content.trim();
18
19
  return trimmed || null;
19
20
  } catch {
@@ -27,7 +28,7 @@ export async function readActiveProfileState(): Promise<string | null> {
27
28
  */
28
29
  export async function writeActiveProfileState(profileName: string): Promise<void> {
29
30
  mkdirSync(STATE_DIR, { recursive: true });
30
- await Bun.write(ACTIVE_PROFILE_PATH, profileName);
31
+ await writeFile(ACTIVE_PROFILE_PATH, profileName, "utf-8");
31
32
  }
32
33
 
33
34
  /**
@@ -35,7 +36,6 @@ export async function writeActiveProfileState(profileName: string): Promise<void
35
36
  */
36
37
  export async function clearActiveProfileState(): Promise<void> {
37
38
  if (existsSync(ACTIVE_PROFILE_PATH)) {
38
- const file = Bun.file(ACTIVE_PROFILE_PATH);
39
- await file.delete();
39
+ await unlink(ACTIVE_PROFILE_PATH);
40
40
  }
41
41
  }
@@ -1,4 +1,5 @@
1
1
  import { existsSync, mkdirSync, rmSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
2
3
  import type { LoadedCapability } from "../types";
3
4
 
4
5
  /**
@@ -52,7 +53,7 @@ export async function loadManifest(): Promise<ResourceManifest> {
52
53
  };
53
54
  }
54
55
 
55
- const content = await Bun.file(MANIFEST_PATH).text();
56
+ const content = await readFile(MANIFEST_PATH, "utf-8");
56
57
  return JSON.parse(content) as ResourceManifest;
57
58
  }
58
59
 
@@ -61,7 +62,7 @@ export async function loadManifest(): Promise<ResourceManifest> {
61
62
  */
62
63
  export async function saveManifest(manifest: ResourceManifest): Promise<void> {
63
64
  mkdirSync(".omni/state", { recursive: true });
64
- await Bun.write(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
65
+ await writeFile(MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}\n`, "utf-8");
65
66
  }
66
67
 
67
68
  /**
@@ -1,4 +1,5 @@
1
1
  import { existsSync, mkdirSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
2
3
  import type { ProviderId } from "../types/index.js";
3
4
 
4
5
  const STATE_DIR = ".omni/state";
@@ -20,7 +21,7 @@ export async function readEnabledProviders(): Promise<ProviderId[]> {
20
21
  }
21
22
 
22
23
  try {
23
- const content = await Bun.file(PROVIDERS_PATH).text();
24
+ const content = await readFile(PROVIDERS_PATH, "utf-8");
24
25
  const state = JSON.parse(content) as ProvidersState;
25
26
  return state.enabled.length > 0 ? state.enabled : DEFAULT_PROVIDERS;
26
27
  } catch {
@@ -35,7 +36,7 @@ export async function readEnabledProviders(): Promise<ProviderId[]> {
35
36
  export async function writeEnabledProviders(providers: ProviderId[]): Promise<void> {
36
37
  mkdirSync(STATE_DIR, { recursive: true });
37
38
  const state: ProvidersState = { enabled: providers };
38
- await Bun.write(PROVIDERS_PATH, JSON.stringify(state, null, 2));
39
+ await writeFile(PROVIDERS_PATH, `${JSON.stringify(state, null, 2)}\n`, "utf-8");
39
40
  }
40
41
 
41
42
  /**
package/src/sync.ts CHANGED
@@ -43,6 +43,23 @@ export async function installCapabilityDependencies(silent: boolean): Promise<vo
43
43
 
44
44
  const entries = readdirSync(capabilitiesDir, { withFileTypes: true });
45
45
 
46
+ async function commandExists(cmd: string): Promise<boolean> {
47
+ return await new Promise((resolve) => {
48
+ const proc = spawn(cmd, ["--version"], { stdio: "ignore" });
49
+ proc.on("error", () => resolve(false));
50
+ proc.on("close", (code) => resolve(code === 0));
51
+ });
52
+ }
53
+
54
+ const hasBun = await commandExists("bun");
55
+ const hasNpm = hasBun ? false : await commandExists("npm");
56
+
57
+ if (!hasBun && !hasNpm) {
58
+ throw new Error(
59
+ "Neither Bun nor npm is installed. Install one of them to install capability dependencies.",
60
+ );
61
+ }
62
+
46
63
  for (const entry of entries) {
47
64
  if (!entry.isDirectory()) {
48
65
  continue;
@@ -60,9 +77,13 @@ export async function installCapabilityDependencies(silent: boolean): Promise<vo
60
77
  console.log(`Installing dependencies for ${capabilityPath}...`);
61
78
  }
62
79
 
63
- // Run bun install in the capability directory
80
+ // Prefer Bun if available, otherwise fallback to npm.
64
81
  await new Promise<void>((resolve, reject) => {
65
- const proc = spawn("bun", ["install"], {
82
+ const useNpmCi = hasNpm && existsSync(join(capabilityPath, "package-lock.json"));
83
+ const cmd = hasBun ? "bun" : "npm";
84
+ const args = hasBun ? ["install"] : useNpmCi ? ["ci"] : ["install"];
85
+
86
+ const proc = spawn(cmd, args, {
66
87
  cwd: capabilityPath,
67
88
  stdio: silent ? "ignore" : "inherit",
68
89
  });