@omnidev-ai/core 0.10.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 +4 -40
- package/dist/index.js +103 -197
- package/package.json +1 -1
- package/src/capability/loader.ts +2 -14
- package/src/capability/registry.ts +1 -3
- package/src/config/AGENTS.md +0 -11
- package/src/config/config.ts +4 -52
- package/src/config/index.ts +0 -1
- package/src/config/toml-patcher.ts +3 -5
- package/src/types/index.ts +1 -9
- package/src/config/env.ts +0 -97
package/dist/index.d.ts
CHANGED
|
@@ -330,11 +330,6 @@ interface CapabilityExports {
|
|
|
330
330
|
module?: string;
|
|
331
331
|
gitignore?: string[];
|
|
332
332
|
}
|
|
333
|
-
interface EnvDeclaration {
|
|
334
|
-
required?: boolean;
|
|
335
|
-
secret?: boolean;
|
|
336
|
-
default?: string;
|
|
337
|
-
}
|
|
338
333
|
interface SyncConfig {
|
|
339
334
|
on_sync?: string;
|
|
340
335
|
}
|
|
@@ -344,7 +339,6 @@ interface CliConfig {
|
|
|
344
339
|
interface CapabilityConfig {
|
|
345
340
|
capability: CapabilityMetadata;
|
|
346
341
|
exports?: CapabilityExports;
|
|
347
|
-
env?: Record<string, EnvDeclaration | Record<string, never>>;
|
|
348
342
|
mcp?: McpConfig;
|
|
349
343
|
sync?: SyncConfig;
|
|
350
344
|
cli?: CliConfig;
|
|
@@ -360,7 +354,7 @@ interface McpToolSchema {
|
|
|
360
354
|
*
|
|
361
355
|
* - **stdio**: Local process using stdin/stdout (default)
|
|
362
356
|
* - Requires: command
|
|
363
|
-
* - Optional: args,
|
|
357
|
+
* - Optional: args, cwd
|
|
364
358
|
*
|
|
365
359
|
* - **http**: Remote HTTP server (recommended for remote servers)
|
|
366
360
|
* - Requires: url
|
|
@@ -508,7 +502,6 @@ interface OmniConfig {
|
|
|
508
502
|
project?: string;
|
|
509
503
|
active_profile?: string;
|
|
510
504
|
always_enabled_capabilities?: string[];
|
|
511
|
-
env?: Record<string, string>;
|
|
512
505
|
profiles?: Record<string, ProfileConfig>;
|
|
513
506
|
providers?: {
|
|
514
507
|
enabled?: Provider[];
|
|
@@ -621,14 +614,11 @@ declare function discoverCapabilities(): Promise<string[]>;
|
|
|
621
614
|
declare function loadCapabilityConfig(capabilityPath: string): Promise<CapabilityConfig>;
|
|
622
615
|
/**
|
|
623
616
|
* Loads a complete capability including config, skills, rules, docs, and exports.
|
|
624
|
-
* Validates environment requirements before loading.
|
|
625
|
-
*
|
|
626
617
|
* @param capabilityPath - Path to the capability directory
|
|
627
|
-
* @param env - Environment variables to validate against
|
|
628
618
|
* @returns Fully loaded capability
|
|
629
|
-
* @throws Error if
|
|
619
|
+
* @throws Error if loading errors occur
|
|
630
620
|
*/
|
|
631
|
-
declare function loadCapability(capabilityPath: string
|
|
621
|
+
declare function loadCapability(capabilityPath: string): Promise<LoadedCapability>;
|
|
632
622
|
/**
|
|
633
623
|
* Registry of loaded capabilities with helper functions.
|
|
634
624
|
*/
|
|
@@ -921,32 +911,6 @@ declare function enableCapability(capabilityId: string): Promise<void>;
|
|
|
921
911
|
*/
|
|
922
912
|
declare function disableCapability(capabilityId: string): Promise<void>;
|
|
923
913
|
/**
|
|
924
|
-
* Load environment variables from .omni/.env file and merge with process.env.
|
|
925
|
-
* Process environment variables take precedence over file values.
|
|
926
|
-
*
|
|
927
|
-
* @returns Merged environment variables
|
|
928
|
-
*/
|
|
929
|
-
declare function loadEnvironment(): Promise<Record<string, string>>;
|
|
930
|
-
/**
|
|
931
|
-
* Validate that all required environment variables are present.
|
|
932
|
-
* Checks declarations from capability config and throws descriptive errors for missing vars.
|
|
933
|
-
*
|
|
934
|
-
* @param declarations - Environment variable declarations from capability.toml
|
|
935
|
-
* @param env - Loaded environment variables
|
|
936
|
-
* @param capabilityId - ID of the capability being validated
|
|
937
|
-
* @throws Error if required environment variables are missing
|
|
938
|
-
*/
|
|
939
|
-
declare function validateEnv(declarations: Record<string, EnvDeclaration | Record<string, never>>, env: Record<string, string | undefined>, capabilityId: string): void;
|
|
940
|
-
/**
|
|
941
|
-
* Check if an environment variable should be treated as a secret.
|
|
942
|
-
* Secrets should be masked in logs and error messages.
|
|
943
|
-
*
|
|
944
|
-
* @param key - Environment variable name
|
|
945
|
-
* @param declarations - Environment variable declarations from capability.toml
|
|
946
|
-
* @returns true if the variable is marked as secret
|
|
947
|
-
*/
|
|
948
|
-
declare function isSecretEnvVar(key: string, declarations: Record<string, EnvDeclaration | Record<string, never>>): boolean;
|
|
949
|
-
/**
|
|
950
914
|
* Load only the base config file (omni.toml) without merging local overrides.
|
|
951
915
|
* Use this when you need to modify and write back to omni.toml.
|
|
952
916
|
* @returns OmniConfig from omni.toml only
|
|
@@ -1251,4 +1215,4 @@ declare function generateOmniMdTemplate(): string;
|
|
|
1251
1215
|
declare function debug(message: string, data?: unknown): void;
|
|
1252
1216
|
declare const version = "0.1.0";
|
|
1253
1217
|
declare function getVersion(): string;
|
|
1254
|
-
export { writeProviderConfig, writeMcpJson, writeEnabledProviders, writeConfig, writeActiveProfileState, version, validateHooksConfig, validateHook,
|
|
1218
|
+
export { writeProviderConfig, writeMcpJson, writeEnabledProviders, writeConfig, writeActiveProfileState, version, validateHooksConfig, validateHook, transformToOmnidev, transformToClaude, transformHooksConfig, syncMcpJson, syncAgentConfiguration, sourceToGitUrl, setProfile, setActiveProfile, saveManifest, saveLockFile, resolveEnabledCapabilities, readMcpJson, readEnabledProviders, readCapabilityIdFromPath, readActiveProfileState, patchAddToProfile, patchAddMcp, patchAddCapabilitySource, parseSourceConfig, parseProviderFlag, parseOmniConfig, parseFileSourcePath, parseCapabilityConfig, mergeHooksConfigs, mergeAndDeduplicateHooks, loadSubagents, loadSkills, loadRules, loadProviderConfig, loadProfileConfig, loadManifest, loadLockFile, loadHooksFromCapability, loadDocs, loadConfig, loadCommands, loadCapabilityHooks, loadCapabilityConfig, loadCapability, loadBaseConfig, isValidMatcherPattern, isProviderEnabled, isPromptHookEvent, isMatcherEvent, isHookType, isHookPrompt, isHookEvent, isHookCommand, isGitSource, isFileSourceConfig, isFileSource, installCapabilityDependencies, hasHooks, hasAnyHooks, getVersion, getSourceCapabilityPath, getLockFilePath, getHooksDirectory, getHooksConfigPath, getEventsWithHooks, getEnabledCapabilities, getActiveProviders, getActiveProfile, generateSkillTemplate, generateRuleTemplate, generateOmniMdTemplate, generateHooksTemplate, generateHookScript, generateClaudeTemplate, generateCapabilityToml, generateAgentsTemplate, findDuplicateCommands, fetchCapabilitySource, fetchAllCapabilitySources, enableProvider, enableCapability, discoverCapabilities, disableProvider, disableCapability, debug, createEmptyValidationResult, createEmptyHooksConfig, countHooks, containsOmnidevVariables, containsClaudeVariables, clearActiveProfileState, cleanupStaleResources, checkForUpdates, buildSyncBundle, buildRouteMap, buildManifestFromCapabilities, buildCommand, buildCapabilityRegistry, ValidationSeverity, VARIABLE_MAPPINGS, SyncResult, SyncOptions, SyncConfig, SyncBundle, SubagentPermissionMode, SubagentModel, SubagentHooks, SubagentHookConfig, SubagentExport, Subagent, SourceUpdateInfo, SkillExport, Skill, SessionStartMatcher, SESSION_START_MATCHERS, Rule, ResourceManifest, ProvidersState, ProviderSyncResult, ProviderManifest, ProviderInitResult, ProviderId, ProviderContext, ProviderConfig, ProviderAdapter, Provider, PromptHookEvent, ProfileConfig, PreCompactMatcher, PROMPT_HOOK_EVENTS, PRE_COMPACT_MATCHERS, OmnidevVariable, OmniConfig, NotificationMatcher, NOTIFICATION_MATCHERS, McpTransport, McpToolSchema, McpServerStdioConfig, McpServerSseConfig, McpServerHttpConfig, McpServerConfig, McpJsonConfig, McpConfig, MatcherEvent, MATCHER_EVENTS, LoadedCapability, LoadHooksResult, LoadHooksOptions, HooksDoctorResult, HooksDoctorCheck, HooksConfig, HookValidationResult, HookValidationIssue, HookValidationCode, HookType, HookPrompt, HookMatcher, HookEvent, HookCommand, Hook, HOOK_TYPES, HOOK_EVENTS, HOOKS_DIRECTORY, HOOKS_CONFIG_FILENAME, GitCapabilitySourceConfig, FileContent, FileCapabilitySourceConfig, FetchResult, DoctorCheckStatus, DocExport, Doc, DiscoveredContent, DeduplicateOptions, DEFAULT_PROMPT_TIMEOUT, DEFAULT_COMMAND_TIMEOUT, CommandExport, Command, CliConfig, CleanupResult, ClaudeVariable, CapabilityTemplateOptions, CapabilitySourceType, CapabilitySourceConfig, CapabilitySource, CapabilityResources, CapabilityRegistry, CapabilityMetadata, CapabilityLockEntry, CapabilityHooks, CapabilityExports, CapabilityExport, CapabilityConfig, CapabilitiesLockFile, CapabilitiesConfig, COMMON_TOOL_MATCHERS };
|
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(),
|
|
@@ -879,12 +831,12 @@ async function loadRules(capabilityPath, capabilityId) {
|
|
|
879
831
|
}
|
|
880
832
|
|
|
881
833
|
// src/capability/skills.ts
|
|
882
|
-
import { existsSync as
|
|
883
|
-
import { readFile as
|
|
834
|
+
import { existsSync as existsSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
835
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
884
836
|
import { join as join5 } from "node:path";
|
|
885
837
|
async function loadSkills(capabilityPath, capabilityId) {
|
|
886
838
|
const skillsDir = join5(capabilityPath, "skills");
|
|
887
|
-
if (!
|
|
839
|
+
if (!existsSync6(skillsDir)) {
|
|
888
840
|
return [];
|
|
889
841
|
}
|
|
890
842
|
const skills = [];
|
|
@@ -892,7 +844,7 @@ async function loadSkills(capabilityPath, capabilityId) {
|
|
|
892
844
|
for (const entry of entries) {
|
|
893
845
|
if (entry.isDirectory()) {
|
|
894
846
|
const skillPath = join5(skillsDir, entry.name, "SKILL.md");
|
|
895
|
-
if (
|
|
847
|
+
if (existsSync6(skillPath)) {
|
|
896
848
|
const skill = await parseSkillFile(skillPath, capabilityId);
|
|
897
849
|
skills.push(skill);
|
|
898
850
|
}
|
|
@@ -901,7 +853,7 @@ async function loadSkills(capabilityPath, capabilityId) {
|
|
|
901
853
|
return skills;
|
|
902
854
|
}
|
|
903
855
|
async function parseSkillFile(filePath, capabilityId) {
|
|
904
|
-
const content = await
|
|
856
|
+
const content = await readFile4(filePath, "utf-8");
|
|
905
857
|
const parsed = parseFrontmatterWithMarkdown(content);
|
|
906
858
|
if (!parsed) {
|
|
907
859
|
throw new Error(`Invalid SKILL.md format at ${filePath}: missing YAML frontmatter`);
|
|
@@ -920,12 +872,12 @@ async function parseSkillFile(filePath, capabilityId) {
|
|
|
920
872
|
}
|
|
921
873
|
|
|
922
874
|
// src/capability/subagents.ts
|
|
923
|
-
import { existsSync as
|
|
924
|
-
import { readFile as
|
|
875
|
+
import { existsSync as existsSync7, readdirSync as readdirSync5 } from "node:fs";
|
|
876
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
925
877
|
import { join as join6 } from "node:path";
|
|
926
878
|
async function loadSubagents(capabilityPath, capabilityId) {
|
|
927
879
|
const subagentsDir = join6(capabilityPath, "subagents");
|
|
928
|
-
if (!
|
|
880
|
+
if (!existsSync7(subagentsDir)) {
|
|
929
881
|
return [];
|
|
930
882
|
}
|
|
931
883
|
const subagents = [];
|
|
@@ -933,7 +885,7 @@ async function loadSubagents(capabilityPath, capabilityId) {
|
|
|
933
885
|
for (const entry of entries) {
|
|
934
886
|
if (entry.isDirectory()) {
|
|
935
887
|
const subagentPath = join6(subagentsDir, entry.name, "SUBAGENT.md");
|
|
936
|
-
if (
|
|
888
|
+
if (existsSync7(subagentPath)) {
|
|
937
889
|
const subagent = await parseSubagentFile(subagentPath, capabilityId);
|
|
938
890
|
subagents.push(subagent);
|
|
939
891
|
}
|
|
@@ -942,7 +894,7 @@ async function loadSubagents(capabilityPath, capabilityId) {
|
|
|
942
894
|
return subagents;
|
|
943
895
|
}
|
|
944
896
|
async function parseSubagentFile(filePath, capabilityId) {
|
|
945
|
-
const content = await
|
|
897
|
+
const content = await readFile5(filePath, "utf-8");
|
|
946
898
|
const parsed = parseFrontmatterWithMarkdown(content);
|
|
947
899
|
if (!parsed) {
|
|
948
900
|
throw new Error(`Invalid SUBAGENT.md format at ${filePath}: missing YAML frontmatter`);
|
|
@@ -986,13 +938,13 @@ function parseCommaSeparatedList(value) {
|
|
|
986
938
|
var CAPABILITIES_DIR = ".omni/capabilities";
|
|
987
939
|
async function discoverCapabilities() {
|
|
988
940
|
const capabilities = [];
|
|
989
|
-
if (
|
|
941
|
+
if (existsSync8(CAPABILITIES_DIR)) {
|
|
990
942
|
const entries = readdirSync6(CAPABILITIES_DIR, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
991
943
|
for (const entry of entries) {
|
|
992
944
|
if (entry.isDirectory()) {
|
|
993
945
|
const entryPath = join7(CAPABILITIES_DIR, entry.name);
|
|
994
946
|
const configPath = join7(entryPath, "capability.toml");
|
|
995
|
-
if (
|
|
947
|
+
if (existsSync8(configPath)) {
|
|
996
948
|
capabilities.push(entryPath);
|
|
997
949
|
}
|
|
998
950
|
}
|
|
@@ -1002,13 +954,13 @@ async function discoverCapabilities() {
|
|
|
1002
954
|
}
|
|
1003
955
|
async function loadCapabilityConfig(capabilityPath) {
|
|
1004
956
|
const configPath = join7(capabilityPath, "capability.toml");
|
|
1005
|
-
const content = await
|
|
957
|
+
const content = await readFile6(configPath, "utf-8");
|
|
1006
958
|
const config = parseCapabilityConfig(content);
|
|
1007
959
|
return config;
|
|
1008
960
|
}
|
|
1009
961
|
async function importCapabilityExports(capabilityPath) {
|
|
1010
962
|
const indexPath = join7(capabilityPath, "index.ts");
|
|
1011
|
-
if (!
|
|
963
|
+
if (!existsSync8(indexPath)) {
|
|
1012
964
|
return {};
|
|
1013
965
|
}
|
|
1014
966
|
try {
|
|
@@ -1028,10 +980,10 @@ If this is a project-specific capability, install dependencies or remove it from
|
|
|
1028
980
|
}
|
|
1029
981
|
async function loadTypeDefinitions(capabilityPath) {
|
|
1030
982
|
const typesPath = join7(capabilityPath, "types.d.ts");
|
|
1031
|
-
if (!
|
|
983
|
+
if (!existsSync8(typesPath)) {
|
|
1032
984
|
return;
|
|
1033
985
|
}
|
|
1034
|
-
return
|
|
986
|
+
return readFile6(typesPath, "utf-8");
|
|
1035
987
|
}
|
|
1036
988
|
function convertSkillExports(skillExports, capabilityId) {
|
|
1037
989
|
return skillExports.map((skillExport) => {
|
|
@@ -1208,12 +1160,9 @@ function convertCommandExports(commandExports, capabilityId) {
|
|
|
1208
1160
|
return result;
|
|
1209
1161
|
});
|
|
1210
1162
|
}
|
|
1211
|
-
async function loadCapability(capabilityPath
|
|
1163
|
+
async function loadCapability(capabilityPath) {
|
|
1212
1164
|
const config = await loadCapabilityConfig(capabilityPath);
|
|
1213
1165
|
const id = config.capability.id;
|
|
1214
|
-
if (config.env) {
|
|
1215
|
-
validateEnv(config.env, env, id);
|
|
1216
|
-
}
|
|
1217
1166
|
const exports = await importCapabilityExports(capabilityPath);
|
|
1218
1167
|
const exportsAny = exports;
|
|
1219
1168
|
const skills = "skills" in exports && Array.isArray(exportsAny.skills) ? convertSkillExports(exportsAny.skills, id) : await loadSkills(capabilityPath, id);
|
|
@@ -1248,13 +1197,12 @@ async function loadCapability(capabilityPath, env) {
|
|
|
1248
1197
|
return result;
|
|
1249
1198
|
}
|
|
1250
1199
|
// src/config/config.ts
|
|
1251
|
-
import { existsSync as
|
|
1252
|
-
import { readFile as
|
|
1200
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
1201
|
+
import { readFile as readFile7, writeFile } from "node:fs/promises";
|
|
1253
1202
|
var CONFIG_PATH = "omni.toml";
|
|
1254
1203
|
var LOCAL_CONFIG = "omni.local.toml";
|
|
1255
1204
|
function mergeConfigs(base, override) {
|
|
1256
1205
|
const merged = { ...base, ...override };
|
|
1257
|
-
merged.env = { ...base.env, ...override.env };
|
|
1258
1206
|
merged.profiles = { ...base.profiles };
|
|
1259
1207
|
for (const [name, profile] of Object.entries(override.profiles || {})) {
|
|
1260
1208
|
merged.profiles[name] = {
|
|
@@ -1268,8 +1216,8 @@ function mergeConfigs(base, override) {
|
|
|
1268
1216
|
return merged;
|
|
1269
1217
|
}
|
|
1270
1218
|
async function loadBaseConfig() {
|
|
1271
|
-
if (
|
|
1272
|
-
const content = await
|
|
1219
|
+
if (existsSync9(CONFIG_PATH)) {
|
|
1220
|
+
const content = await readFile7(CONFIG_PATH, "utf-8");
|
|
1273
1221
|
return parseOmniConfig(content);
|
|
1274
1222
|
}
|
|
1275
1223
|
return {};
|
|
@@ -1277,8 +1225,8 @@ async function loadBaseConfig() {
|
|
|
1277
1225
|
async function loadConfig() {
|
|
1278
1226
|
const baseConfig = await loadBaseConfig();
|
|
1279
1227
|
let localConfig = {};
|
|
1280
|
-
if (
|
|
1281
|
-
const content = await
|
|
1228
|
+
if (existsSync9(LOCAL_CONFIG)) {
|
|
1229
|
+
const content = await readFile7(LOCAL_CONFIG, "utf-8");
|
|
1282
1230
|
localConfig = parseOmniConfig(content);
|
|
1283
1231
|
}
|
|
1284
1232
|
return mergeConfigs(baseConfig, localConfig);
|
|
@@ -1289,22 +1237,6 @@ async function writeConfig(config) {
|
|
|
1289
1237
|
}
|
|
1290
1238
|
function generateConfigToml(config) {
|
|
1291
1239
|
const lines = [];
|
|
1292
|
-
lines.push("# =============================================================================");
|
|
1293
|
-
lines.push("# OmniDev Configuration");
|
|
1294
|
-
lines.push("# =============================================================================");
|
|
1295
|
-
lines.push("# This file defines your project's capabilities, profiles, and settings.");
|
|
1296
|
-
lines.push("#");
|
|
1297
|
-
lines.push("# Files:");
|
|
1298
|
-
lines.push("# • omni.toml - Main config (commit to share with team)");
|
|
1299
|
-
lines.push("# • omni.local.toml - Local overrides (add to .gitignore)");
|
|
1300
|
-
lines.push("# • omni.lock.toml - Version lock file (commit for reproducibility)");
|
|
1301
|
-
lines.push("#");
|
|
1302
|
-
lines.push("# Quick start:");
|
|
1303
|
-
lines.push("# 1. Add capability sources to [capabilities.sources]");
|
|
1304
|
-
lines.push("# 2. Reference them in your profiles");
|
|
1305
|
-
lines.push("# 3. Run: omnidev sync");
|
|
1306
|
-
lines.push("# 4. Switch profiles: omnidev profile use <name>");
|
|
1307
|
-
lines.push("");
|
|
1308
1240
|
if (config.providers?.enabled && config.providers.enabled.length > 0) {
|
|
1309
1241
|
lines.push("# AI providers to enable (claude, codex, or both)");
|
|
1310
1242
|
lines.push("[providers]");
|
|
@@ -1312,23 +1244,6 @@ function generateConfigToml(config) {
|
|
|
1312
1244
|
lines.push("");
|
|
1313
1245
|
}
|
|
1314
1246
|
lines.push("# =============================================================================");
|
|
1315
|
-
lines.push("# Environment Variables");
|
|
1316
|
-
lines.push("# =============================================================================");
|
|
1317
|
-
lines.push("# Global environment variables available to all capabilities.");
|
|
1318
|
-
lines.push("# Use ${VAR_NAME} syntax to reference shell environment variables.");
|
|
1319
|
-
lines.push("#");
|
|
1320
|
-
if (config.env && Object.keys(config.env).length > 0) {
|
|
1321
|
-
lines.push("[env]");
|
|
1322
|
-
for (const [key, value] of Object.entries(config.env)) {
|
|
1323
|
-
lines.push(`${key} = "${value}"`);
|
|
1324
|
-
}
|
|
1325
|
-
} else {
|
|
1326
|
-
lines.push("# [env]");
|
|
1327
|
-
lines.push('# DATABASE_URL = "${DATABASE_URL}"');
|
|
1328
|
-
lines.push('# API_KEY = "${MY_API_KEY}"');
|
|
1329
|
-
}
|
|
1330
|
-
lines.push("");
|
|
1331
|
-
lines.push("# =============================================================================");
|
|
1332
1247
|
lines.push("# Capability Sources");
|
|
1333
1248
|
lines.push("# =============================================================================");
|
|
1334
1249
|
lines.push("# Fetch capabilities from Git repositories. On sync, these are");
|
|
@@ -1408,10 +1323,8 @@ function generateConfigToml(config) {
|
|
|
1408
1323
|
lines.push(`url = "${mcpConfig.url}"`);
|
|
1409
1324
|
}
|
|
1410
1325
|
if (mcpConfig.env && Object.keys(mcpConfig.env).length > 0) {
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
lines.push(`${key} = "${value}"`);
|
|
1414
|
-
}
|
|
1326
|
+
const entries = Object.entries(mcpConfig.env).map(([key, value]) => `${key} = "${value}"`);
|
|
1327
|
+
lines.push(`env = { ${entries.join(", ")} }`);
|
|
1415
1328
|
}
|
|
1416
1329
|
if (mcpConfig.headers && Object.keys(mcpConfig.headers).length > 0) {
|
|
1417
1330
|
lines.push(`[mcps.${name}.headers]`);
|
|
@@ -1431,8 +1344,7 @@ function generateConfigToml(config) {
|
|
|
1431
1344
|
lines.push('# command = "node"');
|
|
1432
1345
|
lines.push('# args = ["./servers/database.js"]');
|
|
1433
1346
|
lines.push('# cwd = "./mcp-servers"');
|
|
1434
|
-
lines.push(
|
|
1435
|
-
lines.push('# DB_URL = "${DATABASE_URL}"');
|
|
1347
|
+
lines.push('# env = { DB_URL = "${DATABASE_URL}" }');
|
|
1436
1348
|
lines.push("");
|
|
1437
1349
|
}
|
|
1438
1350
|
lines.push("# =============================================================================");
|
|
@@ -1471,16 +1383,16 @@ function generateConfigToml(config) {
|
|
|
1471
1383
|
}
|
|
1472
1384
|
|
|
1473
1385
|
// src/state/active-profile.ts
|
|
1474
|
-
import { existsSync as
|
|
1475
|
-
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";
|
|
1476
1388
|
var STATE_DIR = ".omni/state";
|
|
1477
1389
|
var ACTIVE_PROFILE_PATH = `${STATE_DIR}/active-profile`;
|
|
1478
1390
|
async function readActiveProfileState() {
|
|
1479
|
-
if (!
|
|
1391
|
+
if (!existsSync10(ACTIVE_PROFILE_PATH)) {
|
|
1480
1392
|
return null;
|
|
1481
1393
|
}
|
|
1482
1394
|
try {
|
|
1483
|
-
const content = await
|
|
1395
|
+
const content = await readFile8(ACTIVE_PROFILE_PATH, "utf-8");
|
|
1484
1396
|
const trimmed = content.trim();
|
|
1485
1397
|
return trimmed || null;
|
|
1486
1398
|
} catch {
|
|
@@ -1492,7 +1404,7 @@ async function writeActiveProfileState(profileName) {
|
|
|
1492
1404
|
await writeFile2(ACTIVE_PROFILE_PATH, profileName, "utf-8");
|
|
1493
1405
|
}
|
|
1494
1406
|
async function clearActiveProfileState() {
|
|
1495
|
-
if (
|
|
1407
|
+
if (existsSync10(ACTIVE_PROFILE_PATH)) {
|
|
1496
1408
|
await unlink(ACTIVE_PROFILE_PATH);
|
|
1497
1409
|
}
|
|
1498
1410
|
}
|
|
@@ -1666,13 +1578,12 @@ function getEventsWithHooks(config) {
|
|
|
1666
1578
|
|
|
1667
1579
|
// src/capability/registry.ts
|
|
1668
1580
|
async function buildCapabilityRegistry() {
|
|
1669
|
-
const env = await loadEnvironment();
|
|
1670
1581
|
const enabledIds = await getEnabledCapabilities();
|
|
1671
1582
|
const capabilityPaths = await discoverCapabilities();
|
|
1672
1583
|
const capabilities = new Map;
|
|
1673
1584
|
for (const path of capabilityPaths) {
|
|
1674
1585
|
try {
|
|
1675
|
-
const cap = await loadCapability(path
|
|
1586
|
+
const cap = await loadCapability(path);
|
|
1676
1587
|
if (enabledIds.includes(cap.id)) {
|
|
1677
1588
|
capabilities.set(cap.id, cap);
|
|
1678
1589
|
}
|
|
@@ -1703,9 +1614,9 @@ async function buildCapabilityRegistry() {
|
|
|
1703
1614
|
};
|
|
1704
1615
|
}
|
|
1705
1616
|
// src/capability/sources.ts
|
|
1706
|
-
import { existsSync as
|
|
1617
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
1707
1618
|
import { spawn } from "node:child_process";
|
|
1708
|
-
import { cp, mkdir, readdir, readFile as
|
|
1619
|
+
import { cp, mkdir, readdir, readFile as readFile9, rename, rm, stat, writeFile as writeFile3 } from "node:fs/promises";
|
|
1709
1620
|
import { join as join8 } from "node:path";
|
|
1710
1621
|
import { parse as parseToml2 } from "smol-toml";
|
|
1711
1622
|
|
|
@@ -1770,9 +1681,9 @@ function parseFileSourcePath(source) {
|
|
|
1770
1681
|
}
|
|
1771
1682
|
async function readCapabilityIdFromPath(capabilityPath) {
|
|
1772
1683
|
const tomlPath = join8(capabilityPath, "capability.toml");
|
|
1773
|
-
if (
|
|
1684
|
+
if (existsSync11(tomlPath)) {
|
|
1774
1685
|
try {
|
|
1775
|
-
const content = await
|
|
1686
|
+
const content = await readFile9(tomlPath, "utf-8");
|
|
1776
1687
|
const parsed = parseToml2(content);
|
|
1777
1688
|
const capability = parsed["capability"];
|
|
1778
1689
|
if (capability?.["id"] && typeof capability["id"] === "string") {
|
|
@@ -1822,11 +1733,11 @@ function getLockFilePath() {
|
|
|
1822
1733
|
}
|
|
1823
1734
|
async function loadLockFile() {
|
|
1824
1735
|
const lockPath = getLockFilePath();
|
|
1825
|
-
if (!
|
|
1736
|
+
if (!existsSync11(lockPath)) {
|
|
1826
1737
|
return { capabilities: {} };
|
|
1827
1738
|
}
|
|
1828
1739
|
try {
|
|
1829
|
-
const content = await
|
|
1740
|
+
const content = await readFile9(lockPath, "utf-8");
|
|
1830
1741
|
const parsed = parseToml2(content);
|
|
1831
1742
|
const capabilities = parsed["capabilities"];
|
|
1832
1743
|
return {
|
|
@@ -1915,16 +1826,16 @@ async function fetchRepo(repoPath, ref) {
|
|
|
1915
1826
|
return true;
|
|
1916
1827
|
}
|
|
1917
1828
|
function hasCapabilityToml(dirPath) {
|
|
1918
|
-
return
|
|
1829
|
+
return existsSync11(join8(dirPath, "capability.toml"));
|
|
1919
1830
|
}
|
|
1920
1831
|
async function shouldWrapDirectory(dirPath) {
|
|
1921
|
-
if (
|
|
1832
|
+
if (existsSync11(join8(dirPath, ".claude-plugin", "plugin.json"))) {
|
|
1922
1833
|
return true;
|
|
1923
1834
|
}
|
|
1924
1835
|
const allDirs = [...SKILL_DIRS, ...AGENT_DIRS, ...COMMAND_DIRS, ...RULE_DIRS, ...DOC_DIRS];
|
|
1925
1836
|
for (const dirName of allDirs) {
|
|
1926
1837
|
const checkPath = join8(dirPath, dirName);
|
|
1927
|
-
if (
|
|
1838
|
+
if (existsSync11(checkPath)) {
|
|
1928
1839
|
const stats = await stat(checkPath);
|
|
1929
1840
|
if (stats.isDirectory()) {
|
|
1930
1841
|
return true;
|
|
@@ -1936,7 +1847,7 @@ async function shouldWrapDirectory(dirPath) {
|
|
|
1936
1847
|
async function findMatchingDirs(basePath, names) {
|
|
1937
1848
|
for (const name of names) {
|
|
1938
1849
|
const dirPath = join8(basePath, name);
|
|
1939
|
-
if (
|
|
1850
|
+
if (existsSync11(dirPath)) {
|
|
1940
1851
|
const stats = await stat(dirPath);
|
|
1941
1852
|
if (stats.isDirectory()) {
|
|
1942
1853
|
return dirPath;
|
|
@@ -1947,7 +1858,7 @@ async function findMatchingDirs(basePath, names) {
|
|
|
1947
1858
|
}
|
|
1948
1859
|
async function findContentItems(dirPath, filePatterns) {
|
|
1949
1860
|
const items = [];
|
|
1950
|
-
if (!
|
|
1861
|
+
if (!existsSync11(dirPath)) {
|
|
1951
1862
|
return items;
|
|
1952
1863
|
}
|
|
1953
1864
|
const entries = (await readdir(dirPath, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -1955,7 +1866,7 @@ async function findContentItems(dirPath, filePatterns) {
|
|
|
1955
1866
|
const entryPath = join8(dirPath, entry.name);
|
|
1956
1867
|
if (entry.isDirectory()) {
|
|
1957
1868
|
for (const pattern of filePatterns) {
|
|
1958
|
-
if (
|
|
1869
|
+
if (existsSync11(join8(entryPath, pattern))) {
|
|
1959
1870
|
items.push({
|
|
1960
1871
|
name: entry.name,
|
|
1961
1872
|
path: entryPath,
|
|
@@ -1977,11 +1888,11 @@ async function findContentItems(dirPath, filePatterns) {
|
|
|
1977
1888
|
}
|
|
1978
1889
|
async function parsePluginJson(dirPath) {
|
|
1979
1890
|
const pluginJsonPath = join8(dirPath, ".claude-plugin", "plugin.json");
|
|
1980
|
-
if (!
|
|
1891
|
+
if (!existsSync11(pluginJsonPath)) {
|
|
1981
1892
|
return null;
|
|
1982
1893
|
}
|
|
1983
1894
|
try {
|
|
1984
|
-
const content = await
|
|
1895
|
+
const content = await readFile9(pluginJsonPath, "utf-8");
|
|
1985
1896
|
const data = JSON.parse(content);
|
|
1986
1897
|
const result = {
|
|
1987
1898
|
name: data.name,
|
|
@@ -2002,11 +1913,11 @@ async function parsePluginJson(dirPath) {
|
|
|
2002
1913
|
}
|
|
2003
1914
|
async function readReadmeDescription(dirPath) {
|
|
2004
1915
|
const readmePath = join8(dirPath, "README.md");
|
|
2005
|
-
if (!
|
|
1916
|
+
if (!existsSync11(readmePath)) {
|
|
2006
1917
|
return null;
|
|
2007
1918
|
}
|
|
2008
1919
|
try {
|
|
2009
|
-
const content = await
|
|
1920
|
+
const content = await readFile9(readmePath, "utf-8");
|
|
2010
1921
|
const lines = content.split(`
|
|
2011
1922
|
`);
|
|
2012
1923
|
let description = "";
|
|
@@ -2045,7 +1956,7 @@ async function normalizeFolderNames(repoPath) {
|
|
|
2045
1956
|
for (const { from, to } of renameMappings) {
|
|
2046
1957
|
const fromPath = join8(repoPath, from);
|
|
2047
1958
|
const toPath = join8(repoPath, to);
|
|
2048
|
-
if (
|
|
1959
|
+
if (existsSync11(fromPath) && !existsSync11(toPath)) {
|
|
2049
1960
|
try {
|
|
2050
1961
|
const stats = await stat(fromPath);
|
|
2051
1962
|
if (stats.isDirectory()) {
|
|
@@ -2144,7 +2055,7 @@ async function fetchGitCapabilitySource(id, config, options) {
|
|
|
2144
2055
|
let repoPath;
|
|
2145
2056
|
if (config.path) {
|
|
2146
2057
|
const tempPath = join8(OMNI_LOCAL, "_temp", `${id}-repo`);
|
|
2147
|
-
if (
|
|
2058
|
+
if (existsSync11(join8(tempPath, ".git"))) {
|
|
2148
2059
|
if (!options?.silent) {
|
|
2149
2060
|
console.log(` Checking ${id}...`);
|
|
2150
2061
|
}
|
|
@@ -2160,17 +2071,17 @@ async function fetchGitCapabilitySource(id, config, options) {
|
|
|
2160
2071
|
updated = true;
|
|
2161
2072
|
}
|
|
2162
2073
|
const sourcePath = join8(tempPath, config.path);
|
|
2163
|
-
if (!
|
|
2074
|
+
if (!existsSync11(sourcePath)) {
|
|
2164
2075
|
throw new Error(`Path not found in repository: ${config.path}`);
|
|
2165
2076
|
}
|
|
2166
|
-
if (
|
|
2077
|
+
if (existsSync11(targetPath)) {
|
|
2167
2078
|
await rm(targetPath, { recursive: true });
|
|
2168
2079
|
}
|
|
2169
2080
|
await mkdir(join8(targetPath, ".."), { recursive: true });
|
|
2170
2081
|
await cp(sourcePath, targetPath, { recursive: true });
|
|
2171
2082
|
repoPath = targetPath;
|
|
2172
2083
|
} else {
|
|
2173
|
-
if (
|
|
2084
|
+
if (existsSync11(join8(targetPath, ".git"))) {
|
|
2174
2085
|
if (!options?.silent) {
|
|
2175
2086
|
console.log(` Checking ${id}...`);
|
|
2176
2087
|
}
|
|
@@ -2209,9 +2120,9 @@ async function fetchGitCapabilitySource(id, config, options) {
|
|
|
2209
2120
|
}
|
|
2210
2121
|
let version = shortCommit(commit);
|
|
2211
2122
|
const pkgJsonPath = join8(repoPath, "package.json");
|
|
2212
|
-
if (
|
|
2123
|
+
if (existsSync11(pkgJsonPath)) {
|
|
2213
2124
|
try {
|
|
2214
|
-
const pkgJson = JSON.parse(await
|
|
2125
|
+
const pkgJson = JSON.parse(await readFile9(pkgJsonPath, "utf-8"));
|
|
2215
2126
|
if (pkgJson.version) {
|
|
2216
2127
|
version = pkgJson.version;
|
|
2217
2128
|
}
|
|
@@ -2229,29 +2140,29 @@ async function fetchGitCapabilitySource(id, config, options) {
|
|
|
2229
2140
|
async function fetchFileCapabilitySource(id, config, options) {
|
|
2230
2141
|
const sourcePath = parseFileSourcePath(config.source);
|
|
2231
2142
|
const targetPath = getSourceCapabilityPath(id);
|
|
2232
|
-
if (!
|
|
2143
|
+
if (!existsSync11(sourcePath)) {
|
|
2233
2144
|
throw new Error(`File source not found: ${sourcePath}`);
|
|
2234
2145
|
}
|
|
2235
2146
|
const sourceStats = await stat(sourcePath);
|
|
2236
2147
|
if (!sourceStats.isDirectory()) {
|
|
2237
2148
|
throw new Error(`File source must be a directory: ${sourcePath}`);
|
|
2238
2149
|
}
|
|
2239
|
-
if (!
|
|
2150
|
+
if (!existsSync11(join8(sourcePath, "capability.toml"))) {
|
|
2240
2151
|
throw new Error(`No capability.toml found in: ${sourcePath}`);
|
|
2241
2152
|
}
|
|
2242
2153
|
if (!options?.silent) {
|
|
2243
2154
|
console.log(` Copying ${id} from ${sourcePath}...`);
|
|
2244
2155
|
}
|
|
2245
|
-
if (
|
|
2156
|
+
if (existsSync11(targetPath)) {
|
|
2246
2157
|
await rm(targetPath, { recursive: true });
|
|
2247
2158
|
}
|
|
2248
2159
|
await mkdir(join8(targetPath, ".."), { recursive: true });
|
|
2249
2160
|
await cp(sourcePath, targetPath, { recursive: true });
|
|
2250
2161
|
let version = "local";
|
|
2251
2162
|
const capTomlPath = join8(targetPath, "capability.toml");
|
|
2252
|
-
if (
|
|
2163
|
+
if (existsSync11(capTomlPath)) {
|
|
2253
2164
|
try {
|
|
2254
|
-
const content = await
|
|
2165
|
+
const content = await readFile9(capTomlPath, "utf-8");
|
|
2255
2166
|
const parsed = parseToml2(content);
|
|
2256
2167
|
const capability = parsed["capability"];
|
|
2257
2168
|
if (capability?.["version"] && typeof capability["version"] === "string") {
|
|
@@ -2342,12 +2253,12 @@ async function generateMcpCapabilityToml(id, mcpConfig, targetPath) {
|
|
|
2342
2253
|
}
|
|
2343
2254
|
async function isGeneratedMcpCapability(capabilityDir) {
|
|
2344
2255
|
const tomlPath = join8(capabilityDir, "capability.toml");
|
|
2345
|
-
if (!
|
|
2256
|
+
if (!existsSync11(tomlPath)) {
|
|
2346
2257
|
console.warn("no capability.toml found in", capabilityDir);
|
|
2347
2258
|
return false;
|
|
2348
2259
|
}
|
|
2349
2260
|
try {
|
|
2350
|
-
const content = await
|
|
2261
|
+
const content = await readFile9(tomlPath, "utf-8");
|
|
2351
2262
|
const parsed = parseToml2(content);
|
|
2352
2263
|
const capability = parsed["capability"];
|
|
2353
2264
|
const metadata = capability?.["metadata"];
|
|
@@ -2358,7 +2269,7 @@ async function isGeneratedMcpCapability(capabilityDir) {
|
|
|
2358
2269
|
}
|
|
2359
2270
|
async function cleanupStaleMcpCapabilities(currentMcpIds) {
|
|
2360
2271
|
const capabilitiesDir = join8(OMNI_LOCAL, "capabilities");
|
|
2361
|
-
if (!
|
|
2272
|
+
if (!existsSync11(capabilitiesDir)) {
|
|
2362
2273
|
return;
|
|
2363
2274
|
}
|
|
2364
2275
|
const entries = await readdir(capabilitiesDir, { withFileTypes: true });
|
|
@@ -2466,7 +2377,7 @@ async function checkForUpdates(config) {
|
|
|
2466
2377
|
continue;
|
|
2467
2378
|
}
|
|
2468
2379
|
const gitConfig = sourceConfig;
|
|
2469
|
-
if (!
|
|
2380
|
+
if (!existsSync11(join8(targetPath, ".git"))) {
|
|
2470
2381
|
updates.push({
|
|
2471
2382
|
id,
|
|
2472
2383
|
source: gitConfig.source,
|
|
@@ -2502,15 +2413,15 @@ async function checkForUpdates(config) {
|
|
|
2502
2413
|
return updates;
|
|
2503
2414
|
}
|
|
2504
2415
|
// src/config/provider.ts
|
|
2505
|
-
import { existsSync as
|
|
2506
|
-
import { readFile as
|
|
2416
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
2417
|
+
import { readFile as readFile10, writeFile as writeFile4 } from "node:fs/promises";
|
|
2507
2418
|
import { parse as parse2 } from "smol-toml";
|
|
2508
2419
|
var PROVIDER_CONFIG_PATH = ".omni/provider.toml";
|
|
2509
2420
|
async function loadProviderConfig() {
|
|
2510
|
-
if (!
|
|
2421
|
+
if (!existsSync12(PROVIDER_CONFIG_PATH)) {
|
|
2511
2422
|
return { provider: "claude" };
|
|
2512
2423
|
}
|
|
2513
|
-
const content = await
|
|
2424
|
+
const content = await readFile10(PROVIDER_CONFIG_PATH, "utf-8");
|
|
2514
2425
|
const parsed = parse2(content);
|
|
2515
2426
|
return parsed;
|
|
2516
2427
|
}
|
|
@@ -2552,14 +2463,14 @@ function parseProviderFlag(flag) {
|
|
|
2552
2463
|
throw new Error(`Invalid provider: ${flag}. Must be 'claude', 'codex', or 'both'.`);
|
|
2553
2464
|
}
|
|
2554
2465
|
// src/config/toml-patcher.ts
|
|
2555
|
-
import { existsSync as
|
|
2556
|
-
import { readFile as
|
|
2466
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
2467
|
+
import { readFile as readFile11, writeFile as writeFile5 } from "node:fs/promises";
|
|
2557
2468
|
var CONFIG_PATH2 = "omni.toml";
|
|
2558
2469
|
async function readConfigFile() {
|
|
2559
|
-
if (!
|
|
2470
|
+
if (!existsSync13(CONFIG_PATH2)) {
|
|
2560
2471
|
return "";
|
|
2561
2472
|
}
|
|
2562
|
-
return
|
|
2473
|
+
return readFile11(CONFIG_PATH2, "utf-8");
|
|
2563
2474
|
}
|
|
2564
2475
|
async function writeConfigFile(content) {
|
|
2565
2476
|
await writeFile5(CONFIG_PATH2, content, "utf-8");
|
|
@@ -2649,10 +2560,8 @@ function formatMcpConfig(name, config) {
|
|
|
2649
2560
|
lines.push(`url = "${config.url}"`);
|
|
2650
2561
|
}
|
|
2651
2562
|
if (config.env && Object.keys(config.env).length > 0) {
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
lines.push(`${key} = "${value}"`);
|
|
2655
|
-
}
|
|
2563
|
+
const entries = Object.entries(config.env).map(([key, value]) => `${key} = "${value}"`);
|
|
2564
|
+
lines.push(`env = { ${entries.join(", ")} }`);
|
|
2656
2565
|
}
|
|
2657
2566
|
if (config.headers && Object.keys(config.headers).length > 0) {
|
|
2658
2567
|
lines.push(`[mcps.${name}.headers]`);
|
|
@@ -2758,15 +2667,15 @@ function escapeRegExp(str) {
|
|
|
2758
2667
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2759
2668
|
}
|
|
2760
2669
|
// src/mcp-json/manager.ts
|
|
2761
|
-
import { existsSync as
|
|
2762
|
-
import { readFile as
|
|
2670
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
2671
|
+
import { readFile as readFile12, writeFile as writeFile6 } from "node:fs/promises";
|
|
2763
2672
|
var MCP_JSON_PATH = ".mcp.json";
|
|
2764
2673
|
async function readMcpJson() {
|
|
2765
|
-
if (!
|
|
2674
|
+
if (!existsSync14(MCP_JSON_PATH)) {
|
|
2766
2675
|
return { mcpServers: {} };
|
|
2767
2676
|
}
|
|
2768
2677
|
try {
|
|
2769
|
-
const content = await
|
|
2678
|
+
const content = await readFile12(MCP_JSON_PATH, "utf-8");
|
|
2770
2679
|
const parsed = JSON.parse(content);
|
|
2771
2680
|
return {
|
|
2772
2681
|
mcpServers: parsed.mcpServers || {}
|
|
@@ -2845,19 +2754,19 @@ async function syncMcpJson(capabilities2, previousManifest, options = {}) {
|
|
|
2845
2754
|
}
|
|
2846
2755
|
}
|
|
2847
2756
|
// src/state/manifest.ts
|
|
2848
|
-
import { existsSync as
|
|
2849
|
-
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";
|
|
2850
2759
|
var MANIFEST_PATH = ".omni/state/manifest.json";
|
|
2851
2760
|
var CURRENT_VERSION = 1;
|
|
2852
2761
|
async function loadManifest() {
|
|
2853
|
-
if (!
|
|
2762
|
+
if (!existsSync15(MANIFEST_PATH)) {
|
|
2854
2763
|
return {
|
|
2855
2764
|
version: CURRENT_VERSION,
|
|
2856
2765
|
syncedAt: new Date().toISOString(),
|
|
2857
2766
|
capabilities: {}
|
|
2858
2767
|
};
|
|
2859
2768
|
}
|
|
2860
|
-
const content = await
|
|
2769
|
+
const content = await readFile13(MANIFEST_PATH, "utf-8");
|
|
2861
2770
|
return JSON.parse(content);
|
|
2862
2771
|
}
|
|
2863
2772
|
async function saveManifest(manifest) {
|
|
@@ -2897,14 +2806,14 @@ async function cleanupStaleResources(previousManifest, currentCapabilityIds) {
|
|
|
2897
2806
|
}
|
|
2898
2807
|
for (const skillName of resources.skills) {
|
|
2899
2808
|
const skillDir = `.claude/skills/${skillName}`;
|
|
2900
|
-
if (
|
|
2809
|
+
if (existsSync15(skillDir)) {
|
|
2901
2810
|
rmSync(skillDir, { recursive: true });
|
|
2902
2811
|
result.deletedSkills.push(skillName);
|
|
2903
2812
|
}
|
|
2904
2813
|
}
|
|
2905
2814
|
for (const ruleName of resources.rules) {
|
|
2906
2815
|
const rulePath = `.cursor/rules/omnidev-${ruleName}.mdc`;
|
|
2907
|
-
if (
|
|
2816
|
+
if (existsSync15(rulePath)) {
|
|
2908
2817
|
rmSync(rulePath);
|
|
2909
2818
|
result.deletedRules.push(ruleName);
|
|
2910
2819
|
}
|
|
@@ -2913,17 +2822,17 @@ async function cleanupStaleResources(previousManifest, currentCapabilityIds) {
|
|
|
2913
2822
|
return result;
|
|
2914
2823
|
}
|
|
2915
2824
|
// src/state/providers.ts
|
|
2916
|
-
import { existsSync as
|
|
2917
|
-
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";
|
|
2918
2827
|
var STATE_DIR2 = ".omni/state";
|
|
2919
2828
|
var PROVIDERS_PATH = `${STATE_DIR2}/providers.json`;
|
|
2920
2829
|
var DEFAULT_PROVIDERS = ["claude-code"];
|
|
2921
2830
|
async function readEnabledProviders() {
|
|
2922
|
-
if (!
|
|
2831
|
+
if (!existsSync16(PROVIDERS_PATH)) {
|
|
2923
2832
|
return DEFAULT_PROVIDERS;
|
|
2924
2833
|
}
|
|
2925
2834
|
try {
|
|
2926
|
-
const content = await
|
|
2835
|
+
const content = await readFile14(PROVIDERS_PATH, "utf-8");
|
|
2927
2836
|
const state = JSON.parse(content);
|
|
2928
2837
|
return state.enabled.length > 0 ? state.enabled : DEFAULT_PROVIDERS;
|
|
2929
2838
|
} catch {
|
|
@@ -2955,10 +2864,10 @@ async function isProviderEnabled(providerId) {
|
|
|
2955
2864
|
import { spawn as spawn2 } from "node:child_process";
|
|
2956
2865
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
2957
2866
|
async function installCapabilityDependencies(silent) {
|
|
2958
|
-
const { existsSync:
|
|
2867
|
+
const { existsSync: existsSync17, readdirSync: readdirSync7 } = await import("node:fs");
|
|
2959
2868
|
const { join: join9 } = await import("node:path");
|
|
2960
2869
|
const capabilitiesDir = ".omni/capabilities";
|
|
2961
|
-
if (!
|
|
2870
|
+
if (!existsSync17(capabilitiesDir)) {
|
|
2962
2871
|
return;
|
|
2963
2872
|
}
|
|
2964
2873
|
const entries = readdirSync7(capabilitiesDir, { withFileTypes: true });
|
|
@@ -2980,14 +2889,14 @@ async function installCapabilityDependencies(silent) {
|
|
|
2980
2889
|
}
|
|
2981
2890
|
const capabilityPath = join9(capabilitiesDir, entry.name);
|
|
2982
2891
|
const packageJsonPath = join9(capabilityPath, "package.json");
|
|
2983
|
-
if (!
|
|
2892
|
+
if (!existsSync17(packageJsonPath)) {
|
|
2984
2893
|
continue;
|
|
2985
2894
|
}
|
|
2986
2895
|
if (!silent) {
|
|
2987
2896
|
console.log(`Installing dependencies for ${capabilityPath}...`);
|
|
2988
2897
|
}
|
|
2989
2898
|
await new Promise((resolve2, reject) => {
|
|
2990
|
-
const useNpmCi = hasNpm &&
|
|
2899
|
+
const useNpmCi = hasNpm && existsSync17(join9(capabilityPath, "package-lock.json"));
|
|
2991
2900
|
const cmd = hasBun ? "bun" : "npm";
|
|
2992
2901
|
const args = hasBun ? ["install"] : useNpmCi ? ["ci"] : ["install"];
|
|
2993
2902
|
const proc = spawn2(cmd, args, {
|
|
@@ -3349,7 +3258,6 @@ export {
|
|
|
3349
3258
|
version,
|
|
3350
3259
|
validateHooksConfig,
|
|
3351
3260
|
validateHook,
|
|
3352
|
-
validateEnv,
|
|
3353
3261
|
transformToOmnidev,
|
|
3354
3262
|
transformToClaude,
|
|
3355
3263
|
transformHooksConfig,
|
|
@@ -3383,7 +3291,6 @@ export {
|
|
|
3383
3291
|
loadManifest,
|
|
3384
3292
|
loadLockFile,
|
|
3385
3293
|
loadHooksFromCapability,
|
|
3386
|
-
loadEnvironment,
|
|
3387
3294
|
loadDocs,
|
|
3388
3295
|
loadConfig,
|
|
3389
3296
|
loadCommands,
|
|
@@ -3392,7 +3299,6 @@ export {
|
|
|
3392
3299
|
loadCapability,
|
|
3393
3300
|
loadBaseConfig,
|
|
3394
3301
|
isValidMatcherPattern,
|
|
3395
|
-
isSecretEnvVar,
|
|
3396
3302
|
isProviderEnabled,
|
|
3397
3303
|
isPromptHookEvent,
|
|
3398
3304
|
isMatcherEvent,
|
package/package.json
CHANGED
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/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");
|
|
@@ -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
|
@@ -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/types/index.ts
CHANGED
|
@@ -48,12 +48,6 @@ export interface CapabilityExports {
|
|
|
48
48
|
gitignore?: string[];
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export interface EnvDeclaration {
|
|
52
|
-
required?: boolean;
|
|
53
|
-
secret?: boolean;
|
|
54
|
-
default?: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
51
|
export interface SyncConfig {
|
|
58
52
|
on_sync?: string;
|
|
59
53
|
}
|
|
@@ -65,7 +59,6 @@ export interface CliConfig {
|
|
|
65
59
|
export interface CapabilityConfig {
|
|
66
60
|
capability: CapabilityMetadata;
|
|
67
61
|
exports?: CapabilityExports;
|
|
68
|
-
env?: Record<string, EnvDeclaration | Record<string, never>>;
|
|
69
62
|
mcp?: McpConfig;
|
|
70
63
|
sync?: SyncConfig;
|
|
71
64
|
cli?: CliConfig;
|
|
@@ -84,7 +77,7 @@ export interface McpToolSchema {
|
|
|
84
77
|
*
|
|
85
78
|
* - **stdio**: Local process using stdin/stdout (default)
|
|
86
79
|
* - Requires: command
|
|
87
|
-
* - Optional: args,
|
|
80
|
+
* - Optional: args, cwd
|
|
88
81
|
*
|
|
89
82
|
* - **http**: Remote HTTP server (recommended for remote servers)
|
|
90
83
|
* - Requires: url
|
|
@@ -269,7 +262,6 @@ export interface OmniConfig {
|
|
|
269
262
|
project?: string;
|
|
270
263
|
active_profile?: string;
|
|
271
264
|
always_enabled_capabilities?: string[];
|
|
272
|
-
env?: Record<string, string>;
|
|
273
265
|
profiles?: Record<string, ProfileConfig>;
|
|
274
266
|
providers?: {
|
|
275
267
|
enabled?: Provider[];
|
package/src/config/env.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import type { EnvDeclaration } from "../types";
|
|
4
|
-
|
|
5
|
-
const ENV_FILE = ".omni/.env";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Load environment variables from .omni/.env file and merge with process.env.
|
|
9
|
-
* Process environment variables take precedence over file values.
|
|
10
|
-
*
|
|
11
|
-
* @returns Merged environment variables
|
|
12
|
-
*/
|
|
13
|
-
export async function loadEnvironment(): Promise<Record<string, string>> {
|
|
14
|
-
const env: Record<string, string> = {};
|
|
15
|
-
|
|
16
|
-
// Load from .omni/.env
|
|
17
|
-
if (existsSync(ENV_FILE)) {
|
|
18
|
-
const content = await readFile(ENV_FILE, "utf-8");
|
|
19
|
-
for (const line of content.split("\n")) {
|
|
20
|
-
const trimmed = line.trim();
|
|
21
|
-
// Skip empty lines and comments
|
|
22
|
-
if (trimmed && !trimmed.startsWith("#")) {
|
|
23
|
-
const eqIndex = trimmed.indexOf("=");
|
|
24
|
-
if (eqIndex > 0) {
|
|
25
|
-
const key = trimmed.slice(0, eqIndex).trim();
|
|
26
|
-
const value = trimmed.slice(eqIndex + 1).trim();
|
|
27
|
-
// Remove quotes if present
|
|
28
|
-
const unquotedValue =
|
|
29
|
-
(value.startsWith('"') && value.endsWith('"')) ||
|
|
30
|
-
(value.startsWith("'") && value.endsWith("'"))
|
|
31
|
-
? value.slice(1, -1)
|
|
32
|
-
: value;
|
|
33
|
-
env[key] = unquotedValue;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Process env takes precedence - filter out undefined values
|
|
40
|
-
const processEnv: Record<string, string> = {};
|
|
41
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
42
|
-
if (value !== undefined) {
|
|
43
|
-
processEnv[key] = value;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return { ...env, ...processEnv };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Validate that all required environment variables are present.
|
|
52
|
-
* Checks declarations from capability config and throws descriptive errors for missing vars.
|
|
53
|
-
*
|
|
54
|
-
* @param declarations - Environment variable declarations from capability.toml
|
|
55
|
-
* @param env - Loaded environment variables
|
|
56
|
-
* @param capabilityId - ID of the capability being validated
|
|
57
|
-
* @throws Error if required environment variables are missing
|
|
58
|
-
*/
|
|
59
|
-
export function validateEnv(
|
|
60
|
-
declarations: Record<string, EnvDeclaration | Record<string, never>>,
|
|
61
|
-
env: Record<string, string | undefined>,
|
|
62
|
-
capabilityId: string,
|
|
63
|
-
): void {
|
|
64
|
-
const missing: string[] = [];
|
|
65
|
-
|
|
66
|
-
for (const [key, decl] of Object.entries(declarations)) {
|
|
67
|
-
const declaration = decl as EnvDeclaration;
|
|
68
|
-
const value = env[key] ?? declaration.default;
|
|
69
|
-
|
|
70
|
-
if (declaration.required && !value) {
|
|
71
|
-
missing.push(key);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (missing.length > 0) {
|
|
76
|
-
throw new Error(
|
|
77
|
-
`Missing required environment variable${missing.length > 1 ? "s" : ""} for capability "${capabilityId}": ${missing.join(", ")}. ` +
|
|
78
|
-
`Set ${missing.length > 1 ? "them" : "it"} in .omni/.env or as environment variable${missing.length > 1 ? "s" : ""}.`,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Check if an environment variable should be treated as a secret.
|
|
85
|
-
* Secrets should be masked in logs and error messages.
|
|
86
|
-
*
|
|
87
|
-
* @param key - Environment variable name
|
|
88
|
-
* @param declarations - Environment variable declarations from capability.toml
|
|
89
|
-
* @returns true if the variable is marked as secret
|
|
90
|
-
*/
|
|
91
|
-
export function isSecretEnvVar(
|
|
92
|
-
key: string,
|
|
93
|
-
declarations: Record<string, EnvDeclaration | Record<string, never>>,
|
|
94
|
-
): boolean {
|
|
95
|
-
const decl = declarations[key] as EnvDeclaration | undefined;
|
|
96
|
-
return decl?.secret === true;
|
|
97
|
-
}
|