@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/package.json
CHANGED
package/src/capability/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { loadDocs } from "./docs";
|
|
|
3
3
|
export { discoverCapabilities, loadCapability, loadCapabilityConfig } from "./loader";
|
|
4
4
|
export type { CapabilityRegistry } from "./registry";
|
|
5
5
|
export { buildCapabilityRegistry } from "./registry";
|
|
6
|
-
export { loadRules
|
|
6
|
+
export { loadRules } from "./rules";
|
|
7
7
|
export { loadSkills } from "./skills";
|
|
8
8
|
export {
|
|
9
9
|
fetchAllCapabilitySources,
|
|
@@ -15,6 +15,10 @@ export {
|
|
|
15
15
|
sourceToGitUrl,
|
|
16
16
|
getSourceCapabilityPath,
|
|
17
17
|
getLockFilePath,
|
|
18
|
+
isGitSource,
|
|
19
|
+
isFileSource,
|
|
20
|
+
parseFileSourcePath,
|
|
21
|
+
readCapabilityIdFromPath,
|
|
18
22
|
} from "./sources";
|
|
19
23
|
export type { FetchResult, SourceUpdateInfo, DiscoveredContent } from "./sources";
|
|
20
24
|
export { loadSubagents } from "./subagents";
|
package/src/capability/loader.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync, readdirSync } from "node:fs";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { validateEnv } from "../config/env";
|
|
5
4
|
import { parseCapabilityConfig } from "../config/parser";
|
|
6
5
|
import { loadCapabilityHooks } from "../hooks/loader.js";
|
|
7
6
|
import type {
|
|
@@ -339,25 +338,14 @@ function convertCommandExports(commandExports: unknown[], capabilityId: string):
|
|
|
339
338
|
|
|
340
339
|
/**
|
|
341
340
|
* Loads a complete capability including config, skills, rules, docs, and exports.
|
|
342
|
-
* Validates environment requirements before loading.
|
|
343
|
-
*
|
|
344
341
|
* @param capabilityPath - Path to the capability directory
|
|
345
|
-
* @param env - Environment variables to validate against
|
|
346
342
|
* @returns Fully loaded capability
|
|
347
|
-
* @throws Error if
|
|
343
|
+
* @throws Error if loading errors occur
|
|
348
344
|
*/
|
|
349
|
-
export async function loadCapability(
|
|
350
|
-
capabilityPath: string,
|
|
351
|
-
env: Record<string, string>,
|
|
352
|
-
): Promise<LoadedCapability> {
|
|
345
|
+
export async function loadCapability(capabilityPath: string): Promise<LoadedCapability> {
|
|
353
346
|
const config = await loadCapabilityConfig(capabilityPath);
|
|
354
347
|
const id = config.capability.id;
|
|
355
348
|
|
|
356
|
-
// Validate environment
|
|
357
|
-
if (config.env) {
|
|
358
|
-
validateEnv(config.env, env, id);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
349
|
// Load content - programmatic takes precedence
|
|
362
350
|
const exports = await importCapabilityExports(capabilityPath);
|
|
363
351
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getEnabledCapabilities } from "../config/capabilities";
|
|
2
|
-
import { loadEnvironment } from "../config/env";
|
|
3
2
|
import { mergeHooksConfigs } from "../hooks/merger.js";
|
|
4
3
|
import type { HooksConfig, CapabilityHooks, Doc, LoadedCapability, Rule, Skill } from "../types";
|
|
5
4
|
import { discoverCapabilities, loadCapability } from "./loader";
|
|
@@ -27,7 +26,6 @@ export interface CapabilityRegistry {
|
|
|
27
26
|
* @returns Capability registry with helper functions
|
|
28
27
|
*/
|
|
29
28
|
export async function buildCapabilityRegistry(): Promise<CapabilityRegistry> {
|
|
30
|
-
const env = await loadEnvironment();
|
|
31
29
|
const enabledIds = await getEnabledCapabilities();
|
|
32
30
|
|
|
33
31
|
const capabilityPaths = await discoverCapabilities();
|
|
@@ -35,7 +33,7 @@ export async function buildCapabilityRegistry(): Promise<CapabilityRegistry> {
|
|
|
35
33
|
|
|
36
34
|
for (const path of capabilityPaths) {
|
|
37
35
|
try {
|
|
38
|
-
const cap = await loadCapability(path
|
|
36
|
+
const cap = await loadCapability(path);
|
|
39
37
|
|
|
40
38
|
// Only add if enabled
|
|
41
39
|
if (enabledIds.includes(cap.id)) {
|
package/src/capability/rules.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
-
import { readFile
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { basename, join } from "node:path";
|
|
4
|
-
import type {
|
|
4
|
+
import type { Rule } from "../types";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Load rules from a capability's rules/ directory
|
|
@@ -36,101 +36,3 @@ export async function loadRules(capabilityPath: string, capabilityId: string): P
|
|
|
36
36
|
|
|
37
37
|
return rules;
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Write aggregated rules and docs to .omni/instructions.md
|
|
42
|
-
* Updates the generated section between markers while preserving user content
|
|
43
|
-
* @param rules Array of rules from all enabled capabilities
|
|
44
|
-
* @param docs Array of docs from all enabled capabilities
|
|
45
|
-
*/
|
|
46
|
-
export async function writeRules(rules: Rule[], docs: Doc[] = []): Promise<void> {
|
|
47
|
-
const instructionsPath = ".omni/instructions.md";
|
|
48
|
-
|
|
49
|
-
// Generate content from rules and docs
|
|
50
|
-
const rulesContent = generateRulesContent(rules, docs);
|
|
51
|
-
|
|
52
|
-
// Read existing content or create new file
|
|
53
|
-
let content: string;
|
|
54
|
-
if (existsSync(instructionsPath)) {
|
|
55
|
-
content = await readFile(instructionsPath, "utf-8");
|
|
56
|
-
} else {
|
|
57
|
-
// Create new file with basic template
|
|
58
|
-
content = `# OmniDev Instructions
|
|
59
|
-
|
|
60
|
-
## Project Description
|
|
61
|
-
<!-- TODO: Add 2-3 sentences describing your project -->
|
|
62
|
-
[Describe what this project does and its main purpose]
|
|
63
|
-
|
|
64
|
-
<!-- BEGIN OMNIDEV GENERATED CONTENT - DO NOT EDIT BELOW THIS LINE -->
|
|
65
|
-
<!-- END OMNIDEV GENERATED CONTENT -->
|
|
66
|
-
`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Replace content between markers
|
|
70
|
-
const beginMarker = "<!-- BEGIN OMNIDEV GENERATED CONTENT - DO NOT EDIT BELOW THIS LINE -->";
|
|
71
|
-
const endMarker = "<!-- END OMNIDEV GENERATED CONTENT -->";
|
|
72
|
-
|
|
73
|
-
const beginIndex = content.indexOf(beginMarker);
|
|
74
|
-
const endIndex = content.indexOf(endMarker);
|
|
75
|
-
|
|
76
|
-
if (beginIndex === -1 || endIndex === -1) {
|
|
77
|
-
// Markers not found, append to end
|
|
78
|
-
content += `\n\n${beginMarker}\n${rulesContent}\n${endMarker}\n`;
|
|
79
|
-
} else {
|
|
80
|
-
// Replace content between markers
|
|
81
|
-
content =
|
|
82
|
-
content.substring(0, beginIndex + beginMarker.length) +
|
|
83
|
-
"\n" +
|
|
84
|
-
rulesContent +
|
|
85
|
-
"\n" +
|
|
86
|
-
content.substring(endIndex);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
await writeFile(instructionsPath, content, "utf-8");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function generateRulesContent(rules: Rule[], docs: Doc[] = []): string {
|
|
93
|
-
if (rules.length === 0 && docs.length === 0) {
|
|
94
|
-
return `<!-- This section is automatically updated when capabilities change -->
|
|
95
|
-
|
|
96
|
-
## Capabilities
|
|
97
|
-
|
|
98
|
-
No capabilities enabled yet. Run \`omnidev capability enable <name>\` to enable capabilities.`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
let content = `<!-- This section is automatically updated when capabilities change -->
|
|
102
|
-
|
|
103
|
-
## Capabilities
|
|
104
|
-
|
|
105
|
-
`;
|
|
106
|
-
|
|
107
|
-
// Add documentation section if there are docs
|
|
108
|
-
if (docs.length > 0) {
|
|
109
|
-
content += `### Documentation
|
|
110
|
-
|
|
111
|
-
`;
|
|
112
|
-
for (const doc of docs) {
|
|
113
|
-
content += `#### ${doc.name} (from ${doc.capabilityId})
|
|
114
|
-
|
|
115
|
-
${doc.content}
|
|
116
|
-
|
|
117
|
-
`;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Add rules section if there are rules
|
|
122
|
-
if (rules.length > 0) {
|
|
123
|
-
content += `### Rules
|
|
124
|
-
|
|
125
|
-
`;
|
|
126
|
-
for (const rule of rules) {
|
|
127
|
-
content += `#### ${rule.name} (from ${rule.capabilityId})
|
|
128
|
-
|
|
129
|
-
${rule.content}
|
|
130
|
-
|
|
131
|
-
`;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return content.trim();
|
|
136
|
-
}
|
|
@@ -17,9 +17,11 @@ import type {
|
|
|
17
17
|
CapabilitiesLockFile,
|
|
18
18
|
CapabilityLockEntry,
|
|
19
19
|
CapabilitySourceConfig,
|
|
20
|
+
FileCapabilitySourceConfig,
|
|
20
21
|
GitCapabilitySourceConfig,
|
|
21
22
|
OmniConfig,
|
|
22
23
|
} from "../types/index.js";
|
|
24
|
+
import { isFileSourceConfig } from "../types/index.js";
|
|
23
25
|
|
|
24
26
|
// Local path for .omni directory
|
|
25
27
|
const OMNI_LOCAL = ".omni";
|
|
@@ -97,12 +99,62 @@ export function isGitSource(source: string): boolean {
|
|
|
97
99
|
);
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Check if a source string is a file source
|
|
104
|
+
*/
|
|
105
|
+
export function isFileSource(source: string): boolean {
|
|
106
|
+
return source.startsWith("file://");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Parse a file:// source to get the actual file path
|
|
111
|
+
*/
|
|
112
|
+
export function parseFileSourcePath(source: string): string {
|
|
113
|
+
if (!source.startsWith("file://")) {
|
|
114
|
+
throw new Error(`Invalid file source: ${source}`);
|
|
115
|
+
}
|
|
116
|
+
return source.slice(7); // Remove "file://" prefix
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Read the capability ID from a capability directory
|
|
121
|
+
* Tries to read from capability.toml first, then falls back to directory name
|
|
122
|
+
*/
|
|
123
|
+
export async function readCapabilityIdFromPath(capabilityPath: string): Promise<string | null> {
|
|
124
|
+
const tomlPath = join(capabilityPath, "capability.toml");
|
|
125
|
+
|
|
126
|
+
if (existsSync(tomlPath)) {
|
|
127
|
+
try {
|
|
128
|
+
const content = await readFile(tomlPath, "utf-8");
|
|
129
|
+
const parsed = parseToml(content) as Record<string, unknown>;
|
|
130
|
+
const capability = parsed["capability"] as Record<string, unknown> | undefined;
|
|
131
|
+
if (capability?.["id"] && typeof capability["id"] === "string") {
|
|
132
|
+
return capability["id"];
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// Fall through to directory name
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Fall back to directory name
|
|
140
|
+
const parts = capabilityPath.replace(/\\/g, "/").split("/");
|
|
141
|
+
const dirName = parts.pop() || parts.pop(); // Handle trailing slash
|
|
142
|
+
return dirName || null;
|
|
143
|
+
}
|
|
144
|
+
|
|
100
145
|
/**
|
|
101
146
|
* Parse a capability source string or config into normalized form
|
|
102
|
-
* Returns a GitCapabilitySourceConfig
|
|
147
|
+
* Returns a GitCapabilitySourceConfig or FileCapabilitySourceConfig
|
|
103
148
|
*/
|
|
104
|
-
export function parseSourceConfig(
|
|
149
|
+
export function parseSourceConfig(
|
|
150
|
+
source: CapabilitySourceConfig,
|
|
151
|
+
): GitCapabilitySourceConfig | FileCapabilitySourceConfig {
|
|
105
152
|
if (typeof source === "string") {
|
|
153
|
+
// Check for file source
|
|
154
|
+
if (isFileSource(source)) {
|
|
155
|
+
return { source } as FileCapabilitySourceConfig;
|
|
156
|
+
}
|
|
157
|
+
|
|
106
158
|
// Git source shorthand formats:
|
|
107
159
|
// - "github:user/repo"
|
|
108
160
|
// - "github:user/repo#ref"
|
|
@@ -125,7 +177,13 @@ export function parseSourceConfig(source: CapabilitySourceConfig): GitCapability
|
|
|
125
177
|
}
|
|
126
178
|
return result;
|
|
127
179
|
}
|
|
128
|
-
|
|
180
|
+
|
|
181
|
+
// Check if the config object is a file source
|
|
182
|
+
if (isFileSourceConfig(source)) {
|
|
183
|
+
return source as FileCapabilitySourceConfig;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return source as GitCapabilitySourceConfig;
|
|
129
187
|
}
|
|
130
188
|
|
|
131
189
|
/**
|
|
@@ -769,7 +827,74 @@ async function fetchGitCapabilitySource(
|
|
|
769
827
|
}
|
|
770
828
|
|
|
771
829
|
/**
|
|
772
|
-
* Fetch a
|
|
830
|
+
* Fetch a file-sourced capability (copy from local path)
|
|
831
|
+
*/
|
|
832
|
+
async function fetchFileCapabilitySource(
|
|
833
|
+
id: string,
|
|
834
|
+
config: FileCapabilitySourceConfig,
|
|
835
|
+
options?: { silent?: boolean },
|
|
836
|
+
): Promise<FetchResult> {
|
|
837
|
+
const sourcePath = parseFileSourcePath(config.source);
|
|
838
|
+
const targetPath = getSourceCapabilityPath(id);
|
|
839
|
+
|
|
840
|
+
// Validate source exists
|
|
841
|
+
if (!existsSync(sourcePath)) {
|
|
842
|
+
throw new Error(`File source not found: ${sourcePath}`);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Check if it's a directory
|
|
846
|
+
const sourceStats = await stat(sourcePath);
|
|
847
|
+
if (!sourceStats.isDirectory()) {
|
|
848
|
+
throw new Error(`File source must be a directory: ${sourcePath}`);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Check if capability.toml exists in source
|
|
852
|
+
if (!existsSync(join(sourcePath, "capability.toml"))) {
|
|
853
|
+
throw new Error(`No capability.toml found in: ${sourcePath}`);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (!options?.silent) {
|
|
857
|
+
console.log(` Copying ${id} from ${sourcePath}...`);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Remove old target if exists
|
|
861
|
+
if (existsSync(targetPath)) {
|
|
862
|
+
await rm(targetPath, { recursive: true });
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Create parent directory
|
|
866
|
+
await mkdir(join(targetPath, ".."), { recursive: true });
|
|
867
|
+
|
|
868
|
+
// Copy directory contents
|
|
869
|
+
await cp(sourcePath, targetPath, { recursive: true });
|
|
870
|
+
|
|
871
|
+
// Read version from capability.toml
|
|
872
|
+
let version = "local";
|
|
873
|
+
const capTomlPath = join(targetPath, "capability.toml");
|
|
874
|
+
if (existsSync(capTomlPath)) {
|
|
875
|
+
try {
|
|
876
|
+
const content = await readFile(capTomlPath, "utf-8");
|
|
877
|
+
const parsed = parseToml(content) as Record<string, unknown>;
|
|
878
|
+
const capability = parsed["capability"] as Record<string, unknown> | undefined;
|
|
879
|
+
if (capability?.["version"] && typeof capability["version"] === "string") {
|
|
880
|
+
version = capability["version"];
|
|
881
|
+
}
|
|
882
|
+
} catch {
|
|
883
|
+
// Ignore parse errors
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
return {
|
|
888
|
+
id,
|
|
889
|
+
path: targetPath,
|
|
890
|
+
version,
|
|
891
|
+
updated: true,
|
|
892
|
+
wrapped: false,
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* Fetch a single capability source (git or file)
|
|
773
898
|
*/
|
|
774
899
|
export async function fetchCapabilitySource(
|
|
775
900
|
id: string,
|
|
@@ -777,7 +902,13 @@ export async function fetchCapabilitySource(
|
|
|
777
902
|
options?: { silent?: boolean },
|
|
778
903
|
): Promise<FetchResult> {
|
|
779
904
|
const config = parseSourceConfig(sourceConfig);
|
|
780
|
-
|
|
905
|
+
|
|
906
|
+
// Check if it's a file source
|
|
907
|
+
if (isFileSourceConfig(sourceConfig) || isFileSource(config.source)) {
|
|
908
|
+
return fetchFileCapabilitySource(id, config as FileCapabilitySourceConfig, options);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
return fetchGitCapabilitySource(id, config as GitCapabilitySourceConfig, options);
|
|
781
912
|
}
|
|
782
913
|
|
|
783
914
|
/**
|
|
@@ -969,12 +1100,15 @@ export async function fetchAllCapabilitySources(
|
|
|
969
1100
|
};
|
|
970
1101
|
|
|
971
1102
|
// Git source: use commit and ref
|
|
972
|
-
const gitConfig = parseSourceConfig(source);
|
|
973
1103
|
if (result.commit) {
|
|
974
1104
|
lockEntry.commit = result.commit;
|
|
975
1105
|
}
|
|
976
|
-
|
|
977
|
-
|
|
1106
|
+
// Only access ref if it's a git source
|
|
1107
|
+
if (!isFileSourceConfig(source)) {
|
|
1108
|
+
const gitConfig = parseSourceConfig(source) as GitCapabilitySourceConfig;
|
|
1109
|
+
if (gitConfig.ref) {
|
|
1110
|
+
lockEntry.ref = gitConfig.ref;
|
|
1111
|
+
}
|
|
978
1112
|
}
|
|
979
1113
|
|
|
980
1114
|
// Check if lock entry changed
|
|
@@ -1029,8 +1163,20 @@ export async function checkForUpdates(config: OmniConfig): Promise<SourceUpdateI
|
|
|
1029
1163
|
const targetPath = getSourceCapabilityPath(id);
|
|
1030
1164
|
const existing = lockFile.capabilities[id];
|
|
1031
1165
|
|
|
1166
|
+
// Skip file sources - they don't have update checking
|
|
1167
|
+
if (isFileSourceConfig(source) || isFileSource(sourceConfig.source)) {
|
|
1168
|
+
updates.push({
|
|
1169
|
+
id,
|
|
1170
|
+
source: sourceConfig.source,
|
|
1171
|
+
currentVersion: existing?.version || "local",
|
|
1172
|
+
latestVersion: "local",
|
|
1173
|
+
hasUpdate: false,
|
|
1174
|
+
});
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1032
1178
|
// Handle git sources
|
|
1033
|
-
const gitConfig = sourceConfig;
|
|
1179
|
+
const gitConfig = sourceConfig as GitCapabilitySourceConfig;
|
|
1034
1180
|
|
|
1035
1181
|
if (!existsSync(join(targetPath, ".git"))) {
|
|
1036
1182
|
// Not yet cloned
|
package/src/config/AGENTS.md
CHANGED
|
@@ -11,7 +11,6 @@ Configuration loading and parsing system using TOML format with profile-based ca
|
|
|
11
11
|
| Task | Location | Notes |
|
|
12
12
|
|------|----------|-------|
|
|
13
13
|
| Config merge logic | loader.ts | Merges config.toml + config.local.toml |
|
|
14
|
-
| Environment loading | env.ts | Loads .omni/.env, validates declarations |
|
|
15
14
|
| TOML parsing | parser.ts | Uses smol-toml, validates capability.toml |
|
|
16
15
|
| Provider selection | provider.ts | Loads provider.toml, parses CLI flags |
|
|
17
16
|
| Profile management | profiles.ts | Active profile tracking, resolves capabilities |
|
|
@@ -23,7 +22,6 @@ Configuration loading and parsing system using TOML format with profile-based ca
|
|
|
23
22
|
- `.omni/config.toml` - main config (project name, default profile, profiles)
|
|
24
23
|
- `.omni/config.local.toml` - local overrides (gitignored)
|
|
25
24
|
- `.omni/provider.toml` - provider selection (claude/codex/both)
|
|
26
|
-
- `.omni/.env` - secrets, always gitignored
|
|
27
25
|
|
|
28
26
|
**Profile-Based Capability Management:**
|
|
29
27
|
- Profiles define capability sets in `[profiles.name].capabilities`
|
|
@@ -31,16 +29,7 @@ Configuration loading and parsing system using TOML format with profile-based ca
|
|
|
31
29
|
- `always_enabled_capabilities` list merged with profile capabilities
|
|
32
30
|
- Use `enableCapability()` / `disableCapability()` to update active profile
|
|
33
31
|
|
|
34
|
-
**Environment Variable Handling:**
|
|
35
|
-
- capability.toml `[env]` section declares requirements
|
|
36
|
-
- `required = true` → must be present or default
|
|
37
|
-
- `secret = true` → masked in logs/error messages
|
|
38
|
-
- `default = "value"` → optional with fallback
|
|
39
|
-
|
|
40
32
|
## ANTI-PATTERNS (THIS SUBSYSTEM)
|
|
41
33
|
|
|
42
34
|
- **NEVER** edit config.toml directly for capability changes - use enableCapability()/disableCapability()
|
|
43
|
-
- **NEVER** commit .omni/.env or config.local.toml - both gitignored
|
|
44
35
|
- **NEVER** parse TOML manually - use parseOmniConfig() / parseCapabilityConfig()
|
|
45
|
-
- **NEVER** ignore validateEnv() errors - required env vars block capability load
|
|
46
|
-
- **NEVER** assume process.env is complete - merge with .omni/.env via loadEnvironment()
|
package/src/config/config.ts
CHANGED
|
@@ -15,9 +15,6 @@ const LOCAL_CONFIG = "omni.local.toml";
|
|
|
15
15
|
function mergeConfigs(base: OmniConfig, override: OmniConfig): OmniConfig {
|
|
16
16
|
const merged: OmniConfig = { ...base, ...override };
|
|
17
17
|
|
|
18
|
-
// Deep merge env
|
|
19
|
-
merged.env = { ...base.env, ...override.env };
|
|
20
|
-
|
|
21
18
|
// Deep merge profiles
|
|
22
19
|
merged.profiles = { ...base.profiles };
|
|
23
20
|
for (const [name, profile] of Object.entries(override.profiles || {})) {
|
|
@@ -84,26 +81,6 @@ export async function writeConfig(config: OmniConfig): Promise<void> {
|
|
|
84
81
|
function generateConfigToml(config: OmniConfig): string {
|
|
85
82
|
const lines: string[] = [];
|
|
86
83
|
|
|
87
|
-
lines.push("# =============================================================================");
|
|
88
|
-
lines.push("# OmniDev Configuration");
|
|
89
|
-
lines.push("# =============================================================================");
|
|
90
|
-
lines.push("# This file defines your project's capabilities, profiles, and settings.");
|
|
91
|
-
lines.push("#");
|
|
92
|
-
lines.push("# Files:");
|
|
93
|
-
lines.push("# • omni.toml - Main config (commit to share with team)");
|
|
94
|
-
lines.push("# • omni.local.toml - Local overrides (add to .gitignore)");
|
|
95
|
-
lines.push("# • omni.lock.toml - Version lock file (commit for reproducibility)");
|
|
96
|
-
lines.push("#");
|
|
97
|
-
lines.push("# Quick start:");
|
|
98
|
-
lines.push("# 1. Add capability sources to [capabilities.sources]");
|
|
99
|
-
lines.push("# 2. Reference them in your profiles");
|
|
100
|
-
lines.push("# 3. Run: omnidev sync");
|
|
101
|
-
lines.push("# 4. Switch profiles: omnidev profile use <name>");
|
|
102
|
-
lines.push("");
|
|
103
|
-
|
|
104
|
-
// Note: active_profile is stored in .omni/state/active-profile, not in config.toml
|
|
105
|
-
// We still read it from config.toml for backwards compatibility, but don't write it here
|
|
106
|
-
|
|
107
84
|
// Providers
|
|
108
85
|
if (config.providers?.enabled && config.providers.enabled.length > 0) {
|
|
109
86
|
lines.push("# AI providers to enable (claude, codex, or both)");
|
|
@@ -112,28 +89,6 @@ function generateConfigToml(config: OmniConfig): string {
|
|
|
112
89
|
lines.push("");
|
|
113
90
|
}
|
|
114
91
|
|
|
115
|
-
// Environment variables
|
|
116
|
-
lines.push("# =============================================================================");
|
|
117
|
-
lines.push("# Environment Variables");
|
|
118
|
-
lines.push("# =============================================================================");
|
|
119
|
-
lines.push("# Global environment variables available to all capabilities.");
|
|
120
|
-
// biome-ignore lint/suspicious/noTemplateCurlyInString: Example of env var syntax
|
|
121
|
-
lines.push("# Use ${VAR_NAME} syntax to reference shell environment variables.");
|
|
122
|
-
lines.push("#");
|
|
123
|
-
if (config.env && Object.keys(config.env).length > 0) {
|
|
124
|
-
lines.push("[env]");
|
|
125
|
-
for (const [key, value] of Object.entries(config.env)) {
|
|
126
|
-
lines.push(`${key} = "${value}"`);
|
|
127
|
-
}
|
|
128
|
-
} else {
|
|
129
|
-
lines.push("# [env]");
|
|
130
|
-
// biome-ignore lint/suspicious/noTemplateCurlyInString: Example of env var syntax
|
|
131
|
-
lines.push('# DATABASE_URL = "${DATABASE_URL}"');
|
|
132
|
-
// biome-ignore lint/suspicious/noTemplateCurlyInString: Example of env var syntax
|
|
133
|
-
lines.push('# API_KEY = "${MY_API_KEY}"');
|
|
134
|
-
}
|
|
135
|
-
lines.push("");
|
|
136
|
-
|
|
137
92
|
// Capability sources
|
|
138
93
|
lines.push("# =============================================================================");
|
|
139
94
|
lines.push("# Capability Sources");
|
|
@@ -149,8 +104,8 @@ function generateConfigToml(config: OmniConfig): string {
|
|
|
149
104
|
if (typeof sourceConfig === "string") {
|
|
150
105
|
// Simple string source
|
|
151
106
|
lines.push(`${name} = "${sourceConfig}"`);
|
|
152
|
-
} else if (sourceConfig.path) {
|
|
153
|
-
// Full config object with path
|
|
107
|
+
} else if ("path" in sourceConfig && sourceConfig.path) {
|
|
108
|
+
// Full config object with path (git source)
|
|
154
109
|
lines.push(
|
|
155
110
|
`${name} = { source = "${sourceConfig.source}", path = "${sourceConfig.path}" }`,
|
|
156
111
|
);
|
|
@@ -235,12 +190,10 @@ function generateConfigToml(config: OmniConfig): string {
|
|
|
235
190
|
lines.push(`url = "${mcpConfig.url}"`);
|
|
236
191
|
}
|
|
237
192
|
|
|
238
|
-
// Environment variables (
|
|
193
|
+
// Environment variables (inline table)
|
|
239
194
|
if (mcpConfig.env && Object.keys(mcpConfig.env).length > 0) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
lines.push(`${key} = "${value}"`);
|
|
243
|
-
}
|
|
195
|
+
const entries = Object.entries(mcpConfig.env).map(([key, value]) => `${key} = "${value}"`);
|
|
196
|
+
lines.push(`env = { ${entries.join(", ")} }`);
|
|
244
197
|
}
|
|
245
198
|
|
|
246
199
|
// Headers (sub-table)
|
|
@@ -263,9 +216,8 @@ function generateConfigToml(config: OmniConfig): string {
|
|
|
263
216
|
lines.push('# command = "node"');
|
|
264
217
|
lines.push('# args = ["./servers/database.js"]');
|
|
265
218
|
lines.push('# cwd = "./mcp-servers"');
|
|
266
|
-
lines.push("# [mcps.database.env]");
|
|
267
219
|
// biome-ignore lint/suspicious/noTemplateCurlyInString: Example of env var syntax
|
|
268
|
-
lines.push('# DB_URL = "${DATABASE_URL}"');
|
|
220
|
+
lines.push('# env = { DB_URL = "${DATABASE_URL}" }');
|
|
269
221
|
lines.push("");
|
|
270
222
|
}
|
|
271
223
|
|
package/src/config/index.ts
CHANGED
|
@@ -53,7 +53,7 @@ function formatCapabilitySource(name: string, source: CapabilitySourceConfig): s
|
|
|
53
53
|
if (typeof source === "string") {
|
|
54
54
|
return `${name} = "${source}"`;
|
|
55
55
|
}
|
|
56
|
-
if (source.path) {
|
|
56
|
+
if ("path" in source && source.path) {
|
|
57
57
|
return `${name} = { source = "${source.source}", path = "${source.path}" }`;
|
|
58
58
|
}
|
|
59
59
|
return `${name} = "${source.source}"`;
|
|
@@ -150,12 +150,10 @@ function formatMcpConfig(name: string, config: McpConfig): string[] {
|
|
|
150
150
|
lines.push(`url = "${config.url}"`);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
// Environment variables
|
|
153
|
+
// Environment variables (inline table)
|
|
154
154
|
if (config.env && Object.keys(config.env).length > 0) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
lines.push(`${key} = "${value}"`);
|
|
158
|
-
}
|
|
155
|
+
const entries = Object.entries(config.env).map(([key, value]) => `${key} = "${value}"`);
|
|
156
|
+
lines.push(`env = { ${entries.join(", ")} }`);
|
|
159
157
|
}
|
|
160
158
|
|
|
161
159
|
// Headers
|
package/src/index.ts
CHANGED
package/src/sync.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { mkdirSync } from "node:fs";
|
|
3
3
|
import { buildCapabilityRegistry } from "./capability/registry";
|
|
4
|
-
import { writeRules } from "./capability/rules";
|
|
5
4
|
import { fetchAllCapabilitySources } from "./capability/sources";
|
|
6
5
|
import { loadConfig } from "./config/config";
|
|
7
6
|
import { hasAnyHooks } from "./hooks/merger.js";
|
|
@@ -142,7 +141,6 @@ export async function buildSyncBundle(options?: {
|
|
|
142
141
|
docs,
|
|
143
142
|
commands,
|
|
144
143
|
subagents,
|
|
145
|
-
instructionsPath: ".omni/instructions.md",
|
|
146
144
|
instructionsContent,
|
|
147
145
|
};
|
|
148
146
|
|
|
@@ -224,9 +222,6 @@ export async function syncAgentConfiguration(options?: SyncOptions): Promise<Syn
|
|
|
224
222
|
// Ensure core directories exist
|
|
225
223
|
mkdirSync(".omni", { recursive: true });
|
|
226
224
|
|
|
227
|
-
// Write rules and docs to .omni/instructions.md (provider-agnostic)
|
|
228
|
-
await writeRules(bundle.rules, bundle.docs);
|
|
229
|
-
|
|
230
225
|
// Sync .mcp.json with capability MCP servers (before saving manifest)
|
|
231
226
|
await syncMcpJson(capabilities, previousManifest, { silent });
|
|
232
227
|
|
|
@@ -256,9 +251,7 @@ export async function syncAgentConfiguration(options?: SyncOptions): Promise<Syn
|
|
|
256
251
|
|
|
257
252
|
if (!silent) {
|
|
258
253
|
console.log("✓ Synced:");
|
|
259
|
-
console.log(
|
|
260
|
-
` - .omni/instructions.md (${bundle.docs.length} docs, ${bundle.rules.length} rules)`,
|
|
261
|
-
);
|
|
254
|
+
console.log(` - ${bundle.docs.length} docs, ${bundle.rules.length} rules`);
|
|
262
255
|
if (adapters.length > 0) {
|
|
263
256
|
console.log(` - Provider adapters: ${adapters.map((a) => a.displayName).join(", ")}`);
|
|
264
257
|
}
|
package/src/templates/agents.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Template for AGENTS.md (Codex provider)
|
|
3
|
-
* Creates a minimal file
|
|
3
|
+
* Creates a minimal file - actual content is generated during sync from OMNI.md + instructions
|
|
4
4
|
*/
|
|
5
5
|
export function generateAgentsTemplate(): string {
|
|
6
6
|
return `# Project Instructions
|
|
@@ -9,6 +9,6 @@ export function generateAgentsTemplate(): string {
|
|
|
9
9
|
|
|
10
10
|
## OmniDev
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
<!-- This section is populated during sync with capability rules and docs -->
|
|
13
13
|
`;
|
|
14
14
|
}
|