@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 +126 -83
- package/package.json +1 -1
- package/src/capability/commands.ts +2 -1
- package/src/capability/docs.ts +3 -2
- package/src/capability/loader.ts +3 -2
- package/src/capability/rules.ts +4 -3
- package/src/capability/skills.ts +2 -1
- package/src/capability/sources.ts +62 -42
- package/src/capability/subagents.ts +2 -1
- package/src/config/env.ts +2 -1
- package/src/config/loader.ts +4 -3
- package/src/config/provider.ts +3 -2
- package/src/mcp-json/manager.ts +3 -2
- package/src/state/active-profile.ts +4 -4
- package/src/state/manifest.ts +3 -2
- package/src/state/providers.ts +3 -2
- package/src/sync.ts +23 -2
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
840
|
+
await writeFile3(ACTIVE_PROFILE_PATH, profileName, "utf-8");
|
|
832
841
|
}
|
|
833
842
|
async function clearActiveProfileState() {
|
|
834
843
|
if (existsSync9(ACTIVE_PROFILE_PATH)) {
|
|
835
|
-
|
|
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 {
|
|
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
|
|
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
|
|
1048
|
+
await writeFile4(lockPath, content, "utf-8");
|
|
1018
1049
|
}
|
|
1019
1050
|
async function getRepoCommit(repoPath) {
|
|
1020
|
-
const
|
|
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
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
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
|
|
1040
|
-
|
|
1041
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
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
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,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
|
|
46
|
+
const content = await readFile(filePath, "utf-8");
|
|
46
47
|
const parsed = parseFrontmatterWithMarkdown<CommandFrontmatter>(content);
|
|
47
48
|
|
|
48
49
|
if (!parsed) {
|
package/src/capability/docs.ts
CHANGED
|
@@ -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
|
|
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
|
|
37
|
+
const content = await readFile(docPath, "utf-8");
|
|
37
38
|
|
|
38
39
|
docs.push({
|
|
39
40
|
name: basename(entry.name, ".md"),
|
package/src/capability/loader.ts
CHANGED
|
@@ -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
|
|
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
|
|
120
|
+
return readFile(typesPath, "utf-8");
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
/**
|
package/src/capability/rules.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
89
|
+
await writeFile(instructionsPath, content, "utf-8");
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
function generateRulesContent(rules: Rule[], docs: Doc[] = []): string {
|
package/src/capability/skills.ts
CHANGED
|
@@ -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
|
|
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
|
|
223
|
+
const { exitCode, stdout, stderr } = await spawnCapture("git", ["rev-parse", "HEAD"], {
|
|
192
224
|
cwd: repoPath,
|
|
193
|
-
stdout: "pipe",
|
|
194
|
-
stderr: "pipe",
|
|
195
225
|
});
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
263
|
+
const fetchResult = await spawnCapture("git", ["fetch", "--depth", "1", "origin"], {
|
|
240
264
|
cwd: repoPath,
|
|
241
|
-
stdout: "pipe",
|
|
242
|
-
stderr: "pipe",
|
|
243
265
|
});
|
|
244
|
-
|
|
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
|
|
275
|
+
const lsResult = await spawnCapture("git", ["ls-remote", "origin", targetRef], {
|
|
252
276
|
cwd: repoPath,
|
|
253
|
-
stdout: "pipe",
|
|
254
|
-
stderr: "pipe",
|
|
255
277
|
});
|
|
256
|
-
|
|
257
|
-
|
|
278
|
+
if (lsResult.exitCode !== 0) {
|
|
279
|
+
throw new Error(`Failed to ls-remote in ${repoPath}: ${lsResult.stderr.trim()}`);
|
|
280
|
+
}
|
|
258
281
|
|
|
259
|
-
const remoteCommit =
|
|
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
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
|
989
|
+
const fetchResult = await spawnCapture("git", ["fetch", "--depth", "1", "origin"], {
|
|
969
990
|
cwd: targetPath,
|
|
970
|
-
stdout: "pipe",
|
|
971
|
-
stderr: "pipe",
|
|
972
991
|
});
|
|
973
|
-
|
|
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
|
|
998
|
+
const lsResult = await spawnCapture("git", ["ls-remote", "origin", targetRef], {
|
|
978
999
|
cwd: targetPath,
|
|
979
|
-
stdout: "pipe",
|
|
980
|
-
stderr: "pipe",
|
|
981
1000
|
});
|
|
982
|
-
|
|
983
|
-
|
|
1001
|
+
if (lsResult.exitCode !== 0) {
|
|
1002
|
+
throw new Error(`Failed to ls-remote in ${targetPath}: ${lsResult.stderr.trim()}`);
|
|
1003
|
+
}
|
|
984
1004
|
|
|
985
|
-
const remoteCommit =
|
|
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
|
|
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
|
|
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
|
package/src/config/loader.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
68
|
+
await writeFile(CONFIG_PATH, content, "utf-8");
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/**
|
package/src/config/provider.ts
CHANGED
|
@@ -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
|
|
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
|
|
44
|
+
await writeFile(PROVIDER_CONFIG_PATH, `${lines.join("\n")}\n`, "utf-8");
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export function parseProviderFlag(flag: string): Provider[] {
|
package/src/mcp-json/manager.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
39
|
-
await file.delete();
|
|
39
|
+
await unlink(ACTIVE_PROFILE_PATH);
|
|
40
40
|
}
|
|
41
41
|
}
|
package/src/state/manifest.ts
CHANGED
|
@@ -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
|
|
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
|
|
65
|
+
await writeFile(MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}\n`, "utf-8");
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/**
|
package/src/state/providers.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
//
|
|
80
|
+
// Prefer Bun if available, otherwise fallback to npm.
|
|
64
81
|
await new Promise<void>((resolve, reject) => {
|
|
65
|
-
const
|
|
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
|
});
|