@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnidev-ai/core",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -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, writeRules } from "./rules";
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";
@@ -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 validation fails or loading errors occur
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, env);
36
+ const cap = await loadCapability(path);
39
37
 
40
38
  // Only add if enabled
41
39
  if (enabledIds.includes(cap.id)) {
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
- import { readFile, writeFile } from "node:fs/promises";
2
+ import { readFile } from "node:fs/promises";
3
3
  import { basename, join } from "node:path";
4
- import type { Doc, Rule } from "../types";
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(source: CapabilitySourceConfig): GitCapabilitySourceConfig {
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
- return source;
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 single capability source from git
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
- return fetchGitCapabilitySource(id, config, options);
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
- if (gitConfig.ref) {
977
- lockEntry.ref = gitConfig.ref;
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
@@ -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()
@@ -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 (sub-table)
193
+ // Environment variables (inline table)
239
194
  if (mcpConfig.env && Object.keys(mcpConfig.env).length > 0) {
240
- lines.push(`[mcps.${name}.env]`);
241
- for (const [key, value] of Object.entries(mcpConfig.env)) {
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
 
@@ -1,5 +1,4 @@
1
1
  export * from "./capabilities";
2
- export * from "./env";
3
2
  export * from "./config";
4
3
  export * from "./parser";
5
4
  export * from "./profiles";
@@ -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
- lines.push(`[mcps.${name}.env]`);
156
- for (const [key, value] of Object.entries(config.env)) {
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
@@ -32,6 +32,7 @@ export * from "./sync";
32
32
 
33
33
  // Export templates
34
34
  export * from "./templates/agents";
35
+ export * from "./templates/capability";
35
36
  export * from "./templates/claude";
36
37
  export * from "./templates/omni";
37
38
  // Export core types
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
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Template for AGENTS.md (Codex provider)
3
- * Creates a minimal file with reference to OmniDev instructions
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
- @import .omni/instructions.md
12
+ <!-- This section is populated during sync with capability rules and docs -->
13
13
  `;
14
14
  }