@omnidev-ai/core 0.9.0 → 0.10.1
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.d.ts +65 -59
- package/dist/index.js +350 -319
- package/package.json +1 -1
- package/src/capability/index.ts +5 -1
- package/src/capability/loader.ts +2 -14
- package/src/capability/registry.ts +1 -3
- package/src/capability/rules.ts +2 -100
- package/src/capability/sources.ts +155 -9
- package/src/config/AGENTS.md +0 -11
- package/src/config/config.ts +6 -54
- package/src/config/index.ts +0 -1
- package/src/config/toml-patcher.ts +4 -6
- package/src/index.ts +1 -0
- package/src/sync.ts +1 -8
- package/src/templates/agents.ts +2 -2
- package/src/templates/capability.ts +167 -0
- package/src/templates/claude.ts +2 -45
- package/src/types/index.ts +24 -13
- package/src/config/env.ts +0 -97
package/dist/index.js
CHANGED
|
@@ -118,58 +118,10 @@ async function loadDocs(capabilityPath, capabilityId) {
|
|
|
118
118
|
return docs;
|
|
119
119
|
}
|
|
120
120
|
// src/capability/loader.ts
|
|
121
|
-
import { existsSync as
|
|
122
|
-
import { readFile as
|
|
121
|
+
import { existsSync as existsSync8, readdirSync as readdirSync6 } from "node:fs";
|
|
122
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
123
123
|
import { join as join7 } from "node:path";
|
|
124
124
|
|
|
125
|
-
// src/config/env.ts
|
|
126
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
127
|
-
import { readFile as readFile3 } from "node:fs/promises";
|
|
128
|
-
var ENV_FILE = ".omni/.env";
|
|
129
|
-
async function loadEnvironment() {
|
|
130
|
-
const env = {};
|
|
131
|
-
if (existsSync3(ENV_FILE)) {
|
|
132
|
-
const content = await readFile3(ENV_FILE, "utf-8");
|
|
133
|
-
for (const line of content.split(`
|
|
134
|
-
`)) {
|
|
135
|
-
const trimmed = line.trim();
|
|
136
|
-
if (trimmed && !trimmed.startsWith("#")) {
|
|
137
|
-
const eqIndex = trimmed.indexOf("=");
|
|
138
|
-
if (eqIndex > 0) {
|
|
139
|
-
const key = trimmed.slice(0, eqIndex).trim();
|
|
140
|
-
const value = trimmed.slice(eqIndex + 1).trim();
|
|
141
|
-
const unquotedValue = value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'") ? value.slice(1, -1) : value;
|
|
142
|
-
env[key] = unquotedValue;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
const processEnv = {};
|
|
148
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
149
|
-
if (value !== undefined) {
|
|
150
|
-
processEnv[key] = value;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return { ...env, ...processEnv };
|
|
154
|
-
}
|
|
155
|
-
function validateEnv(declarations, env, capabilityId) {
|
|
156
|
-
const missing = [];
|
|
157
|
-
for (const [key, decl] of Object.entries(declarations)) {
|
|
158
|
-
const declaration = decl;
|
|
159
|
-
const value = env[key] ?? declaration.default;
|
|
160
|
-
if (declaration.required && !value) {
|
|
161
|
-
missing.push(key);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
if (missing.length > 0) {
|
|
165
|
-
throw new Error(`Missing required environment variable${missing.length > 1 ? "s" : ""} for capability "${capabilityId}": ${missing.join(", ")}. ` + `Set ${missing.length > 1 ? "them" : "it"} in .omni/.env or as environment variable${missing.length > 1 ? "s" : ""}.`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
function isSecretEnvVar(key, declarations) {
|
|
169
|
-
const decl = declarations[key];
|
|
170
|
-
return decl?.secret === true;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
125
|
// src/config/parser.ts
|
|
174
126
|
import { parse } from "smol-toml";
|
|
175
127
|
function parseOmniConfig(tomlContent) {
|
|
@@ -208,7 +160,7 @@ function parseCapabilityConfig(tomlContent) {
|
|
|
208
160
|
}
|
|
209
161
|
|
|
210
162
|
// src/hooks/loader.ts
|
|
211
|
-
import { existsSync as
|
|
163
|
+
import { existsSync as existsSync4, readFileSync } from "node:fs";
|
|
212
164
|
import { join as join3 } from "node:path";
|
|
213
165
|
import { parse as parseToml } from "smol-toml";
|
|
214
166
|
|
|
@@ -274,7 +226,7 @@ var HOOKS_CONFIG_FILENAME = "hooks.toml";
|
|
|
274
226
|
var HOOKS_DIRECTORY = "hooks";
|
|
275
227
|
|
|
276
228
|
// src/hooks/validation.ts
|
|
277
|
-
import { existsSync as
|
|
229
|
+
import { existsSync as existsSync3, statSync } from "node:fs";
|
|
278
230
|
import { resolve } from "node:path";
|
|
279
231
|
|
|
280
232
|
// src/hooks/types.ts
|
|
@@ -590,7 +542,7 @@ function validateScriptInCommand(command, basePath, event, matcherIndex, hookInd
|
|
|
590
542
|
const relativePath = match[1];
|
|
591
543
|
if (relativePath) {
|
|
592
544
|
const fullPath = resolve(basePath, relativePath);
|
|
593
|
-
if (!
|
|
545
|
+
if (!existsSync3(fullPath)) {
|
|
594
546
|
issues.push({
|
|
595
547
|
severity: "error",
|
|
596
548
|
code: "HOOKS_SCRIPT_NOT_FOUND",
|
|
@@ -757,7 +709,7 @@ function loadHooksFromCapability(capabilityPath, options) {
|
|
|
757
709
|
};
|
|
758
710
|
const hooksDir = join3(capabilityPath, HOOKS_DIRECTORY);
|
|
759
711
|
const configPath = join3(hooksDir, HOOKS_CONFIG_FILENAME);
|
|
760
|
-
if (!
|
|
712
|
+
if (!existsSync4(configPath)) {
|
|
761
713
|
return {
|
|
762
714
|
config: createEmptyHooksConfig(),
|
|
763
715
|
validation: createEmptyValidationResult(),
|
|
@@ -844,7 +796,7 @@ function loadCapabilityHooks(capabilityName, capabilityPath, options) {
|
|
|
844
796
|
}
|
|
845
797
|
function hasHooks(capabilityPath) {
|
|
846
798
|
const configPath = join3(capabilityPath, HOOKS_DIRECTORY, HOOKS_CONFIG_FILENAME);
|
|
847
|
-
return
|
|
799
|
+
return existsSync4(configPath);
|
|
848
800
|
}
|
|
849
801
|
function getHooksDirectory(capabilityPath) {
|
|
850
802
|
return join3(capabilityPath, HOOKS_DIRECTORY);
|
|
@@ -854,12 +806,12 @@ function getHooksConfigPath(capabilityPath) {
|
|
|
854
806
|
}
|
|
855
807
|
|
|
856
808
|
// src/capability/rules.ts
|
|
857
|
-
import { existsSync as
|
|
858
|
-
import { readFile as
|
|
809
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3 } from "node:fs";
|
|
810
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
859
811
|
import { basename as basename2, join as join4 } from "node:path";
|
|
860
812
|
async function loadRules(capabilityPath, capabilityId) {
|
|
861
813
|
const rulesDir = join4(capabilityPath, "rules");
|
|
862
|
-
if (!
|
|
814
|
+
if (!existsSync5(rulesDir)) {
|
|
863
815
|
return [];
|
|
864
816
|
}
|
|
865
817
|
const rules = [];
|
|
@@ -867,7 +819,7 @@ async function loadRules(capabilityPath, capabilityId) {
|
|
|
867
819
|
for (const entry of entries) {
|
|
868
820
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
869
821
|
const rulePath = join4(rulesDir, entry.name);
|
|
870
|
-
const content = await
|
|
822
|
+
const content = await readFile3(rulePath, "utf-8");
|
|
871
823
|
rules.push({
|
|
872
824
|
name: basename2(entry.name, ".md"),
|
|
873
825
|
content: content.trim(),
|
|
@@ -877,88 +829,14 @@ async function loadRules(capabilityPath, capabilityId) {
|
|
|
877
829
|
}
|
|
878
830
|
return rules;
|
|
879
831
|
}
|
|
880
|
-
async function writeRules(rules, docs = []) {
|
|
881
|
-
const instructionsPath = ".omni/instructions.md";
|
|
882
|
-
const rulesContent = generateRulesContent(rules, docs);
|
|
883
|
-
let content;
|
|
884
|
-
if (existsSync6(instructionsPath)) {
|
|
885
|
-
content = await readFile4(instructionsPath, "utf-8");
|
|
886
|
-
} else {
|
|
887
|
-
content = `# OmniDev Instructions
|
|
888
|
-
|
|
889
|
-
## Project Description
|
|
890
|
-
<!-- TODO: Add 2-3 sentences describing your project -->
|
|
891
|
-
[Describe what this project does and its main purpose]
|
|
892
|
-
|
|
893
|
-
<!-- BEGIN OMNIDEV GENERATED CONTENT - DO NOT EDIT BELOW THIS LINE -->
|
|
894
|
-
<!-- END OMNIDEV GENERATED CONTENT -->
|
|
895
|
-
`;
|
|
896
|
-
}
|
|
897
|
-
const beginMarker = "<!-- BEGIN OMNIDEV GENERATED CONTENT - DO NOT EDIT BELOW THIS LINE -->";
|
|
898
|
-
const endMarker = "<!-- END OMNIDEV GENERATED CONTENT -->";
|
|
899
|
-
const beginIndex = content.indexOf(beginMarker);
|
|
900
|
-
const endIndex = content.indexOf(endMarker);
|
|
901
|
-
if (beginIndex === -1 || endIndex === -1) {
|
|
902
|
-
content += `
|
|
903
|
-
|
|
904
|
-
${beginMarker}
|
|
905
|
-
${rulesContent}
|
|
906
|
-
${endMarker}
|
|
907
|
-
`;
|
|
908
|
-
} else {
|
|
909
|
-
content = content.substring(0, beginIndex + beginMarker.length) + `
|
|
910
|
-
` + rulesContent + `
|
|
911
|
-
` + content.substring(endIndex);
|
|
912
|
-
}
|
|
913
|
-
await writeFile(instructionsPath, content, "utf-8");
|
|
914
|
-
}
|
|
915
|
-
function generateRulesContent(rules, docs = []) {
|
|
916
|
-
if (rules.length === 0 && docs.length === 0) {
|
|
917
|
-
return `<!-- This section is automatically updated when capabilities change -->
|
|
918
|
-
|
|
919
|
-
## Capabilities
|
|
920
|
-
|
|
921
|
-
No capabilities enabled yet. Run \`omnidev capability enable <name>\` to enable capabilities.`;
|
|
922
|
-
}
|
|
923
|
-
let content = `<!-- This section is automatically updated when capabilities change -->
|
|
924
|
-
|
|
925
|
-
## Capabilities
|
|
926
|
-
|
|
927
|
-
`;
|
|
928
|
-
if (docs.length > 0) {
|
|
929
|
-
content += `### Documentation
|
|
930
|
-
|
|
931
|
-
`;
|
|
932
|
-
for (const doc of docs) {
|
|
933
|
-
content += `#### ${doc.name} (from ${doc.capabilityId})
|
|
934
|
-
|
|
935
|
-
${doc.content}
|
|
936
|
-
|
|
937
|
-
`;
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
if (rules.length > 0) {
|
|
941
|
-
content += `### Rules
|
|
942
|
-
|
|
943
|
-
`;
|
|
944
|
-
for (const rule of rules) {
|
|
945
|
-
content += `#### ${rule.name} (from ${rule.capabilityId})
|
|
946
|
-
|
|
947
|
-
${rule.content}
|
|
948
|
-
|
|
949
|
-
`;
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
return content.trim();
|
|
953
|
-
}
|
|
954
832
|
|
|
955
833
|
// src/capability/skills.ts
|
|
956
|
-
import { existsSync as
|
|
957
|
-
import { readFile as
|
|
834
|
+
import { existsSync as existsSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
835
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
958
836
|
import { join as join5 } from "node:path";
|
|
959
837
|
async function loadSkills(capabilityPath, capabilityId) {
|
|
960
838
|
const skillsDir = join5(capabilityPath, "skills");
|
|
961
|
-
if (!
|
|
839
|
+
if (!existsSync6(skillsDir)) {
|
|
962
840
|
return [];
|
|
963
841
|
}
|
|
964
842
|
const skills = [];
|
|
@@ -966,7 +844,7 @@ async function loadSkills(capabilityPath, capabilityId) {
|
|
|
966
844
|
for (const entry of entries) {
|
|
967
845
|
if (entry.isDirectory()) {
|
|
968
846
|
const skillPath = join5(skillsDir, entry.name, "SKILL.md");
|
|
969
|
-
if (
|
|
847
|
+
if (existsSync6(skillPath)) {
|
|
970
848
|
const skill = await parseSkillFile(skillPath, capabilityId);
|
|
971
849
|
skills.push(skill);
|
|
972
850
|
}
|
|
@@ -975,7 +853,7 @@ async function loadSkills(capabilityPath, capabilityId) {
|
|
|
975
853
|
return skills;
|
|
976
854
|
}
|
|
977
855
|
async function parseSkillFile(filePath, capabilityId) {
|
|
978
|
-
const content = await
|
|
856
|
+
const content = await readFile4(filePath, "utf-8");
|
|
979
857
|
const parsed = parseFrontmatterWithMarkdown(content);
|
|
980
858
|
if (!parsed) {
|
|
981
859
|
throw new Error(`Invalid SKILL.md format at ${filePath}: missing YAML frontmatter`);
|
|
@@ -994,12 +872,12 @@ async function parseSkillFile(filePath, capabilityId) {
|
|
|
994
872
|
}
|
|
995
873
|
|
|
996
874
|
// src/capability/subagents.ts
|
|
997
|
-
import { existsSync as
|
|
998
|
-
import { readFile as
|
|
875
|
+
import { existsSync as existsSync7, readdirSync as readdirSync5 } from "node:fs";
|
|
876
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
999
877
|
import { join as join6 } from "node:path";
|
|
1000
878
|
async function loadSubagents(capabilityPath, capabilityId) {
|
|
1001
879
|
const subagentsDir = join6(capabilityPath, "subagents");
|
|
1002
|
-
if (!
|
|
880
|
+
if (!existsSync7(subagentsDir)) {
|
|
1003
881
|
return [];
|
|
1004
882
|
}
|
|
1005
883
|
const subagents = [];
|
|
@@ -1007,7 +885,7 @@ async function loadSubagents(capabilityPath, capabilityId) {
|
|
|
1007
885
|
for (const entry of entries) {
|
|
1008
886
|
if (entry.isDirectory()) {
|
|
1009
887
|
const subagentPath = join6(subagentsDir, entry.name, "SUBAGENT.md");
|
|
1010
|
-
if (
|
|
888
|
+
if (existsSync7(subagentPath)) {
|
|
1011
889
|
const subagent = await parseSubagentFile(subagentPath, capabilityId);
|
|
1012
890
|
subagents.push(subagent);
|
|
1013
891
|
}
|
|
@@ -1016,7 +894,7 @@ async function loadSubagents(capabilityPath, capabilityId) {
|
|
|
1016
894
|
return subagents;
|
|
1017
895
|
}
|
|
1018
896
|
async function parseSubagentFile(filePath, capabilityId) {
|
|
1019
|
-
const content = await
|
|
897
|
+
const content = await readFile5(filePath, "utf-8");
|
|
1020
898
|
const parsed = parseFrontmatterWithMarkdown(content);
|
|
1021
899
|
if (!parsed) {
|
|
1022
900
|
throw new Error(`Invalid SUBAGENT.md format at ${filePath}: missing YAML frontmatter`);
|
|
@@ -1060,13 +938,13 @@ function parseCommaSeparatedList(value) {
|
|
|
1060
938
|
var CAPABILITIES_DIR = ".omni/capabilities";
|
|
1061
939
|
async function discoverCapabilities() {
|
|
1062
940
|
const capabilities = [];
|
|
1063
|
-
if (
|
|
941
|
+
if (existsSync8(CAPABILITIES_DIR)) {
|
|
1064
942
|
const entries = readdirSync6(CAPABILITIES_DIR, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
1065
943
|
for (const entry of entries) {
|
|
1066
944
|
if (entry.isDirectory()) {
|
|
1067
945
|
const entryPath = join7(CAPABILITIES_DIR, entry.name);
|
|
1068
946
|
const configPath = join7(entryPath, "capability.toml");
|
|
1069
|
-
if (
|
|
947
|
+
if (existsSync8(configPath)) {
|
|
1070
948
|
capabilities.push(entryPath);
|
|
1071
949
|
}
|
|
1072
950
|
}
|
|
@@ -1076,13 +954,13 @@ async function discoverCapabilities() {
|
|
|
1076
954
|
}
|
|
1077
955
|
async function loadCapabilityConfig(capabilityPath) {
|
|
1078
956
|
const configPath = join7(capabilityPath, "capability.toml");
|
|
1079
|
-
const content = await
|
|
957
|
+
const content = await readFile6(configPath, "utf-8");
|
|
1080
958
|
const config = parseCapabilityConfig(content);
|
|
1081
959
|
return config;
|
|
1082
960
|
}
|
|
1083
961
|
async function importCapabilityExports(capabilityPath) {
|
|
1084
962
|
const indexPath = join7(capabilityPath, "index.ts");
|
|
1085
|
-
if (!
|
|
963
|
+
if (!existsSync8(indexPath)) {
|
|
1086
964
|
return {};
|
|
1087
965
|
}
|
|
1088
966
|
try {
|
|
@@ -1102,10 +980,10 @@ If this is a project-specific capability, install dependencies or remove it from
|
|
|
1102
980
|
}
|
|
1103
981
|
async function loadTypeDefinitions(capabilityPath) {
|
|
1104
982
|
const typesPath = join7(capabilityPath, "types.d.ts");
|
|
1105
|
-
if (!
|
|
983
|
+
if (!existsSync8(typesPath)) {
|
|
1106
984
|
return;
|
|
1107
985
|
}
|
|
1108
|
-
return
|
|
986
|
+
return readFile6(typesPath, "utf-8");
|
|
1109
987
|
}
|
|
1110
988
|
function convertSkillExports(skillExports, capabilityId) {
|
|
1111
989
|
return skillExports.map((skillExport) => {
|
|
@@ -1282,12 +1160,9 @@ function convertCommandExports(commandExports, capabilityId) {
|
|
|
1282
1160
|
return result;
|
|
1283
1161
|
});
|
|
1284
1162
|
}
|
|
1285
|
-
async function loadCapability(capabilityPath
|
|
1163
|
+
async function loadCapability(capabilityPath) {
|
|
1286
1164
|
const config = await loadCapabilityConfig(capabilityPath);
|
|
1287
1165
|
const id = config.capability.id;
|
|
1288
|
-
if (config.env) {
|
|
1289
|
-
validateEnv(config.env, env, id);
|
|
1290
|
-
}
|
|
1291
1166
|
const exports = await importCapabilityExports(capabilityPath);
|
|
1292
1167
|
const exportsAny = exports;
|
|
1293
1168
|
const skills = "skills" in exports && Array.isArray(exportsAny.skills) ? convertSkillExports(exportsAny.skills, id) : await loadSkills(capabilityPath, id);
|
|
@@ -1322,13 +1197,12 @@ async function loadCapability(capabilityPath, env) {
|
|
|
1322
1197
|
return result;
|
|
1323
1198
|
}
|
|
1324
1199
|
// src/config/config.ts
|
|
1325
|
-
import { existsSync as
|
|
1326
|
-
import { readFile as
|
|
1200
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
1201
|
+
import { readFile as readFile7, writeFile } from "node:fs/promises";
|
|
1327
1202
|
var CONFIG_PATH = "omni.toml";
|
|
1328
1203
|
var LOCAL_CONFIG = "omni.local.toml";
|
|
1329
1204
|
function mergeConfigs(base, override) {
|
|
1330
1205
|
const merged = { ...base, ...override };
|
|
1331
|
-
merged.env = { ...base.env, ...override.env };
|
|
1332
1206
|
merged.profiles = { ...base.profiles };
|
|
1333
1207
|
for (const [name, profile] of Object.entries(override.profiles || {})) {
|
|
1334
1208
|
merged.profiles[name] = {
|
|
@@ -1342,8 +1216,8 @@ function mergeConfigs(base, override) {
|
|
|
1342
1216
|
return merged;
|
|
1343
1217
|
}
|
|
1344
1218
|
async function loadBaseConfig() {
|
|
1345
|
-
if (
|
|
1346
|
-
const content = await
|
|
1219
|
+
if (existsSync9(CONFIG_PATH)) {
|
|
1220
|
+
const content = await readFile7(CONFIG_PATH, "utf-8");
|
|
1347
1221
|
return parseOmniConfig(content);
|
|
1348
1222
|
}
|
|
1349
1223
|
return {};
|
|
@@ -1351,34 +1225,18 @@ async function loadBaseConfig() {
|
|
|
1351
1225
|
async function loadConfig() {
|
|
1352
1226
|
const baseConfig = await loadBaseConfig();
|
|
1353
1227
|
let localConfig = {};
|
|
1354
|
-
if (
|
|
1355
|
-
const content = await
|
|
1228
|
+
if (existsSync9(LOCAL_CONFIG)) {
|
|
1229
|
+
const content = await readFile7(LOCAL_CONFIG, "utf-8");
|
|
1356
1230
|
localConfig = parseOmniConfig(content);
|
|
1357
1231
|
}
|
|
1358
1232
|
return mergeConfigs(baseConfig, localConfig);
|
|
1359
1233
|
}
|
|
1360
1234
|
async function writeConfig(config) {
|
|
1361
1235
|
const content = generateConfigToml(config);
|
|
1362
|
-
await
|
|
1236
|
+
await writeFile(CONFIG_PATH, content, "utf-8");
|
|
1363
1237
|
}
|
|
1364
1238
|
function generateConfigToml(config) {
|
|
1365
1239
|
const lines = [];
|
|
1366
|
-
lines.push("# =============================================================================");
|
|
1367
|
-
lines.push("# OmniDev Configuration");
|
|
1368
|
-
lines.push("# =============================================================================");
|
|
1369
|
-
lines.push("# This file defines your project's capabilities, profiles, and settings.");
|
|
1370
|
-
lines.push("#");
|
|
1371
|
-
lines.push("# Files:");
|
|
1372
|
-
lines.push("# • omni.toml - Main config (commit to share with team)");
|
|
1373
|
-
lines.push("# • omni.local.toml - Local overrides (add to .gitignore)");
|
|
1374
|
-
lines.push("# • omni.lock.toml - Version lock file (commit for reproducibility)");
|
|
1375
|
-
lines.push("#");
|
|
1376
|
-
lines.push("# Quick start:");
|
|
1377
|
-
lines.push("# 1. Add capability sources to [capabilities.sources]");
|
|
1378
|
-
lines.push("# 2. Reference them in your profiles");
|
|
1379
|
-
lines.push("# 3. Run: omnidev sync");
|
|
1380
|
-
lines.push("# 4. Switch profiles: omnidev profile use <name>");
|
|
1381
|
-
lines.push("");
|
|
1382
1240
|
if (config.providers?.enabled && config.providers.enabled.length > 0) {
|
|
1383
1241
|
lines.push("# AI providers to enable (claude, codex, or both)");
|
|
1384
1242
|
lines.push("[providers]");
|
|
@@ -1386,23 +1244,6 @@ function generateConfigToml(config) {
|
|
|
1386
1244
|
lines.push("");
|
|
1387
1245
|
}
|
|
1388
1246
|
lines.push("# =============================================================================");
|
|
1389
|
-
lines.push("# Environment Variables");
|
|
1390
|
-
lines.push("# =============================================================================");
|
|
1391
|
-
lines.push("# Global environment variables available to all capabilities.");
|
|
1392
|
-
lines.push("# Use ${VAR_NAME} syntax to reference shell environment variables.");
|
|
1393
|
-
lines.push("#");
|
|
1394
|
-
if (config.env && Object.keys(config.env).length > 0) {
|
|
1395
|
-
lines.push("[env]");
|
|
1396
|
-
for (const [key, value] of Object.entries(config.env)) {
|
|
1397
|
-
lines.push(`${key} = "${value}"`);
|
|
1398
|
-
}
|
|
1399
|
-
} else {
|
|
1400
|
-
lines.push("# [env]");
|
|
1401
|
-
lines.push('# DATABASE_URL = "${DATABASE_URL}"');
|
|
1402
|
-
lines.push('# API_KEY = "${MY_API_KEY}"');
|
|
1403
|
-
}
|
|
1404
|
-
lines.push("");
|
|
1405
|
-
lines.push("# =============================================================================");
|
|
1406
1247
|
lines.push("# Capability Sources");
|
|
1407
1248
|
lines.push("# =============================================================================");
|
|
1408
1249
|
lines.push("# Fetch capabilities from Git repositories. On sync, these are");
|
|
@@ -1414,7 +1255,7 @@ function generateConfigToml(config) {
|
|
|
1414
1255
|
for (const [name, sourceConfig] of Object.entries(sources)) {
|
|
1415
1256
|
if (typeof sourceConfig === "string") {
|
|
1416
1257
|
lines.push(`${name} = "${sourceConfig}"`);
|
|
1417
|
-
} else if (sourceConfig.path) {
|
|
1258
|
+
} else if ("path" in sourceConfig && sourceConfig.path) {
|
|
1418
1259
|
lines.push(`${name} = { source = "${sourceConfig.source}", path = "${sourceConfig.path}" }`);
|
|
1419
1260
|
} else {
|
|
1420
1261
|
lines.push(`${name} = "${sourceConfig.source}"`);
|
|
@@ -1482,10 +1323,8 @@ function generateConfigToml(config) {
|
|
|
1482
1323
|
lines.push(`url = "${mcpConfig.url}"`);
|
|
1483
1324
|
}
|
|
1484
1325
|
if (mcpConfig.env && Object.keys(mcpConfig.env).length > 0) {
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
lines.push(`${key} = "${value}"`);
|
|
1488
|
-
}
|
|
1326
|
+
const entries = Object.entries(mcpConfig.env).map(([key, value]) => `${key} = "${value}"`);
|
|
1327
|
+
lines.push(`env = { ${entries.join(", ")} }`);
|
|
1489
1328
|
}
|
|
1490
1329
|
if (mcpConfig.headers && Object.keys(mcpConfig.headers).length > 0) {
|
|
1491
1330
|
lines.push(`[mcps.${name}.headers]`);
|
|
@@ -1505,8 +1344,7 @@ function generateConfigToml(config) {
|
|
|
1505
1344
|
lines.push('# command = "node"');
|
|
1506
1345
|
lines.push('# args = ["./servers/database.js"]');
|
|
1507
1346
|
lines.push('# cwd = "./mcp-servers"');
|
|
1508
|
-
lines.push(
|
|
1509
|
-
lines.push('# DB_URL = "${DATABASE_URL}"');
|
|
1347
|
+
lines.push('# env = { DB_URL = "${DATABASE_URL}" }');
|
|
1510
1348
|
lines.push("");
|
|
1511
1349
|
}
|
|
1512
1350
|
lines.push("# =============================================================================");
|
|
@@ -1545,16 +1383,16 @@ function generateConfigToml(config) {
|
|
|
1545
1383
|
}
|
|
1546
1384
|
|
|
1547
1385
|
// src/state/active-profile.ts
|
|
1548
|
-
import { existsSync as
|
|
1549
|
-
import { readFile as
|
|
1386
|
+
import { existsSync as existsSync10, mkdirSync } from "node:fs";
|
|
1387
|
+
import { readFile as readFile8, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
1550
1388
|
var STATE_DIR = ".omni/state";
|
|
1551
1389
|
var ACTIVE_PROFILE_PATH = `${STATE_DIR}/active-profile`;
|
|
1552
1390
|
async function readActiveProfileState() {
|
|
1553
|
-
if (!
|
|
1391
|
+
if (!existsSync10(ACTIVE_PROFILE_PATH)) {
|
|
1554
1392
|
return null;
|
|
1555
1393
|
}
|
|
1556
1394
|
try {
|
|
1557
|
-
const content = await
|
|
1395
|
+
const content = await readFile8(ACTIVE_PROFILE_PATH, "utf-8");
|
|
1558
1396
|
const trimmed = content.trim();
|
|
1559
1397
|
return trimmed || null;
|
|
1560
1398
|
} catch {
|
|
@@ -1563,10 +1401,10 @@ async function readActiveProfileState() {
|
|
|
1563
1401
|
}
|
|
1564
1402
|
async function writeActiveProfileState(profileName) {
|
|
1565
1403
|
mkdirSync(STATE_DIR, { recursive: true });
|
|
1566
|
-
await
|
|
1404
|
+
await writeFile2(ACTIVE_PROFILE_PATH, profileName, "utf-8");
|
|
1567
1405
|
}
|
|
1568
1406
|
async function clearActiveProfileState() {
|
|
1569
|
-
if (
|
|
1407
|
+
if (existsSync10(ACTIVE_PROFILE_PATH)) {
|
|
1570
1408
|
await unlink(ACTIVE_PROFILE_PATH);
|
|
1571
1409
|
}
|
|
1572
1410
|
}
|
|
@@ -1740,13 +1578,12 @@ function getEventsWithHooks(config) {
|
|
|
1740
1578
|
|
|
1741
1579
|
// src/capability/registry.ts
|
|
1742
1580
|
async function buildCapabilityRegistry() {
|
|
1743
|
-
const env = await loadEnvironment();
|
|
1744
1581
|
const enabledIds = await getEnabledCapabilities();
|
|
1745
1582
|
const capabilityPaths = await discoverCapabilities();
|
|
1746
1583
|
const capabilities = new Map;
|
|
1747
1584
|
for (const path of capabilityPaths) {
|
|
1748
1585
|
try {
|
|
1749
|
-
const cap = await loadCapability(path
|
|
1586
|
+
const cap = await loadCapability(path);
|
|
1750
1587
|
if (enabledIds.includes(cap.id)) {
|
|
1751
1588
|
capabilities.set(cap.id, cap);
|
|
1752
1589
|
}
|
|
@@ -1777,11 +1614,28 @@ async function buildCapabilityRegistry() {
|
|
|
1777
1614
|
};
|
|
1778
1615
|
}
|
|
1779
1616
|
// src/capability/sources.ts
|
|
1780
|
-
import { existsSync as
|
|
1617
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
1781
1618
|
import { spawn } from "node:child_process";
|
|
1782
|
-
import { cp, mkdir, readdir, readFile as
|
|
1619
|
+
import { cp, mkdir, readdir, readFile as readFile9, rename, rm, stat, writeFile as writeFile3 } from "node:fs/promises";
|
|
1783
1620
|
import { join as join8 } from "node:path";
|
|
1784
1621
|
import { parse as parseToml2 } from "smol-toml";
|
|
1622
|
+
|
|
1623
|
+
// src/types/index.ts
|
|
1624
|
+
function isFileSourceConfig(config) {
|
|
1625
|
+
if (typeof config === "string") {
|
|
1626
|
+
return config.startsWith("file://");
|
|
1627
|
+
}
|
|
1628
|
+
return config.source.startsWith("file://");
|
|
1629
|
+
}
|
|
1630
|
+
function getActiveProviders(config) {
|
|
1631
|
+
if (config.providers)
|
|
1632
|
+
return config.providers;
|
|
1633
|
+
if (config.provider)
|
|
1634
|
+
return [config.provider];
|
|
1635
|
+
return ["claude"];
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// src/capability/sources.ts
|
|
1785
1639
|
var OMNI_LOCAL = ".omni";
|
|
1786
1640
|
var SKILL_DIRS = ["skills", "skill"];
|
|
1787
1641
|
var AGENT_DIRS = ["agents", "agent", "subagents", "subagent"];
|
|
@@ -1813,8 +1667,39 @@ async function spawnCapture(command, args, options) {
|
|
|
1813
1667
|
});
|
|
1814
1668
|
});
|
|
1815
1669
|
}
|
|
1670
|
+
function isGitSource(source) {
|
|
1671
|
+
return source.startsWith("github:") || source.startsWith("git@") || source.startsWith("https://") || source.startsWith("http://");
|
|
1672
|
+
}
|
|
1673
|
+
function isFileSource(source) {
|
|
1674
|
+
return source.startsWith("file://");
|
|
1675
|
+
}
|
|
1676
|
+
function parseFileSourcePath(source) {
|
|
1677
|
+
if (!source.startsWith("file://")) {
|
|
1678
|
+
throw new Error(`Invalid file source: ${source}`);
|
|
1679
|
+
}
|
|
1680
|
+
return source.slice(7);
|
|
1681
|
+
}
|
|
1682
|
+
async function readCapabilityIdFromPath(capabilityPath) {
|
|
1683
|
+
const tomlPath = join8(capabilityPath, "capability.toml");
|
|
1684
|
+
if (existsSync11(tomlPath)) {
|
|
1685
|
+
try {
|
|
1686
|
+
const content = await readFile9(tomlPath, "utf-8");
|
|
1687
|
+
const parsed = parseToml2(content);
|
|
1688
|
+
const capability = parsed["capability"];
|
|
1689
|
+
if (capability?.["id"] && typeof capability["id"] === "string") {
|
|
1690
|
+
return capability["id"];
|
|
1691
|
+
}
|
|
1692
|
+
} catch {}
|
|
1693
|
+
}
|
|
1694
|
+
const parts = capabilityPath.replace(/\\/g, "/").split("/");
|
|
1695
|
+
const dirName = parts.pop() || parts.pop();
|
|
1696
|
+
return dirName || null;
|
|
1697
|
+
}
|
|
1816
1698
|
function parseSourceConfig(source) {
|
|
1817
1699
|
if (typeof source === "string") {
|
|
1700
|
+
if (isFileSource(source)) {
|
|
1701
|
+
return { source };
|
|
1702
|
+
}
|
|
1818
1703
|
let sourceUrl = source;
|
|
1819
1704
|
let ref;
|
|
1820
1705
|
if (source.startsWith("github:") && source.includes("#")) {
|
|
@@ -1828,6 +1713,9 @@ function parseSourceConfig(source) {
|
|
|
1828
1713
|
}
|
|
1829
1714
|
return result;
|
|
1830
1715
|
}
|
|
1716
|
+
if (isFileSourceConfig(source)) {
|
|
1717
|
+
return source;
|
|
1718
|
+
}
|
|
1831
1719
|
return source;
|
|
1832
1720
|
}
|
|
1833
1721
|
function sourceToGitUrl(source) {
|
|
@@ -1845,11 +1733,11 @@ function getLockFilePath() {
|
|
|
1845
1733
|
}
|
|
1846
1734
|
async function loadLockFile() {
|
|
1847
1735
|
const lockPath = getLockFilePath();
|
|
1848
|
-
if (!
|
|
1736
|
+
if (!existsSync11(lockPath)) {
|
|
1849
1737
|
return { capabilities: {} };
|
|
1850
1738
|
}
|
|
1851
1739
|
try {
|
|
1852
|
-
const content = await
|
|
1740
|
+
const content = await readFile9(lockPath, "utf-8");
|
|
1853
1741
|
const parsed = parseToml2(content);
|
|
1854
1742
|
const capabilities = parsed["capabilities"];
|
|
1855
1743
|
return {
|
|
@@ -1886,7 +1774,7 @@ async function saveLockFile(lockFile) {
|
|
|
1886
1774
|
|
|
1887
1775
|
`;
|
|
1888
1776
|
const content = header + stringifyLockFile(lockFile);
|
|
1889
|
-
await
|
|
1777
|
+
await writeFile3(lockPath, content, "utf-8");
|
|
1890
1778
|
}
|
|
1891
1779
|
async function getRepoCommit(repoPath) {
|
|
1892
1780
|
const { exitCode, stdout, stderr } = await spawnCapture("git", ["rev-parse", "HEAD"], {
|
|
@@ -1938,16 +1826,16 @@ async function fetchRepo(repoPath, ref) {
|
|
|
1938
1826
|
return true;
|
|
1939
1827
|
}
|
|
1940
1828
|
function hasCapabilityToml(dirPath) {
|
|
1941
|
-
return
|
|
1829
|
+
return existsSync11(join8(dirPath, "capability.toml"));
|
|
1942
1830
|
}
|
|
1943
1831
|
async function shouldWrapDirectory(dirPath) {
|
|
1944
|
-
if (
|
|
1832
|
+
if (existsSync11(join8(dirPath, ".claude-plugin", "plugin.json"))) {
|
|
1945
1833
|
return true;
|
|
1946
1834
|
}
|
|
1947
1835
|
const allDirs = [...SKILL_DIRS, ...AGENT_DIRS, ...COMMAND_DIRS, ...RULE_DIRS, ...DOC_DIRS];
|
|
1948
1836
|
for (const dirName of allDirs) {
|
|
1949
1837
|
const checkPath = join8(dirPath, dirName);
|
|
1950
|
-
if (
|
|
1838
|
+
if (existsSync11(checkPath)) {
|
|
1951
1839
|
const stats = await stat(checkPath);
|
|
1952
1840
|
if (stats.isDirectory()) {
|
|
1953
1841
|
return true;
|
|
@@ -1959,7 +1847,7 @@ async function shouldWrapDirectory(dirPath) {
|
|
|
1959
1847
|
async function findMatchingDirs(basePath, names) {
|
|
1960
1848
|
for (const name of names) {
|
|
1961
1849
|
const dirPath = join8(basePath, name);
|
|
1962
|
-
if (
|
|
1850
|
+
if (existsSync11(dirPath)) {
|
|
1963
1851
|
const stats = await stat(dirPath);
|
|
1964
1852
|
if (stats.isDirectory()) {
|
|
1965
1853
|
return dirPath;
|
|
@@ -1970,7 +1858,7 @@ async function findMatchingDirs(basePath, names) {
|
|
|
1970
1858
|
}
|
|
1971
1859
|
async function findContentItems(dirPath, filePatterns) {
|
|
1972
1860
|
const items = [];
|
|
1973
|
-
if (!
|
|
1861
|
+
if (!existsSync11(dirPath)) {
|
|
1974
1862
|
return items;
|
|
1975
1863
|
}
|
|
1976
1864
|
const entries = (await readdir(dirPath, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -1978,7 +1866,7 @@ async function findContentItems(dirPath, filePatterns) {
|
|
|
1978
1866
|
const entryPath = join8(dirPath, entry.name);
|
|
1979
1867
|
if (entry.isDirectory()) {
|
|
1980
1868
|
for (const pattern of filePatterns) {
|
|
1981
|
-
if (
|
|
1869
|
+
if (existsSync11(join8(entryPath, pattern))) {
|
|
1982
1870
|
items.push({
|
|
1983
1871
|
name: entry.name,
|
|
1984
1872
|
path: entryPath,
|
|
@@ -2000,11 +1888,11 @@ async function findContentItems(dirPath, filePatterns) {
|
|
|
2000
1888
|
}
|
|
2001
1889
|
async function parsePluginJson(dirPath) {
|
|
2002
1890
|
const pluginJsonPath = join8(dirPath, ".claude-plugin", "plugin.json");
|
|
2003
|
-
if (!
|
|
1891
|
+
if (!existsSync11(pluginJsonPath)) {
|
|
2004
1892
|
return null;
|
|
2005
1893
|
}
|
|
2006
1894
|
try {
|
|
2007
|
-
const content = await
|
|
1895
|
+
const content = await readFile9(pluginJsonPath, "utf-8");
|
|
2008
1896
|
const data = JSON.parse(content);
|
|
2009
1897
|
const result = {
|
|
2010
1898
|
name: data.name,
|
|
@@ -2025,11 +1913,11 @@ async function parsePluginJson(dirPath) {
|
|
|
2025
1913
|
}
|
|
2026
1914
|
async function readReadmeDescription(dirPath) {
|
|
2027
1915
|
const readmePath = join8(dirPath, "README.md");
|
|
2028
|
-
if (!
|
|
1916
|
+
if (!existsSync11(readmePath)) {
|
|
2029
1917
|
return null;
|
|
2030
1918
|
}
|
|
2031
1919
|
try {
|
|
2032
|
-
const content = await
|
|
1920
|
+
const content = await readFile9(readmePath, "utf-8");
|
|
2033
1921
|
const lines = content.split(`
|
|
2034
1922
|
`);
|
|
2035
1923
|
let description = "";
|
|
@@ -2068,7 +1956,7 @@ async function normalizeFolderNames(repoPath) {
|
|
|
2068
1956
|
for (const { from, to } of renameMappings) {
|
|
2069
1957
|
const fromPath = join8(repoPath, from);
|
|
2070
1958
|
const toPath = join8(repoPath, to);
|
|
2071
|
-
if (
|
|
1959
|
+
if (existsSync11(fromPath) && !existsSync11(toPath)) {
|
|
2072
1960
|
try {
|
|
2073
1961
|
const stats = await stat(fromPath);
|
|
2074
1962
|
if (stats.isDirectory()) {
|
|
@@ -2157,7 +2045,7 @@ repository = "${repoUrl}"
|
|
|
2157
2045
|
wrapped = true
|
|
2158
2046
|
commit = "${commit}"
|
|
2159
2047
|
`;
|
|
2160
|
-
await
|
|
2048
|
+
await writeFile3(join8(repoPath, "capability.toml"), tomlContent, "utf-8");
|
|
2161
2049
|
}
|
|
2162
2050
|
async function fetchGitCapabilitySource(id, config, options) {
|
|
2163
2051
|
const gitUrl = sourceToGitUrl(config.source);
|
|
@@ -2167,7 +2055,7 @@ async function fetchGitCapabilitySource(id, config, options) {
|
|
|
2167
2055
|
let repoPath;
|
|
2168
2056
|
if (config.path) {
|
|
2169
2057
|
const tempPath = join8(OMNI_LOCAL, "_temp", `${id}-repo`);
|
|
2170
|
-
if (
|
|
2058
|
+
if (existsSync11(join8(tempPath, ".git"))) {
|
|
2171
2059
|
if (!options?.silent) {
|
|
2172
2060
|
console.log(` Checking ${id}...`);
|
|
2173
2061
|
}
|
|
@@ -2183,17 +2071,17 @@ async function fetchGitCapabilitySource(id, config, options) {
|
|
|
2183
2071
|
updated = true;
|
|
2184
2072
|
}
|
|
2185
2073
|
const sourcePath = join8(tempPath, config.path);
|
|
2186
|
-
if (!
|
|
2074
|
+
if (!existsSync11(sourcePath)) {
|
|
2187
2075
|
throw new Error(`Path not found in repository: ${config.path}`);
|
|
2188
2076
|
}
|
|
2189
|
-
if (
|
|
2077
|
+
if (existsSync11(targetPath)) {
|
|
2190
2078
|
await rm(targetPath, { recursive: true });
|
|
2191
2079
|
}
|
|
2192
2080
|
await mkdir(join8(targetPath, ".."), { recursive: true });
|
|
2193
2081
|
await cp(sourcePath, targetPath, { recursive: true });
|
|
2194
2082
|
repoPath = targetPath;
|
|
2195
2083
|
} else {
|
|
2196
|
-
if (
|
|
2084
|
+
if (existsSync11(join8(targetPath, ".git"))) {
|
|
2197
2085
|
if (!options?.silent) {
|
|
2198
2086
|
console.log(` Checking ${id}...`);
|
|
2199
2087
|
}
|
|
@@ -2232,9 +2120,9 @@ async function fetchGitCapabilitySource(id, config, options) {
|
|
|
2232
2120
|
}
|
|
2233
2121
|
let version = shortCommit(commit);
|
|
2234
2122
|
const pkgJsonPath = join8(repoPath, "package.json");
|
|
2235
|
-
if (
|
|
2123
|
+
if (existsSync11(pkgJsonPath)) {
|
|
2236
2124
|
try {
|
|
2237
|
-
const pkgJson = JSON.parse(await
|
|
2125
|
+
const pkgJson = JSON.parse(await readFile9(pkgJsonPath, "utf-8"));
|
|
2238
2126
|
if (pkgJson.version) {
|
|
2239
2127
|
version = pkgJson.version;
|
|
2240
2128
|
}
|
|
@@ -2249,8 +2137,52 @@ async function fetchGitCapabilitySource(id, config, options) {
|
|
|
2249
2137
|
wrapped: needsWrap
|
|
2250
2138
|
};
|
|
2251
2139
|
}
|
|
2140
|
+
async function fetchFileCapabilitySource(id, config, options) {
|
|
2141
|
+
const sourcePath = parseFileSourcePath(config.source);
|
|
2142
|
+
const targetPath = getSourceCapabilityPath(id);
|
|
2143
|
+
if (!existsSync11(sourcePath)) {
|
|
2144
|
+
throw new Error(`File source not found: ${sourcePath}`);
|
|
2145
|
+
}
|
|
2146
|
+
const sourceStats = await stat(sourcePath);
|
|
2147
|
+
if (!sourceStats.isDirectory()) {
|
|
2148
|
+
throw new Error(`File source must be a directory: ${sourcePath}`);
|
|
2149
|
+
}
|
|
2150
|
+
if (!existsSync11(join8(sourcePath, "capability.toml"))) {
|
|
2151
|
+
throw new Error(`No capability.toml found in: ${sourcePath}`);
|
|
2152
|
+
}
|
|
2153
|
+
if (!options?.silent) {
|
|
2154
|
+
console.log(` Copying ${id} from ${sourcePath}...`);
|
|
2155
|
+
}
|
|
2156
|
+
if (existsSync11(targetPath)) {
|
|
2157
|
+
await rm(targetPath, { recursive: true });
|
|
2158
|
+
}
|
|
2159
|
+
await mkdir(join8(targetPath, ".."), { recursive: true });
|
|
2160
|
+
await cp(sourcePath, targetPath, { recursive: true });
|
|
2161
|
+
let version = "local";
|
|
2162
|
+
const capTomlPath = join8(targetPath, "capability.toml");
|
|
2163
|
+
if (existsSync11(capTomlPath)) {
|
|
2164
|
+
try {
|
|
2165
|
+
const content = await readFile9(capTomlPath, "utf-8");
|
|
2166
|
+
const parsed = parseToml2(content);
|
|
2167
|
+
const capability = parsed["capability"];
|
|
2168
|
+
if (capability?.["version"] && typeof capability["version"] === "string") {
|
|
2169
|
+
version = capability["version"];
|
|
2170
|
+
}
|
|
2171
|
+
} catch {}
|
|
2172
|
+
}
|
|
2173
|
+
return {
|
|
2174
|
+
id,
|
|
2175
|
+
path: targetPath,
|
|
2176
|
+
version,
|
|
2177
|
+
updated: true,
|
|
2178
|
+
wrapped: false
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2252
2181
|
async function fetchCapabilitySource(id, sourceConfig, options) {
|
|
2253
2182
|
const config = parseSourceConfig(sourceConfig);
|
|
2183
|
+
if (isFileSourceConfig(sourceConfig) || isFileSource(config.source)) {
|
|
2184
|
+
return fetchFileCapabilitySource(id, config, options);
|
|
2185
|
+
}
|
|
2254
2186
|
return fetchGitCapabilitySource(id, config, options);
|
|
2255
2187
|
}
|
|
2256
2188
|
function generateMcpCapabilityTomlContent(id, mcpConfig) {
|
|
@@ -2317,16 +2249,16 @@ generated_from_omni_toml = true
|
|
|
2317
2249
|
}
|
|
2318
2250
|
async function generateMcpCapabilityToml(id, mcpConfig, targetPath) {
|
|
2319
2251
|
const tomlContent = generateMcpCapabilityTomlContent(id, mcpConfig);
|
|
2320
|
-
await
|
|
2252
|
+
await writeFile3(join8(targetPath, "capability.toml"), tomlContent, "utf-8");
|
|
2321
2253
|
}
|
|
2322
2254
|
async function isGeneratedMcpCapability(capabilityDir) {
|
|
2323
2255
|
const tomlPath = join8(capabilityDir, "capability.toml");
|
|
2324
|
-
if (!
|
|
2256
|
+
if (!existsSync11(tomlPath)) {
|
|
2325
2257
|
console.warn("no capability.toml found in", capabilityDir);
|
|
2326
2258
|
return false;
|
|
2327
2259
|
}
|
|
2328
2260
|
try {
|
|
2329
|
-
const content = await
|
|
2261
|
+
const content = await readFile9(tomlPath, "utf-8");
|
|
2330
2262
|
const parsed = parseToml2(content);
|
|
2331
2263
|
const capability = parsed["capability"];
|
|
2332
2264
|
const metadata = capability?.["metadata"];
|
|
@@ -2337,7 +2269,7 @@ async function isGeneratedMcpCapability(capabilityDir) {
|
|
|
2337
2269
|
}
|
|
2338
2270
|
async function cleanupStaleMcpCapabilities(currentMcpIds) {
|
|
2339
2271
|
const capabilitiesDir = join8(OMNI_LOCAL, "capabilities");
|
|
2340
|
-
if (!
|
|
2272
|
+
if (!existsSync11(capabilitiesDir)) {
|
|
2341
2273
|
return;
|
|
2342
2274
|
}
|
|
2343
2275
|
const entries = await readdir(capabilitiesDir, { withFileTypes: true });
|
|
@@ -2387,12 +2319,14 @@ async function fetchAllCapabilitySources(config, options) {
|
|
|
2387
2319
|
version: result.version,
|
|
2388
2320
|
updated_at: new Date().toISOString()
|
|
2389
2321
|
};
|
|
2390
|
-
const gitConfig = parseSourceConfig(source);
|
|
2391
2322
|
if (result.commit) {
|
|
2392
2323
|
lockEntry.commit = result.commit;
|
|
2393
2324
|
}
|
|
2394
|
-
if (
|
|
2395
|
-
|
|
2325
|
+
if (!isFileSourceConfig(source)) {
|
|
2326
|
+
const gitConfig = parseSourceConfig(source);
|
|
2327
|
+
if (gitConfig.ref) {
|
|
2328
|
+
lockEntry.ref = gitConfig.ref;
|
|
2329
|
+
}
|
|
2396
2330
|
}
|
|
2397
2331
|
const existing = lockFile.capabilities[id];
|
|
2398
2332
|
const hasChanged = !existing || existing.commit !== result.commit;
|
|
@@ -2432,8 +2366,18 @@ async function checkForUpdates(config) {
|
|
|
2432
2366
|
const sourceConfig = parseSourceConfig(source);
|
|
2433
2367
|
const targetPath = getSourceCapabilityPath(id);
|
|
2434
2368
|
const existing = lockFile.capabilities[id];
|
|
2369
|
+
if (isFileSourceConfig(source) || isFileSource(sourceConfig.source)) {
|
|
2370
|
+
updates.push({
|
|
2371
|
+
id,
|
|
2372
|
+
source: sourceConfig.source,
|
|
2373
|
+
currentVersion: existing?.version || "local",
|
|
2374
|
+
latestVersion: "local",
|
|
2375
|
+
hasUpdate: false
|
|
2376
|
+
});
|
|
2377
|
+
continue;
|
|
2378
|
+
}
|
|
2435
2379
|
const gitConfig = sourceConfig;
|
|
2436
|
-
if (!
|
|
2380
|
+
if (!existsSync11(join8(targetPath, ".git"))) {
|
|
2437
2381
|
updates.push({
|
|
2438
2382
|
id,
|
|
2439
2383
|
source: gitConfig.source,
|
|
@@ -2469,15 +2413,15 @@ async function checkForUpdates(config) {
|
|
|
2469
2413
|
return updates;
|
|
2470
2414
|
}
|
|
2471
2415
|
// src/config/provider.ts
|
|
2472
|
-
import { existsSync as
|
|
2473
|
-
import { readFile as
|
|
2416
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
2417
|
+
import { readFile as readFile10, writeFile as writeFile4 } from "node:fs/promises";
|
|
2474
2418
|
import { parse as parse2 } from "smol-toml";
|
|
2475
2419
|
var PROVIDER_CONFIG_PATH = ".omni/provider.toml";
|
|
2476
2420
|
async function loadProviderConfig() {
|
|
2477
|
-
if (!
|
|
2421
|
+
if (!existsSync12(PROVIDER_CONFIG_PATH)) {
|
|
2478
2422
|
return { provider: "claude" };
|
|
2479
2423
|
}
|
|
2480
|
-
const content = await
|
|
2424
|
+
const content = await readFile10(PROVIDER_CONFIG_PATH, "utf-8");
|
|
2481
2425
|
const parsed = parse2(content);
|
|
2482
2426
|
return parsed;
|
|
2483
2427
|
}
|
|
@@ -2504,7 +2448,7 @@ async function writeProviderConfig(config) {
|
|
|
2504
2448
|
lines.push("# Default: Claude");
|
|
2505
2449
|
lines.push('provider = "claude"');
|
|
2506
2450
|
}
|
|
2507
|
-
await
|
|
2451
|
+
await writeFile4(PROVIDER_CONFIG_PATH, `${lines.join(`
|
|
2508
2452
|
`)}
|
|
2509
2453
|
`, "utf-8");
|
|
2510
2454
|
}
|
|
@@ -2519,17 +2463,17 @@ function parseProviderFlag(flag) {
|
|
|
2519
2463
|
throw new Error(`Invalid provider: ${flag}. Must be 'claude', 'codex', or 'both'.`);
|
|
2520
2464
|
}
|
|
2521
2465
|
// src/config/toml-patcher.ts
|
|
2522
|
-
import { existsSync as
|
|
2523
|
-
import { readFile as
|
|
2466
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
2467
|
+
import { readFile as readFile11, writeFile as writeFile5 } from "node:fs/promises";
|
|
2524
2468
|
var CONFIG_PATH2 = "omni.toml";
|
|
2525
2469
|
async function readConfigFile() {
|
|
2526
|
-
if (!
|
|
2470
|
+
if (!existsSync13(CONFIG_PATH2)) {
|
|
2527
2471
|
return "";
|
|
2528
2472
|
}
|
|
2529
|
-
return
|
|
2473
|
+
return readFile11(CONFIG_PATH2, "utf-8");
|
|
2530
2474
|
}
|
|
2531
2475
|
async function writeConfigFile(content) {
|
|
2532
|
-
await
|
|
2476
|
+
await writeFile5(CONFIG_PATH2, content, "utf-8");
|
|
2533
2477
|
}
|
|
2534
2478
|
function findSection(lines, sectionPattern) {
|
|
2535
2479
|
return lines.findIndex((line) => sectionPattern.test(line.trim()));
|
|
@@ -2550,7 +2494,7 @@ function formatCapabilitySource(name, source) {
|
|
|
2550
2494
|
if (typeof source === "string") {
|
|
2551
2495
|
return `${name} = "${source}"`;
|
|
2552
2496
|
}
|
|
2553
|
-
if (source.path) {
|
|
2497
|
+
if ("path" in source && source.path) {
|
|
2554
2498
|
return `${name} = { source = "${source.source}", path = "${source.path}" }`;
|
|
2555
2499
|
}
|
|
2556
2500
|
return `${name} = "${source.source}"`;
|
|
@@ -2616,10 +2560,8 @@ function formatMcpConfig(name, config) {
|
|
|
2616
2560
|
lines.push(`url = "${config.url}"`);
|
|
2617
2561
|
}
|
|
2618
2562
|
if (config.env && Object.keys(config.env).length > 0) {
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
lines.push(`${key} = "${value}"`);
|
|
2622
|
-
}
|
|
2563
|
+
const entries = Object.entries(config.env).map(([key, value]) => `${key} = "${value}"`);
|
|
2564
|
+
lines.push(`env = { ${entries.join(", ")} }`);
|
|
2623
2565
|
}
|
|
2624
2566
|
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
2625
2567
|
lines.push(`[mcps.${name}.headers]`);
|
|
@@ -2725,15 +2667,15 @@ function escapeRegExp(str) {
|
|
|
2725
2667
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2726
2668
|
}
|
|
2727
2669
|
// src/mcp-json/manager.ts
|
|
2728
|
-
import { existsSync as
|
|
2729
|
-
import { readFile as
|
|
2670
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
2671
|
+
import { readFile as readFile12, writeFile as writeFile6 } from "node:fs/promises";
|
|
2730
2672
|
var MCP_JSON_PATH = ".mcp.json";
|
|
2731
2673
|
async function readMcpJson() {
|
|
2732
|
-
if (!
|
|
2674
|
+
if (!existsSync14(MCP_JSON_PATH)) {
|
|
2733
2675
|
return { mcpServers: {} };
|
|
2734
2676
|
}
|
|
2735
2677
|
try {
|
|
2736
|
-
const content = await
|
|
2678
|
+
const content = await readFile12(MCP_JSON_PATH, "utf-8");
|
|
2737
2679
|
const parsed = JSON.parse(content);
|
|
2738
2680
|
return {
|
|
2739
2681
|
mcpServers: parsed.mcpServers || {}
|
|
@@ -2743,7 +2685,7 @@ async function readMcpJson() {
|
|
|
2743
2685
|
}
|
|
2744
2686
|
}
|
|
2745
2687
|
async function writeMcpJson(config2) {
|
|
2746
|
-
await
|
|
2688
|
+
await writeFile6(MCP_JSON_PATH, `${JSON.stringify(config2, null, 2)}
|
|
2747
2689
|
`, "utf-8");
|
|
2748
2690
|
}
|
|
2749
2691
|
function buildMcpServerConfig(mcp) {
|
|
@@ -2812,24 +2754,24 @@ async function syncMcpJson(capabilities2, previousManifest, options = {}) {
|
|
|
2812
2754
|
}
|
|
2813
2755
|
}
|
|
2814
2756
|
// src/state/manifest.ts
|
|
2815
|
-
import { existsSync as
|
|
2816
|
-
import { readFile as
|
|
2757
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync2, rmSync } from "node:fs";
|
|
2758
|
+
import { readFile as readFile13, writeFile as writeFile7 } from "node:fs/promises";
|
|
2817
2759
|
var MANIFEST_PATH = ".omni/state/manifest.json";
|
|
2818
2760
|
var CURRENT_VERSION = 1;
|
|
2819
2761
|
async function loadManifest() {
|
|
2820
|
-
if (!
|
|
2762
|
+
if (!existsSync15(MANIFEST_PATH)) {
|
|
2821
2763
|
return {
|
|
2822
2764
|
version: CURRENT_VERSION,
|
|
2823
2765
|
syncedAt: new Date().toISOString(),
|
|
2824
2766
|
capabilities: {}
|
|
2825
2767
|
};
|
|
2826
2768
|
}
|
|
2827
|
-
const content = await
|
|
2769
|
+
const content = await readFile13(MANIFEST_PATH, "utf-8");
|
|
2828
2770
|
return JSON.parse(content);
|
|
2829
2771
|
}
|
|
2830
2772
|
async function saveManifest(manifest) {
|
|
2831
2773
|
mkdirSync2(".omni/state", { recursive: true });
|
|
2832
|
-
await
|
|
2774
|
+
await writeFile7(MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}
|
|
2833
2775
|
`, "utf-8");
|
|
2834
2776
|
}
|
|
2835
2777
|
function buildManifestFromCapabilities(capabilities2) {
|
|
@@ -2864,14 +2806,14 @@ async function cleanupStaleResources(previousManifest, currentCapabilityIds) {
|
|
|
2864
2806
|
}
|
|
2865
2807
|
for (const skillName of resources.skills) {
|
|
2866
2808
|
const skillDir = `.claude/skills/${skillName}`;
|
|
2867
|
-
if (
|
|
2809
|
+
if (existsSync15(skillDir)) {
|
|
2868
2810
|
rmSync(skillDir, { recursive: true });
|
|
2869
2811
|
result.deletedSkills.push(skillName);
|
|
2870
2812
|
}
|
|
2871
2813
|
}
|
|
2872
2814
|
for (const ruleName of resources.rules) {
|
|
2873
2815
|
const rulePath = `.cursor/rules/omnidev-${ruleName}.mdc`;
|
|
2874
|
-
if (
|
|
2816
|
+
if (existsSync15(rulePath)) {
|
|
2875
2817
|
rmSync(rulePath);
|
|
2876
2818
|
result.deletedRules.push(ruleName);
|
|
2877
2819
|
}
|
|
@@ -2880,17 +2822,17 @@ async function cleanupStaleResources(previousManifest, currentCapabilityIds) {
|
|
|
2880
2822
|
return result;
|
|
2881
2823
|
}
|
|
2882
2824
|
// src/state/providers.ts
|
|
2883
|
-
import { existsSync as
|
|
2884
|
-
import { readFile as
|
|
2825
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync3 } from "node:fs";
|
|
2826
|
+
import { readFile as readFile14, writeFile as writeFile8 } from "node:fs/promises";
|
|
2885
2827
|
var STATE_DIR2 = ".omni/state";
|
|
2886
2828
|
var PROVIDERS_PATH = `${STATE_DIR2}/providers.json`;
|
|
2887
2829
|
var DEFAULT_PROVIDERS = ["claude-code"];
|
|
2888
2830
|
async function readEnabledProviders() {
|
|
2889
|
-
if (!
|
|
2831
|
+
if (!existsSync16(PROVIDERS_PATH)) {
|
|
2890
2832
|
return DEFAULT_PROVIDERS;
|
|
2891
2833
|
}
|
|
2892
2834
|
try {
|
|
2893
|
-
const content = await
|
|
2835
|
+
const content = await readFile14(PROVIDERS_PATH, "utf-8");
|
|
2894
2836
|
const state = JSON.parse(content);
|
|
2895
2837
|
return state.enabled.length > 0 ? state.enabled : DEFAULT_PROVIDERS;
|
|
2896
2838
|
} catch {
|
|
@@ -2900,7 +2842,7 @@ async function readEnabledProviders() {
|
|
|
2900
2842
|
async function writeEnabledProviders(providers) {
|
|
2901
2843
|
mkdirSync3(STATE_DIR2, { recursive: true });
|
|
2902
2844
|
const state = { enabled: providers };
|
|
2903
|
-
await
|
|
2845
|
+
await writeFile8(PROVIDERS_PATH, `${JSON.stringify(state, null, 2)}
|
|
2904
2846
|
`, "utf-8");
|
|
2905
2847
|
}
|
|
2906
2848
|
async function enableProvider(providerId) {
|
|
@@ -2922,10 +2864,10 @@ async function isProviderEnabled(providerId) {
|
|
|
2922
2864
|
import { spawn as spawn2 } from "node:child_process";
|
|
2923
2865
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
2924
2866
|
async function installCapabilityDependencies(silent) {
|
|
2925
|
-
const { existsSync:
|
|
2867
|
+
const { existsSync: existsSync17, readdirSync: readdirSync7 } = await import("node:fs");
|
|
2926
2868
|
const { join: join9 } = await import("node:path");
|
|
2927
2869
|
const capabilitiesDir = ".omni/capabilities";
|
|
2928
|
-
if (!
|
|
2870
|
+
if (!existsSync17(capabilitiesDir)) {
|
|
2929
2871
|
return;
|
|
2930
2872
|
}
|
|
2931
2873
|
const entries = readdirSync7(capabilitiesDir, { withFileTypes: true });
|
|
@@ -2947,14 +2889,14 @@ async function installCapabilityDependencies(silent) {
|
|
|
2947
2889
|
}
|
|
2948
2890
|
const capabilityPath = join9(capabilitiesDir, entry.name);
|
|
2949
2891
|
const packageJsonPath = join9(capabilityPath, "package.json");
|
|
2950
|
-
if (!
|
|
2892
|
+
if (!existsSync17(packageJsonPath)) {
|
|
2951
2893
|
continue;
|
|
2952
2894
|
}
|
|
2953
2895
|
if (!silent) {
|
|
2954
2896
|
console.log(`Installing dependencies for ${capabilityPath}...`);
|
|
2955
2897
|
}
|
|
2956
2898
|
await new Promise((resolve2, reject) => {
|
|
2957
|
-
const useNpmCi = hasNpm &&
|
|
2899
|
+
const useNpmCi = hasNpm && existsSync17(join9(capabilityPath, "package-lock.json"));
|
|
2958
2900
|
const cmd = hasBun ? "bun" : "npm";
|
|
2959
2901
|
const args = hasBun ? ["install"] : useNpmCi ? ["ci"] : ["install"];
|
|
2960
2902
|
const proc = spawn2(cmd, args, {
|
|
@@ -2995,7 +2937,6 @@ async function buildSyncBundle(options) {
|
|
|
2995
2937
|
docs,
|
|
2996
2938
|
commands,
|
|
2997
2939
|
subagents,
|
|
2998
|
-
instructionsPath: ".omni/instructions.md",
|
|
2999
2940
|
instructionsContent
|
|
3000
2941
|
};
|
|
3001
2942
|
if (hasAnyHooks(mergedHooks)) {
|
|
@@ -3044,7 +2985,6 @@ async function syncAgentConfiguration(options) {
|
|
|
3044
2985
|
}
|
|
3045
2986
|
}
|
|
3046
2987
|
mkdirSync4(".omni", { recursive: true });
|
|
3047
|
-
await writeRules(bundle.rules, bundle.docs);
|
|
3048
2988
|
await syncMcpJson(capabilities2, previousManifest, { silent });
|
|
3049
2989
|
const newManifest = buildManifestFromCapabilities(capabilities2);
|
|
3050
2990
|
await saveManifest(newManifest);
|
|
@@ -3067,7 +3007,7 @@ async function syncAgentConfiguration(options) {
|
|
|
3067
3007
|
}
|
|
3068
3008
|
if (!silent) {
|
|
3069
3009
|
console.log("✓ Synced:");
|
|
3070
|
-
console.log(` -
|
|
3010
|
+
console.log(` - ${bundle.docs.length} docs, ${bundle.rules.length} rules`);
|
|
3071
3011
|
if (adapters.length > 0) {
|
|
3072
3012
|
console.log(` - Provider adapters: ${adapters.map((a) => a.displayName).join(", ")}`);
|
|
3073
3013
|
}
|
|
@@ -3122,56 +3062,150 @@ function generateAgentsTemplate() {
|
|
|
3122
3062
|
|
|
3123
3063
|
## OmniDev
|
|
3124
3064
|
|
|
3125
|
-
|
|
3065
|
+
<!-- This section is populated during sync with capability rules and docs -->
|
|
3126
3066
|
`;
|
|
3127
3067
|
}
|
|
3128
|
-
// src/templates/
|
|
3129
|
-
function
|
|
3130
|
-
|
|
3068
|
+
// src/templates/capability.ts
|
|
3069
|
+
function generateCapabilityToml2(options) {
|
|
3070
|
+
const description = options.description || "TODO: Add a description for your capability";
|
|
3071
|
+
return `[capability]
|
|
3072
|
+
id = "${options.id}"
|
|
3073
|
+
name = "${options.name}"
|
|
3074
|
+
version = "0.1.0"
|
|
3075
|
+
description = "${description}"
|
|
3131
3076
|
|
|
3132
|
-
|
|
3077
|
+
# Optional author information
|
|
3078
|
+
# [capability.author]
|
|
3079
|
+
# name = "Your Name"
|
|
3080
|
+
# email = "you@example.com"
|
|
3133
3081
|
|
|
3134
|
-
|
|
3082
|
+
# Optional metadata
|
|
3083
|
+
# [capability.metadata]
|
|
3084
|
+
# repository = "https://github.com/user/repo"
|
|
3085
|
+
# license = "MIT"
|
|
3086
|
+
`;
|
|
3087
|
+
}
|
|
3088
|
+
function generateSkillTemplate(skillName) {
|
|
3089
|
+
return `---
|
|
3090
|
+
name: ${skillName}
|
|
3091
|
+
description: TODO: Add a description for this skill
|
|
3092
|
+
---
|
|
3093
|
+
|
|
3094
|
+
## What I do
|
|
3095
|
+
|
|
3096
|
+
<!-- Describe what this skill helps the AI agent accomplish -->
|
|
3097
|
+
- TODO: List the main capabilities of this skill
|
|
3098
|
+
|
|
3099
|
+
## When to use me
|
|
3100
|
+
|
|
3101
|
+
<!-- Describe scenarios when this skill should be invoked -->
|
|
3102
|
+
Use this skill when you need to:
|
|
3103
|
+
- TODO: Add trigger conditions
|
|
3135
3104
|
|
|
3136
|
-
|
|
3105
|
+
## Implementation
|
|
3106
|
+
|
|
3107
|
+
<!-- Add detailed instructions for the AI agent -->
|
|
3108
|
+
### Steps
|
|
3109
|
+
|
|
3110
|
+
1. TODO: Add implementation steps
|
|
3111
|
+
2. Validate inputs and outputs
|
|
3112
|
+
3. Report results to the user
|
|
3113
|
+
|
|
3114
|
+
## Examples
|
|
3115
|
+
|
|
3116
|
+
<!-- Optional: Add examples of how this skill should be used -->
|
|
3117
|
+
\`\`\`
|
|
3118
|
+
TODO: Add example usage
|
|
3119
|
+
\`\`\`
|
|
3137
3120
|
`;
|
|
3138
3121
|
}
|
|
3139
|
-
function
|
|
3140
|
-
return `#
|
|
3122
|
+
function generateRuleTemplate(ruleName) {
|
|
3123
|
+
return `# ${formatDisplayName(ruleName)}
|
|
3141
3124
|
|
|
3142
|
-
|
|
3143
|
-
<!-- TODO: Add 2-3 sentences describing your project -->
|
|
3144
|
-
[Describe what this project does and its main purpose]
|
|
3125
|
+
<!-- Rules are guidelines that the AI agent should follow when working in this project -->
|
|
3145
3126
|
|
|
3146
|
-
##
|
|
3127
|
+
## Overview
|
|
3147
3128
|
|
|
3148
|
-
|
|
3129
|
+
TODO: Describe what this rule enforces or guides.
|
|
3149
3130
|
|
|
3150
|
-
|
|
3151
|
-
- Rules (for guardrails and conventions)
|
|
3152
|
-
- Docs (reference material)
|
|
3153
|
-
- Commands and subagents (optional)
|
|
3131
|
+
## Guidelines
|
|
3154
3132
|
|
|
3155
|
-
|
|
3133
|
+
- TODO: Add specific guidelines the AI should follow
|
|
3134
|
+
- Be specific and actionable
|
|
3135
|
+
- Include examples where helpful
|
|
3136
|
+
|
|
3137
|
+
## Examples
|
|
3138
|
+
|
|
3139
|
+
### Good
|
|
3156
3140
|
|
|
3157
3141
|
\`\`\`
|
|
3158
|
-
|
|
3142
|
+
TODO: Add example of correct behavior
|
|
3159
3143
|
\`\`\`
|
|
3160
3144
|
|
|
3161
|
-
|
|
3145
|
+
### Bad
|
|
3162
3146
|
|
|
3163
3147
|
\`\`\`
|
|
3164
|
-
|
|
3148
|
+
TODO: Add example of incorrect behavior
|
|
3165
3149
|
\`\`\`
|
|
3150
|
+
`;
|
|
3151
|
+
}
|
|
3152
|
+
function generateHooksTemplate() {
|
|
3153
|
+
return `# Hook configuration for this capability
|
|
3154
|
+
# See: https://omnidev.dev/docs/advanced/hooks
|
|
3155
|
+
|
|
3156
|
+
# Example: Validate bash commands before execution
|
|
3157
|
+
# [[PreToolUse]]
|
|
3158
|
+
# matcher = "Bash"
|
|
3159
|
+
# [[PreToolUse.hooks]]
|
|
3160
|
+
# type = "command"
|
|
3161
|
+
# command = "\${OMNIDEV_CAPABILITY_ROOT}/hooks/validate-bash.sh"
|
|
3162
|
+
# timeout = 30
|
|
3163
|
+
|
|
3164
|
+
# Example: Run linter after file edits
|
|
3165
|
+
# [[PostToolUse]]
|
|
3166
|
+
# matcher = "Write|Edit"
|
|
3167
|
+
# [[PostToolUse.hooks]]
|
|
3168
|
+
# type = "command"
|
|
3169
|
+
# command = "\${OMNIDEV_CAPABILITY_ROOT}/hooks/run-linter.sh"
|
|
3170
|
+
|
|
3171
|
+
# Example: Load context at session start
|
|
3172
|
+
# [[SessionStart]]
|
|
3173
|
+
# matcher = "startup|resume"
|
|
3174
|
+
# [[SessionStart.hooks]]
|
|
3175
|
+
# type = "command"
|
|
3176
|
+
# command = "\${OMNIDEV_CAPABILITY_ROOT}/hooks/load-context.sh"
|
|
3177
|
+
`;
|
|
3178
|
+
}
|
|
3179
|
+
function generateHookScript() {
|
|
3180
|
+
return `#!/bin/bash
|
|
3181
|
+
# Sample hook script
|
|
3182
|
+
# This script receives JSON input via stdin
|
|
3166
3183
|
|
|
3167
|
-
|
|
3168
|
-
|
|
3184
|
+
# Read JSON input from stdin
|
|
3185
|
+
INPUT=$(cat)
|
|
3169
3186
|
|
|
3170
|
-
|
|
3187
|
+
# Example: Extract tool information
|
|
3188
|
+
# TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
|
|
3189
|
+
# COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
|
3171
3190
|
|
|
3172
|
-
|
|
3191
|
+
# Add your validation logic here
|
|
3192
|
+
# Exit 0 to allow, exit 2 to block
|
|
3173
3193
|
|
|
3174
|
-
|
|
3194
|
+
exit 0
|
|
3195
|
+
`;
|
|
3196
|
+
}
|
|
3197
|
+
function formatDisplayName(kebabCase) {
|
|
3198
|
+
return kebabCase.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
3199
|
+
}
|
|
3200
|
+
// src/templates/claude.ts
|
|
3201
|
+
function generateClaudeTemplate() {
|
|
3202
|
+
return `# Project Instructions
|
|
3203
|
+
|
|
3204
|
+
<!-- Add your project-specific instructions here -->
|
|
3205
|
+
|
|
3206
|
+
## OmniDev
|
|
3207
|
+
|
|
3208
|
+
<!-- This section is populated during sync with capability rules and docs -->
|
|
3175
3209
|
`;
|
|
3176
3210
|
}
|
|
3177
3211
|
// src/templates/omni.ts
|
|
@@ -3195,14 +3229,6 @@ function generateOmniMdTemplate() {
|
|
|
3195
3229
|
<!-- Describe your project's architecture and key components -->
|
|
3196
3230
|
`;
|
|
3197
3231
|
}
|
|
3198
|
-
// src/types/index.ts
|
|
3199
|
-
function getActiveProviders(config2) {
|
|
3200
|
-
if (config2.providers)
|
|
3201
|
-
return config2.providers;
|
|
3202
|
-
if (config2.provider)
|
|
3203
|
-
return [config2.provider];
|
|
3204
|
-
return ["claude"];
|
|
3205
|
-
}
|
|
3206
3232
|
// src/debug.ts
|
|
3207
3233
|
function debug(message, data) {
|
|
3208
3234
|
if (process.env["OMNIDEV_DEBUG"] !== "1") {
|
|
@@ -3224,7 +3250,6 @@ function getVersion() {
|
|
|
3224
3250
|
return version;
|
|
3225
3251
|
}
|
|
3226
3252
|
export {
|
|
3227
|
-
writeRules,
|
|
3228
3253
|
writeProviderConfig,
|
|
3229
3254
|
writeMcpJson,
|
|
3230
3255
|
writeEnabledProviders,
|
|
@@ -3233,7 +3258,6 @@ export {
|
|
|
3233
3258
|
version,
|
|
3234
3259
|
validateHooksConfig,
|
|
3235
3260
|
validateHook,
|
|
3236
|
-
validateEnv,
|
|
3237
3261
|
transformToOmnidev,
|
|
3238
3262
|
transformToClaude,
|
|
3239
3263
|
transformHooksConfig,
|
|
@@ -3247,6 +3271,7 @@ export {
|
|
|
3247
3271
|
resolveEnabledCapabilities,
|
|
3248
3272
|
readMcpJson,
|
|
3249
3273
|
readEnabledProviders,
|
|
3274
|
+
readCapabilityIdFromPath,
|
|
3250
3275
|
readActiveProfileState,
|
|
3251
3276
|
patchAddToProfile,
|
|
3252
3277
|
patchAddMcp,
|
|
@@ -3254,6 +3279,7 @@ export {
|
|
|
3254
3279
|
parseSourceConfig,
|
|
3255
3280
|
parseProviderFlag,
|
|
3256
3281
|
parseOmniConfig,
|
|
3282
|
+
parseFileSourcePath,
|
|
3257
3283
|
parseCapabilityConfig,
|
|
3258
3284
|
mergeHooksConfigs,
|
|
3259
3285
|
mergeAndDeduplicateHooks,
|
|
@@ -3265,7 +3291,6 @@ export {
|
|
|
3265
3291
|
loadManifest,
|
|
3266
3292
|
loadLockFile,
|
|
3267
3293
|
loadHooksFromCapability,
|
|
3268
|
-
loadEnvironment,
|
|
3269
3294
|
loadDocs,
|
|
3270
3295
|
loadConfig,
|
|
3271
3296
|
loadCommands,
|
|
@@ -3274,7 +3299,6 @@ export {
|
|
|
3274
3299
|
loadCapability,
|
|
3275
3300
|
loadBaseConfig,
|
|
3276
3301
|
isValidMatcherPattern,
|
|
3277
|
-
isSecretEnvVar,
|
|
3278
3302
|
isProviderEnabled,
|
|
3279
3303
|
isPromptHookEvent,
|
|
3280
3304
|
isMatcherEvent,
|
|
@@ -3282,6 +3306,9 @@ export {
|
|
|
3282
3306
|
isHookPrompt,
|
|
3283
3307
|
isHookEvent,
|
|
3284
3308
|
isHookCommand,
|
|
3309
|
+
isGitSource,
|
|
3310
|
+
isFileSourceConfig,
|
|
3311
|
+
isFileSource,
|
|
3285
3312
|
installCapabilityDependencies,
|
|
3286
3313
|
hasHooks,
|
|
3287
3314
|
hasAnyHooks,
|
|
@@ -3294,9 +3321,13 @@ export {
|
|
|
3294
3321
|
getEnabledCapabilities,
|
|
3295
3322
|
getActiveProviders,
|
|
3296
3323
|
getActiveProfile,
|
|
3324
|
+
generateSkillTemplate,
|
|
3325
|
+
generateRuleTemplate,
|
|
3297
3326
|
generateOmniMdTemplate,
|
|
3298
|
-
|
|
3327
|
+
generateHooksTemplate,
|
|
3328
|
+
generateHookScript,
|
|
3299
3329
|
generateClaudeTemplate,
|
|
3330
|
+
generateCapabilityToml2 as generateCapabilityToml,
|
|
3300
3331
|
generateAgentsTemplate,
|
|
3301
3332
|
findDuplicateCommands,
|
|
3302
3333
|
fetchCapabilitySource,
|