@omnidev-ai/core 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -959,8 +959,18 @@ async function loadCapabilityConfig(capabilityPath) {
959
959
  return config;
960
960
  }
961
961
  async function importCapabilityExports(capabilityPath) {
962
- const indexPath = join7(capabilityPath, "index.ts");
963
- if (!existsSync8(indexPath)) {
962
+ const builtIndexPath = join7(capabilityPath, "dist", "index.js");
963
+ const jsIndexPath = join7(capabilityPath, "index.js");
964
+ const tsIndexPath = join7(capabilityPath, "index.ts");
965
+ let indexPath = null;
966
+ if (existsSync8(builtIndexPath)) {
967
+ indexPath = builtIndexPath;
968
+ } else if (existsSync8(jsIndexPath)) {
969
+ indexPath = jsIndexPath;
970
+ } else if (existsSync8(tsIndexPath)) {
971
+ indexPath = tsIndexPath;
972
+ }
973
+ if (!indexPath) {
964
974
  return {};
965
975
  }
966
976
  try {
@@ -972,6 +982,11 @@ async function importCapabilityExports(capabilityPath) {
972
982
  if (errorMessage.includes("Cannot find module")) {
973
983
  const match = errorMessage.match(/Cannot find module '([^']+)'/);
974
984
  const missingModule = match ? match[1] : "unknown";
985
+ if (indexPath === tsIndexPath && missingModule?.endsWith(".js")) {
986
+ throw new Error(`Capability at ${capabilityPath} has TypeScript files but no built output.
987
+ Add a "build" script to package.json (e.g., "build": "tsc") and run it to compile TypeScript.
988
+ The build output should be in dist/index.js.`);
989
+ }
975
990
  throw new Error(`Missing dependency '${missingModule}' for capability at ${capabilityPath}.
976
991
  If this is a project-specific capability, install dependencies or remove it from .omni/capabilities/`);
977
992
  }
@@ -1634,6 +1649,7 @@ function getActiveProviders(config) {
1634
1649
  }
1635
1650
 
1636
1651
  // src/capability/sources.ts
1652
+ import { createHash } from "node:crypto";
1637
1653
  var OMNI_LOCAL = ".omni";
1638
1654
  var SKILL_DIRS = ["skills", "skill"];
1639
1655
  var AGENT_DIRS = ["agents", "agent", "subagents", "subagent"];
@@ -1751,12 +1767,18 @@ function stringifyLockFile(lockFile) {
1751
1767
  lines.push(`[capabilities.${id}]`);
1752
1768
  lines.push(`source = "${entry.source}"`);
1753
1769
  lines.push(`version = "${entry.version}"`);
1770
+ if (entry.version_source) {
1771
+ lines.push(`version_source = "${entry.version_source}"`);
1772
+ }
1754
1773
  if (entry.commit) {
1755
1774
  lines.push(`commit = "${entry.commit}"`);
1756
1775
  }
1757
1776
  if (entry.ref) {
1758
1777
  lines.push(`ref = "${entry.ref}"`);
1759
1778
  }
1779
+ if (entry.content_hash) {
1780
+ lines.push(`content_hash = "${entry.content_hash}"`);
1781
+ }
1760
1782
  lines.push(`updated_at = "${entry.updated_at}"`);
1761
1783
  lines.push("");
1762
1784
  }
@@ -1786,6 +1808,83 @@ async function getRepoCommit(repoPath) {
1786
1808
  function shortCommit(commit) {
1787
1809
  return commit.substring(0, 7);
1788
1810
  }
1811
+ function shortContentHash(hash) {
1812
+ return hash.substring(0, 12);
1813
+ }
1814
+ var CONTENT_HASH_EXCLUDES = [
1815
+ ".git",
1816
+ "node_modules",
1817
+ ".omni",
1818
+ "__pycache__",
1819
+ ".pytest_cache",
1820
+ ".mypy_cache",
1821
+ "dist",
1822
+ "build",
1823
+ ".DS_Store",
1824
+ "Thumbs.db"
1825
+ ];
1826
+ async function computeContentHash(dirPath, excludePatterns = CONTENT_HASH_EXCLUDES) {
1827
+ const hash = createHash("sha256");
1828
+ const files = [];
1829
+ async function collectFiles(currentPath, relativeTo) {
1830
+ const entries = await readdir(currentPath, { withFileTypes: true });
1831
+ entries.sort((a, b) => a.name.localeCompare(b.name));
1832
+ for (const entry of entries) {
1833
+ const fullPath = join8(currentPath, entry.name);
1834
+ const relativePath = fullPath.slice(relativeTo.length + 1);
1835
+ if (excludePatterns.some((pattern) => entry.name === pattern || relativePath.startsWith(`${pattern}/`))) {
1836
+ continue;
1837
+ }
1838
+ if (entry.isDirectory()) {
1839
+ await collectFiles(fullPath, relativeTo);
1840
+ } else if (entry.isFile()) {
1841
+ const content = await readFile9(fullPath);
1842
+ files.push({ relativePath, content });
1843
+ }
1844
+ }
1845
+ }
1846
+ await collectFiles(dirPath, dirPath);
1847
+ files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
1848
+ for (const file of files) {
1849
+ hash.update(file.relativePath);
1850
+ hash.update(file.content);
1851
+ }
1852
+ return hash.digest("hex");
1853
+ }
1854
+ async function detectDisplayVersion(dirPath, fallback, fallbackSource) {
1855
+ const capTomlPath = join8(dirPath, "capability.toml");
1856
+ if (existsSync11(capTomlPath)) {
1857
+ try {
1858
+ const content = await readFile9(capTomlPath, "utf-8");
1859
+ const parsed = parseToml2(content);
1860
+ const capability = parsed["capability"];
1861
+ if (capability?.["version"] && typeof capability["version"] === "string") {
1862
+ return { version: capability["version"], source: "capability.toml" };
1863
+ }
1864
+ } catch {}
1865
+ }
1866
+ const pluginJsonPath = join8(dirPath, ".claude-plugin", "plugin.json");
1867
+ if (existsSync11(pluginJsonPath)) {
1868
+ try {
1869
+ const content = await readFile9(pluginJsonPath, "utf-8");
1870
+ const parsed = JSON.parse(content);
1871
+ if (parsed.version && typeof parsed.version === "string") {
1872
+ return { version: parsed.version, source: "plugin.json" };
1873
+ }
1874
+ } catch {}
1875
+ }
1876
+ const pkgJsonPath = join8(dirPath, "package.json");
1877
+ if (existsSync11(pkgJsonPath)) {
1878
+ try {
1879
+ const content = await readFile9(pkgJsonPath, "utf-8");
1880
+ const parsed = JSON.parse(content);
1881
+ if (parsed.version && typeof parsed.version === "string") {
1882
+ return { version: parsed.version, source: "package.json" };
1883
+ }
1884
+ } catch {}
1885
+ }
1886
+ return { version: fallback, source: fallbackSource };
1887
+ }
1789
1888
  async function cloneRepo(gitUrl, targetPath, ref) {
1790
1889
  await mkdir(join8(targetPath, ".."), { recursive: true });
1791
1890
  const args = ["clone", "--depth", "1"];
@@ -2054,15 +2153,9 @@ async function fetchGitCapabilitySource(id, config, options) {
2054
2153
  if (config.path) {
2055
2154
  const tempPath = join8(OMNI_LOCAL, "_temp", `${id}-repo`);
2056
2155
  if (existsSync11(join8(tempPath, ".git"))) {
2057
- if (!options?.silent) {
2058
- console.log(` Checking ${id}...`);
2059
- }
2060
2156
  updated = await fetchRepo(tempPath, config.ref);
2061
2157
  commit = await getRepoCommit(tempPath);
2062
2158
  } else {
2063
- if (!options?.silent) {
2064
- console.log(` Cloning ${id} from ${config.source}...`);
2065
- }
2066
2159
  await mkdir(join8(tempPath, ".."), { recursive: true });
2067
2160
  await cloneRepo(gitUrl, tempPath, config.ref);
2068
2161
  commit = await getRepoCommit(tempPath);
@@ -2077,6 +2170,7 @@ async function fetchGitCapabilitySource(id, config, options) {
2077
2170
  }
2078
2171
  await mkdir(join8(targetPath, ".."), { recursive: true });
2079
2172
  await cp(sourcePath, targetPath, { recursive: true });
2173
+ await rm(tempPath, { recursive: true });
2080
2174
  repoPath = targetPath;
2081
2175
  } else {
2082
2176
  if (existsSync11(join8(targetPath, ".git"))) {
@@ -2116,20 +2210,12 @@ async function fetchGitCapabilitySource(id, config, options) {
2116
2210
  }
2117
2211
  }
2118
2212
  }
2119
- let version = shortCommit(commit);
2120
- const pkgJsonPath = join8(repoPath, "package.json");
2121
- if (existsSync11(pkgJsonPath)) {
2122
- try {
2123
- const pkgJson = JSON.parse(await readFile9(pkgJsonPath, "utf-8"));
2124
- if (pkgJson.version) {
2125
- version = pkgJson.version;
2126
- }
2127
- } catch {}
2128
- }
2213
+ const versionResult = await detectDisplayVersion(repoPath, shortCommit(commit), "commit");
2129
2214
  return {
2130
2215
  id,
2131
2216
  path: targetPath,
2132
- version,
2217
+ version: versionResult.version,
2218
+ versionSource: versionResult.source,
2133
2219
  commit,
2134
2220
  updated,
2135
2221
  wrapped: needsWrap
@@ -2145,8 +2231,14 @@ async function fetchFileCapabilitySource(id, config, options) {
2145
2231
  if (!sourceStats.isDirectory()) {
2146
2232
  throw new Error(`File source must be a directory: ${sourcePath}`);
2147
2233
  }
2148
- if (!existsSync11(join8(sourcePath, "capability.toml"))) {
2149
- throw new Error(`No capability.toml found in: ${sourcePath}`);
2234
+ const contentHash = await computeContentHash(sourcePath);
2235
+ const hasCapToml = existsSync11(join8(sourcePath, "capability.toml"));
2236
+ let needsWrap = false;
2237
+ if (!hasCapToml) {
2238
+ needsWrap = await shouldWrapDirectory(sourcePath);
2239
+ if (!needsWrap) {
2240
+ throw new Error(`No capability.toml found in: ${sourcePath} (and no wrappable content detected)`);
2241
+ }
2150
2242
  }
2151
2243
  if (!options?.silent) {
2152
2244
  console.log(` Copying ${id} from ${sourcePath}...`);
@@ -2156,26 +2248,86 @@ async function fetchFileCapabilitySource(id, config, options) {
2156
2248
  }
2157
2249
  await mkdir(join8(targetPath, ".."), { recursive: true });
2158
2250
  await cp(sourcePath, targetPath, { recursive: true });
2159
- let version = "local";
2160
- const capTomlPath = join8(targetPath, "capability.toml");
2161
- if (existsSync11(capTomlPath)) {
2162
- try {
2163
- const content = await readFile9(capTomlPath, "utf-8");
2164
- const parsed = parseToml2(content);
2165
- const capability = parsed["capability"];
2166
- if (capability?.["version"] && typeof capability["version"] === "string") {
2167
- version = capability["version"];
2251
+ if (needsWrap) {
2252
+ await normalizeFolderNames(targetPath);
2253
+ const content = await discoverContent(targetPath);
2254
+ await generateFileSourceCapabilityToml(id, config.source, shortContentHash(contentHash), content, targetPath);
2255
+ if (!options?.silent) {
2256
+ const parts = [];
2257
+ if (content.skills.length > 0)
2258
+ parts.push(`${content.skills.length} skills`);
2259
+ if (content.agents.length > 0)
2260
+ parts.push(`${content.agents.length} agents`);
2261
+ if (content.commands.length > 0)
2262
+ parts.push(`${content.commands.length} commands`);
2263
+ if (parts.length > 0) {
2264
+ console.log(` Wrapped: ${parts.join(", ")}`);
2168
2265
  }
2169
- } catch {}
2266
+ }
2170
2267
  }
2268
+ const versionResult = await detectDisplayVersion(targetPath, shortContentHash(contentHash), "content_hash");
2171
2269
  return {
2172
2270
  id,
2173
2271
  path: targetPath,
2174
- version,
2272
+ version: versionResult.version,
2273
+ versionSource: versionResult.source,
2274
+ contentHash,
2175
2275
  updated: true,
2176
- wrapped: false
2276
+ wrapped: needsWrap
2177
2277
  };
2178
2278
  }
2279
+ async function generateFileSourceCapabilityToml(id, source, hashVersion, content, targetPath) {
2280
+ const pluginMeta = await parsePluginJson(targetPath);
2281
+ const readmeDesc = await readReadmeDescription(targetPath);
2282
+ let description;
2283
+ if (pluginMeta?.description) {
2284
+ description = pluginMeta.description;
2285
+ } else if (readmeDesc) {
2286
+ description = readmeDesc;
2287
+ } else {
2288
+ const parts = [];
2289
+ if (content.skills.length > 0) {
2290
+ parts.push(`${content.skills.length} skill${content.skills.length > 1 ? "s" : ""}`);
2291
+ }
2292
+ if (content.agents.length > 0) {
2293
+ parts.push(`${content.agents.length} agent${content.agents.length > 1 ? "s" : ""}`);
2294
+ }
2295
+ if (content.commands.length > 0) {
2296
+ parts.push(`${content.commands.length} command${content.commands.length > 1 ? "s" : ""}`);
2297
+ }
2298
+ description = parts.length > 0 ? `${parts.join(", ")}` : `Wrapped from ${source}`;
2299
+ }
2300
+ const name = pluginMeta?.name || `${id} (wrapped)`;
2301
+ const version = pluginMeta?.version || hashVersion;
2302
+ let tomlContent = `# Auto-generated by OmniDev - DO NOT EDIT
2303
+ # This capability was wrapped from a local directory
2304
+
2305
+ [capability]
2306
+ id = "${id}"
2307
+ name = "${name}"
2308
+ version = "${version}"
2309
+ description = "${description}"
2310
+ `;
2311
+ if (pluginMeta?.author?.name || pluginMeta?.author?.email) {
2312
+ tomlContent += `
2313
+ [capability.author]
2314
+ `;
2315
+ if (pluginMeta.author.name) {
2316
+ tomlContent += `name = "${pluginMeta.author.name}"
2317
+ `;
2318
+ }
2319
+ if (pluginMeta.author.email) {
2320
+ tomlContent += `email = "${pluginMeta.author.email}"
2321
+ `;
2322
+ }
2323
+ }
2324
+ tomlContent += `
2325
+ [capability.metadata]
2326
+ wrapped = true
2327
+ source = "${source}"
2328
+ `;
2329
+ await writeFile3(join8(targetPath, "capability.toml"), tomlContent, "utf-8");
2330
+ }
2179
2331
  async function fetchCapabilitySource(id, sourceConfig, options) {
2180
2332
  const config = parseSourceConfig(sourceConfig);
2181
2333
  if (isFileSourceConfig(sourceConfig) || isFileSource(config.source)) {
@@ -2298,13 +2450,14 @@ async function generateMcpCapabilities(config) {
2298
2450
  }
2299
2451
  async function fetchAllCapabilitySources(config, options) {
2300
2452
  await generateMcpCapabilities(config);
2453
+ const tempDir = join8(OMNI_LOCAL, "_temp");
2454
+ if (existsSync11(tempDir)) {
2455
+ await rm(tempDir, { recursive: true });
2456
+ }
2301
2457
  const sources = config.capabilities?.sources;
2302
2458
  if (!sources || Object.keys(sources).length === 0) {
2303
2459
  return [];
2304
2460
  }
2305
- if (!options?.silent) {
2306
- console.log("Fetching capability sources...");
2307
- }
2308
2461
  const results = [];
2309
2462
  const lockFile = await loadLockFile();
2310
2463
  let lockUpdated = false;
@@ -2315,11 +2468,15 @@ async function fetchAllCapabilitySources(config, options) {
2315
2468
  const lockEntry = {
2316
2469
  source: typeof source === "string" ? source : source.source,
2317
2470
  version: result.version,
2471
+ version_source: result.versionSource,
2318
2472
  updated_at: new Date().toISOString()
2319
2473
  };
2320
2474
  if (result.commit) {
2321
2475
  lockEntry.commit = result.commit;
2322
2476
  }
2477
+ if (result.contentHash) {
2478
+ lockEntry.content_hash = result.contentHash;
2479
+ }
2323
2480
  if (!isFileSourceConfig(source)) {
2324
2481
  const gitConfig = parseSourceConfig(source);
2325
2482
  if (gitConfig.ref) {
@@ -2327,14 +2484,10 @@ async function fetchAllCapabilitySources(config, options) {
2327
2484
  }
2328
2485
  }
2329
2486
  const existing = lockFile.capabilities[id];
2330
- const hasChanged = !existing || existing.commit !== result.commit;
2487
+ const hasChanged = !existing || result.commit && existing.commit !== result.commit || result.contentHash && existing.content_hash !== result.contentHash;
2331
2488
  if (hasChanged) {
2332
2489
  lockFile.capabilities[id] = lockEntry;
2333
2490
  lockUpdated = true;
2334
- if (!options?.silent && result.updated) {
2335
- const oldVersion = existing?.version || "new";
2336
- console.log(` ${result.wrapped ? "+" : "~"} ${id}: ${oldVersion} -> ${result.version}`);
2337
- }
2338
2491
  }
2339
2492
  } catch (error) {
2340
2493
  console.error(` Failed to fetch ${id}: ${error}`);
@@ -2343,14 +2496,6 @@ async function fetchAllCapabilitySources(config, options) {
2343
2496
  if (lockUpdated) {
2344
2497
  await saveLockFile(lockFile);
2345
2498
  }
2346
- if (!options?.silent && results.length > 0) {
2347
- const updated = results.filter((r) => r.updated).length;
2348
- if (updated > 0) {
2349
- console.log(` Updated ${updated} capability source(s)`);
2350
- } else {
2351
- console.log(` All ${results.length} source(s) up to date`);
2352
- }
2353
- }
2354
2499
  return results;
2355
2500
  }
2356
2501
  async function checkForUpdates(config) {
@@ -2728,7 +2873,7 @@ function buildMcpServerConfig(mcp) {
2728
2873
  }
2729
2874
  return config2;
2730
2875
  }
2731
- async function syncMcpJson(capabilities2, previousManifest, options = {}) {
2876
+ async function syncMcpJson(capabilities2, previousManifest) {
2732
2877
  const mcpJson = await readMcpJson();
2733
2878
  const previouslyManagedMcps = new Set;
2734
2879
  for (const resources of Object.values(previousManifest.capabilities)) {
@@ -2739,17 +2884,12 @@ async function syncMcpJson(capabilities2, previousManifest, options = {}) {
2739
2884
  for (const serverName of previouslyManagedMcps) {
2740
2885
  delete mcpJson.mcpServers[serverName];
2741
2886
  }
2742
- let addedCount = 0;
2743
2887
  for (const cap of capabilities2) {
2744
2888
  if (cap.config.mcp) {
2745
2889
  mcpJson.mcpServers[cap.id] = buildMcpServerConfig(cap.config.mcp);
2746
- addedCount++;
2747
2890
  }
2748
2891
  }
2749
2892
  await writeMcpJson(mcpJson);
2750
- if (!options.silent) {
2751
- console.log(` - .mcp.json (${addedCount} MCP server(s))`);
2752
- }
2753
2893
  }
2754
2894
  // src/state/manifest.ts
2755
2895
  import { existsSync as existsSync15, mkdirSync as mkdirSync2, rmSync } from "node:fs";
@@ -2858,14 +2998,104 @@ async function isProviderEnabled(providerId) {
2858
2998
  const current = await readEnabledProviders();
2859
2999
  return current.includes(providerId);
2860
3000
  }
3001
+ // src/state/security-allows.ts
3002
+ import { existsSync as existsSync17, mkdirSync as mkdirSync4 } from "node:fs";
3003
+ import { readFile as readFile15, writeFile as writeFile9 } from "node:fs/promises";
3004
+ var OMNI_DIR = ".omni";
3005
+ var SECURITY_PATH = `${OMNI_DIR}/security.json`;
3006
+ var DEFAULT_STATE = {
3007
+ version: 1,
3008
+ modifiedAt: new Date().toISOString(),
3009
+ allows: {}
3010
+ };
3011
+ async function readSecurityAllows() {
3012
+ if (!existsSync17(SECURITY_PATH)) {
3013
+ return { ...DEFAULT_STATE };
3014
+ }
3015
+ try {
3016
+ const content = await readFile15(SECURITY_PATH, "utf-8");
3017
+ const state = JSON.parse(content);
3018
+ return state;
3019
+ } catch {
3020
+ return { ...DEFAULT_STATE };
3021
+ }
3022
+ }
3023
+ async function writeSecurityAllows(state) {
3024
+ mkdirSync4(OMNI_DIR, { recursive: true });
3025
+ state.modifiedAt = new Date().toISOString();
3026
+ await writeFile9(SECURITY_PATH, `${JSON.stringify(state, null, 2)}
3027
+ `, "utf-8");
3028
+ }
3029
+ async function addSecurityAllow(capabilityId, findingType) {
3030
+ const state = await readSecurityAllows();
3031
+ if (!state.allows[capabilityId]) {
3032
+ state.allows[capabilityId] = [];
3033
+ }
3034
+ if (state.allows[capabilityId].includes(findingType)) {
3035
+ return false;
3036
+ }
3037
+ state.allows[capabilityId].push(findingType);
3038
+ await writeSecurityAllows(state);
3039
+ return true;
3040
+ }
3041
+ async function removeSecurityAllow(capabilityId, findingType) {
3042
+ const state = await readSecurityAllows();
3043
+ if (!state.allows[capabilityId]) {
3044
+ return false;
3045
+ }
3046
+ const index = state.allows[capabilityId].indexOf(findingType);
3047
+ if (index === -1) {
3048
+ return false;
3049
+ }
3050
+ state.allows[capabilityId].splice(index, 1);
3051
+ if (state.allows[capabilityId].length === 0) {
3052
+ delete state.allows[capabilityId];
3053
+ }
3054
+ await writeSecurityAllows(state);
3055
+ return true;
3056
+ }
3057
+ async function isSecurityAllowed(capabilityId, findingType) {
3058
+ const state = await readSecurityAllows();
3059
+ const allows = state.allows[capabilityId];
3060
+ if (!allows)
3061
+ return false;
3062
+ return allows.includes(findingType);
3063
+ }
3064
+ async function getCapabilityAllows(capabilityId) {
3065
+ const state = await readSecurityAllows();
3066
+ return state.allows[capabilityId] ?? [];
3067
+ }
3068
+ async function getAllSecurityAllows() {
3069
+ const state = await readSecurityAllows();
3070
+ const result = [];
3071
+ for (const [capabilityId, findingTypes] of Object.entries(state.allows)) {
3072
+ for (const findingType of findingTypes) {
3073
+ result.push({ capabilityId, findingType });
3074
+ }
3075
+ }
3076
+ return result;
3077
+ }
3078
+ async function clearCapabilityAllows(capabilityId) {
3079
+ const state = await readSecurityAllows();
3080
+ if (!state.allows[capabilityId]) {
3081
+ return false;
3082
+ }
3083
+ delete state.allows[capabilityId];
3084
+ await writeSecurityAllows(state);
3085
+ return true;
3086
+ }
3087
+ async function clearAllSecurityAllows() {
3088
+ const state = { ...DEFAULT_STATE };
3089
+ await writeSecurityAllows(state);
3090
+ }
2861
3091
  // src/sync.ts
2862
3092
  import { spawn as spawn2 } from "node:child_process";
2863
- import { mkdirSync as mkdirSync4 } from "node:fs";
3093
+ import { mkdirSync as mkdirSync5 } from "node:fs";
2864
3094
  async function installCapabilityDependencies(silent) {
2865
- const { existsSync: existsSync17, readdirSync: readdirSync7 } = await import("node:fs");
3095
+ const { existsSync: existsSync18, readdirSync: readdirSync7, readFileSync: readFileSync2 } = await import("node:fs");
2866
3096
  const { join: join9 } = await import("node:path");
2867
3097
  const capabilitiesDir = ".omni/capabilities";
2868
- if (!existsSync17(capabilitiesDir)) {
3098
+ if (!existsSync18(capabilitiesDir)) {
2869
3099
  return;
2870
3100
  }
2871
3101
  const entries = readdirSync7(capabilitiesDir, { withFileTypes: true });
@@ -2887,31 +3117,70 @@ async function installCapabilityDependencies(silent) {
2887
3117
  }
2888
3118
  const capabilityPath = join9(capabilitiesDir, entry.name);
2889
3119
  const packageJsonPath = join9(capabilityPath, "package.json");
2890
- if (!existsSync17(packageJsonPath)) {
3120
+ if (!existsSync18(packageJsonPath)) {
2891
3121
  continue;
2892
3122
  }
2893
- if (!silent) {
2894
- console.log(`Installing dependencies for ${capabilityPath}...`);
2895
- }
2896
3123
  await new Promise((resolve2, reject) => {
2897
- const useNpmCi = hasNpm && existsSync17(join9(capabilityPath, "package-lock.json"));
3124
+ const useNpmCi = hasNpm && existsSync18(join9(capabilityPath, "package-lock.json"));
2898
3125
  const cmd = hasBun ? "bun" : "npm";
2899
3126
  const args = hasBun ? ["install"] : useNpmCi ? ["ci"] : ["install"];
2900
3127
  const proc = spawn2(cmd, args, {
2901
3128
  cwd: capabilityPath,
2902
- stdio: silent ? "ignore" : "inherit"
3129
+ stdio: "pipe"
3130
+ });
3131
+ let stderr = "";
3132
+ proc.stderr?.on("data", (data) => {
3133
+ stderr += data.toString();
2903
3134
  });
2904
3135
  proc.on("close", (code) => {
2905
3136
  if (code === 0) {
2906
3137
  resolve2();
2907
3138
  } else {
2908
- reject(new Error(`Failed to install dependencies for ${capabilityPath}`));
3139
+ reject(new Error(`Failed to install dependencies for ${capabilityPath}:
3140
+ ${stderr}`));
2909
3141
  }
2910
3142
  });
2911
3143
  proc.on("error", (error) => {
2912
3144
  reject(error);
2913
3145
  });
2914
3146
  });
3147
+ const hasIndexTs = existsSync18(join9(capabilityPath, "index.ts"));
3148
+ const hasBuiltIndex = existsSync18(join9(capabilityPath, "dist", "index.js"));
3149
+ if (hasIndexTs && !hasBuiltIndex) {
3150
+ let hasBuildScript = false;
3151
+ try {
3152
+ const pkgJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
3153
+ hasBuildScript = Boolean(pkgJson.scripts?.build);
3154
+ } catch {}
3155
+ if (hasBuildScript) {
3156
+ await new Promise((resolve2, reject) => {
3157
+ const cmd = hasBun ? "bun" : "npm";
3158
+ const args = ["run", "build"];
3159
+ const proc = spawn2(cmd, args, {
3160
+ cwd: capabilityPath,
3161
+ stdio: "pipe"
3162
+ });
3163
+ let stderr = "";
3164
+ proc.stderr?.on("data", (data) => {
3165
+ stderr += data.toString();
3166
+ });
3167
+ proc.on("close", (code) => {
3168
+ if (code === 0) {
3169
+ resolve2();
3170
+ } else {
3171
+ reject(new Error(`Failed to build capability ${capabilityPath}:
3172
+ ${stderr}`));
3173
+ }
3174
+ });
3175
+ proc.on("error", (error) => {
3176
+ reject(error);
3177
+ });
3178
+ });
3179
+ } else if (!silent) {
3180
+ console.warn(`Warning: Capability at ${capabilityPath} has index.ts but no build script.
3181
+ Add a "build" script to package.json (e.g., "build": "tsc") to compile TypeScript.`);
3182
+ }
3183
+ }
2915
3184
  }
2916
3185
  }
2917
3186
  async function buildSyncBundle(options) {
@@ -2945,23 +3214,11 @@ async function buildSyncBundle(options) {
2945
3214
  async function syncAgentConfiguration(options) {
2946
3215
  const silent = options?.silent ?? false;
2947
3216
  const adapters = options?.adapters ?? [];
2948
- if (!silent) {
2949
- console.log("Syncing agent configuration...");
2950
- }
2951
3217
  const { bundle } = await buildSyncBundle({ silent });
2952
3218
  const capabilities2 = bundle.capabilities;
2953
3219
  const previousManifest = await loadManifest();
2954
3220
  const currentCapabilityIds = new Set(capabilities2.map((c) => c.id));
2955
- const cleanupResult = await cleanupStaleResources(previousManifest, currentCapabilityIds);
2956
- if (!silent && (cleanupResult.deletedSkills.length > 0 || cleanupResult.deletedRules.length > 0)) {
2957
- console.log("Cleaned up stale resources:");
2958
- if (cleanupResult.deletedSkills.length > 0) {
2959
- console.log(` - Removed ${cleanupResult.deletedSkills.length} skill(s): ${cleanupResult.deletedSkills.join(", ")}`);
2960
- }
2961
- if (cleanupResult.deletedRules.length > 0) {
2962
- console.log(` - Removed ${cleanupResult.deletedRules.length} rule(s): ${cleanupResult.deletedRules.join(", ")}`);
2963
- }
2964
- }
3221
+ await cleanupStaleResources(previousManifest, currentCapabilityIds);
2965
3222
  for (const capability of capabilities2) {
2966
3223
  const defaultExport = capability.exports.default;
2967
3224
  if (defaultExport && typeof defaultExport.sync === "function") {
@@ -2982,8 +3239,8 @@ async function syncAgentConfiguration(options) {
2982
3239
  }
2983
3240
  }
2984
3241
  }
2985
- mkdirSync4(".omni", { recursive: true });
2986
- await syncMcpJson(capabilities2, previousManifest, { silent });
3242
+ mkdirSync5(".omni", { recursive: true });
3243
+ await syncMcpJson(capabilities2, previousManifest);
2987
3244
  const newManifest = buildManifestFromCapabilities(capabilities2);
2988
3245
  await saveManifest(newManifest);
2989
3246
  if (adapters.length > 0) {
@@ -2994,22 +3251,12 @@ async function syncAgentConfiguration(options) {
2994
3251
  };
2995
3252
  for (const adapter of adapters) {
2996
3253
  try {
2997
- const result = await adapter.sync(bundle, ctx);
2998
- if (!silent && result.filesWritten.length > 0) {
2999
- console.log(` - ${adapter.displayName}: ${result.filesWritten.length} files`);
3000
- }
3254
+ await adapter.sync(bundle, ctx);
3001
3255
  } catch (error) {
3002
3256
  console.error(`Error running ${adapter.displayName} adapter:`, error);
3003
3257
  }
3004
3258
  }
3005
3259
  }
3006
- if (!silent) {
3007
- console.log("✓ Synced:");
3008
- console.log(` - ${bundle.docs.length} docs, ${bundle.rules.length} rules`);
3009
- if (adapters.length > 0) {
3010
- console.log(` - Provider adapters: ${adapters.map((a) => a.displayName).join(", ")}`);
3011
- }
3012
- }
3013
3260
  return {
3014
3261
  capabilities: capabilities2.map((c) => c.id),
3015
3262
  skillCount: bundle.skills.length,
@@ -3031,6 +3278,427 @@ function generateInstructionsContent(rules, _docs) {
3031
3278
  }
3032
3279
  return content.trim();
3033
3280
  }
3281
+ // src/security/scanner.ts
3282
+ import { existsSync as existsSync18 } from "node:fs";
3283
+ import { lstat, readdir as readdir2, readFile as readFile16, readlink, realpath } from "node:fs/promises";
3284
+ import { join as join9, relative, resolve as resolve2 } from "node:path";
3285
+
3286
+ // src/security/types.ts
3287
+ var DEFAULT_SECURITY_CONFIG = {
3288
+ mode: "off",
3289
+ trusted_sources: [],
3290
+ scan: {
3291
+ unicode: true,
3292
+ symlinks: true,
3293
+ scripts: true,
3294
+ binaries: false
3295
+ }
3296
+ };
3297
+ var DEFAULT_SCAN_SETTINGS = {
3298
+ unicode: true,
3299
+ symlinks: true,
3300
+ scripts: true,
3301
+ binaries: false
3302
+ };
3303
+
3304
+ // src/security/scanner.ts
3305
+ var UNICODE_PATTERNS = {
3306
+ bidi: [
3307
+ 8234,
3308
+ 8235,
3309
+ 8236,
3310
+ 8237,
3311
+ 8238,
3312
+ 8294,
3313
+ 8295,
3314
+ 8296,
3315
+ 8297
3316
+ ],
3317
+ zeroWidth: [
3318
+ 8203,
3319
+ 8204,
3320
+ 8205,
3321
+ 8288,
3322
+ 65279
3323
+ ],
3324
+ control: [
3325
+ 0,
3326
+ 1,
3327
+ 2,
3328
+ 3,
3329
+ 4,
3330
+ 5,
3331
+ 6,
3332
+ 7,
3333
+ 8,
3334
+ 11,
3335
+ 12,
3336
+ 14,
3337
+ 15,
3338
+ 16,
3339
+ 17,
3340
+ 18,
3341
+ 19,
3342
+ 20,
3343
+ 21,
3344
+ 22,
3345
+ 23,
3346
+ 24,
3347
+ 25,
3348
+ 26,
3349
+ 27,
3350
+ 28,
3351
+ 29,
3352
+ 30,
3353
+ 31,
3354
+ 127
3355
+ ]
3356
+ };
3357
+ var SUSPICIOUS_SCRIPT_PATTERNS = [
3358
+ {
3359
+ pattern: /curl\s+.*\|\s*(ba)?sh/i,
3360
+ message: "Piping curl to shell can execute arbitrary remote code",
3361
+ severity: "high"
3362
+ },
3363
+ {
3364
+ pattern: /wget\s+.*\|\s*(ba)?sh/i,
3365
+ message: "Piping wget to shell can execute arbitrary remote code",
3366
+ severity: "high"
3367
+ },
3368
+ {
3369
+ pattern: /eval\s*\(\s*\$\(/,
3370
+ message: "eval with command substitution can be dangerous",
3371
+ severity: "medium"
3372
+ },
3373
+ {
3374
+ pattern: /rm\s+-rf\s+\/($|\s)|rm\s+-rf\s+~($|\s)/,
3375
+ message: "Recursive deletion from root or home directory",
3376
+ severity: "critical"
3377
+ },
3378
+ {
3379
+ pattern: /chmod\s+777/,
3380
+ message: "Setting world-writable permissions",
3381
+ severity: "medium"
3382
+ },
3383
+ {
3384
+ pattern: /\bsudo\b.*>/,
3385
+ message: "Using sudo with output redirection",
3386
+ severity: "medium"
3387
+ },
3388
+ {
3389
+ pattern: /base64\s+-d.*\|\s*(ba)?sh/i,
3390
+ message: "Decoding and executing base64 content",
3391
+ severity: "high"
3392
+ }
3393
+ ];
3394
+ var BINARY_EXTENSIONS = new Set([
3395
+ ".exe",
3396
+ ".dll",
3397
+ ".so",
3398
+ ".dylib",
3399
+ ".bin",
3400
+ ".o",
3401
+ ".a",
3402
+ ".lib",
3403
+ ".pyc",
3404
+ ".pyo",
3405
+ ".class",
3406
+ ".jar",
3407
+ ".war",
3408
+ ".ear",
3409
+ ".wasm",
3410
+ ".node"
3411
+ ]);
3412
+ var TEXT_EXTENSIONS = new Set([
3413
+ ".md",
3414
+ ".txt",
3415
+ ".toml",
3416
+ ".yaml",
3417
+ ".yml",
3418
+ ".json",
3419
+ ".js",
3420
+ ".ts",
3421
+ ".sh",
3422
+ ".bash",
3423
+ ".zsh",
3424
+ ".fish",
3425
+ ".py",
3426
+ ".rb"
3427
+ ]);
3428
+ async function scanFileForUnicode(filePath, relativePath) {
3429
+ const findings = [];
3430
+ try {
3431
+ const content = await readFile16(filePath, "utf-8");
3432
+ const lines = content.split(`
3433
+ `);
3434
+ for (let lineNum = 0;lineNum < lines.length; lineNum++) {
3435
+ const line = lines[lineNum];
3436
+ if (!line)
3437
+ continue;
3438
+ for (let col = 0;col < line.length; col++) {
3439
+ const codePoint = line.codePointAt(col);
3440
+ if (codePoint === undefined)
3441
+ continue;
3442
+ if (UNICODE_PATTERNS.bidi.includes(codePoint)) {
3443
+ findings.push({
3444
+ type: "unicode_bidi",
3445
+ severity: "high",
3446
+ file: relativePath,
3447
+ line: lineNum + 1,
3448
+ column: col + 1,
3449
+ message: "Bidirectional text override character detected",
3450
+ details: `Codepoint U+${codePoint.toString(16).toUpperCase().padStart(4, "0")}`
3451
+ });
3452
+ }
3453
+ if (UNICODE_PATTERNS.zeroWidth.includes(codePoint)) {
3454
+ if (codePoint === 65279 && lineNum === 0 && col === 0)
3455
+ continue;
3456
+ findings.push({
3457
+ type: "unicode_zero_width",
3458
+ severity: "medium",
3459
+ file: relativePath,
3460
+ line: lineNum + 1,
3461
+ column: col + 1,
3462
+ message: "Zero-width character detected",
3463
+ details: `Codepoint U+${codePoint.toString(16).toUpperCase().padStart(4, "0")}`
3464
+ });
3465
+ }
3466
+ if (UNICODE_PATTERNS.control.includes(codePoint)) {
3467
+ findings.push({
3468
+ type: "unicode_control",
3469
+ severity: "medium",
3470
+ file: relativePath,
3471
+ line: lineNum + 1,
3472
+ column: col + 1,
3473
+ message: "Suspicious control character detected",
3474
+ details: `Codepoint U+${codePoint.toString(16).toUpperCase().padStart(4, "0")}`
3475
+ });
3476
+ }
3477
+ }
3478
+ }
3479
+ } catch {}
3480
+ return findings;
3481
+ }
3482
+ async function scanFileForScripts(filePath, relativePath) {
3483
+ const findings = [];
3484
+ try {
3485
+ const content = await readFile16(filePath, "utf-8");
3486
+ const lines = content.split(`
3487
+ `);
3488
+ for (let lineNum = 0;lineNum < lines.length; lineNum++) {
3489
+ const line = lines[lineNum];
3490
+ if (!line)
3491
+ continue;
3492
+ for (const { pattern, message, severity } of SUSPICIOUS_SCRIPT_PATTERNS) {
3493
+ if (pattern.test(line)) {
3494
+ findings.push({
3495
+ type: "suspicious_script",
3496
+ severity,
3497
+ file: relativePath,
3498
+ line: lineNum + 1,
3499
+ message,
3500
+ details: line.trim().substring(0, 100)
3501
+ });
3502
+ }
3503
+ }
3504
+ }
3505
+ } catch {}
3506
+ return findings;
3507
+ }
3508
+ async function checkSymlink(symlinkPath, relativePath, capabilityRoot) {
3509
+ try {
3510
+ const linkTarget = await readlink(symlinkPath);
3511
+ const resolvedTarget = resolve2(join9(symlinkPath, "..", linkTarget));
3512
+ const normalizedRoot = await realpath(capabilityRoot);
3513
+ if (linkTarget.startsWith("/")) {
3514
+ return {
3515
+ type: "symlink_absolute",
3516
+ severity: "high",
3517
+ file: relativePath,
3518
+ message: "Symlink points to an absolute path",
3519
+ details: `Target: ${linkTarget}`
3520
+ };
3521
+ }
3522
+ const relativeToRoot = relative(normalizedRoot, resolvedTarget);
3523
+ if (relativeToRoot.startsWith("..") || relativeToRoot.startsWith("/")) {
3524
+ return {
3525
+ type: "symlink_escape",
3526
+ severity: "critical",
3527
+ file: relativePath,
3528
+ message: "Symlink escapes capability directory",
3529
+ details: `Resolves to: ${resolvedTarget}`
3530
+ };
3531
+ }
3532
+ } catch {}
3533
+ return null;
3534
+ }
3535
+ function isBinaryFile(filePath) {
3536
+ const ext = filePath.toLowerCase().substring(filePath.lastIndexOf("."));
3537
+ return BINARY_EXTENSIONS.has(ext);
3538
+ }
3539
+ function isTextFile(filePath) {
3540
+ const ext = filePath.toLowerCase().substring(filePath.lastIndexOf("."));
3541
+ return TEXT_EXTENSIONS.has(ext);
3542
+ }
3543
+ async function scanCapability(capabilityId, capabilityPath, settings = DEFAULT_SCAN_SETTINGS) {
3544
+ const startTime = Date.now();
3545
+ const findings = [];
3546
+ if (!existsSync18(capabilityPath)) {
3547
+ return {
3548
+ capabilityId,
3549
+ path: capabilityPath,
3550
+ findings: [],
3551
+ passed: true,
3552
+ duration: Date.now() - startTime
3553
+ };
3554
+ }
3555
+ async function scanDirectory(dirPath) {
3556
+ const entries = await readdir2(dirPath, { withFileTypes: true });
3557
+ for (const entry of entries) {
3558
+ const fullPath = join9(dirPath, entry.name);
3559
+ const relativePath = relative(capabilityPath, fullPath);
3560
+ if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "__pycache__") {
3561
+ continue;
3562
+ }
3563
+ const stats = await lstat(fullPath);
3564
+ if (stats.isSymbolicLink() && settings.symlinks) {
3565
+ const finding = await checkSymlink(fullPath, relativePath, capabilityPath);
3566
+ if (finding) {
3567
+ findings.push(finding);
3568
+ }
3569
+ continue;
3570
+ }
3571
+ if (entry.isDirectory()) {
3572
+ await scanDirectory(fullPath);
3573
+ continue;
3574
+ }
3575
+ if (entry.isFile()) {
3576
+ if (settings.binaries && isBinaryFile(fullPath)) {
3577
+ findings.push({
3578
+ type: "binary_file",
3579
+ severity: "low",
3580
+ file: relativePath,
3581
+ message: "Binary file detected in capability"
3582
+ });
3583
+ }
3584
+ if (isTextFile(fullPath)) {
3585
+ if (settings.unicode) {
3586
+ const unicodeFindings = await scanFileForUnicode(fullPath, relativePath);
3587
+ findings.push(...unicodeFindings);
3588
+ }
3589
+ if (settings.scripts) {
3590
+ const ext = fullPath.toLowerCase().substring(fullPath.lastIndexOf("."));
3591
+ if ([".sh", ".bash", ".zsh", ".fish", ".py", ".rb", ".js", ".ts"].includes(ext)) {
3592
+ const scriptFindings = await scanFileForScripts(fullPath, relativePath);
3593
+ findings.push(...scriptFindings);
3594
+ }
3595
+ }
3596
+ }
3597
+ }
3598
+ }
3599
+ }
3600
+ await scanDirectory(capabilityPath);
3601
+ return {
3602
+ capabilityId,
3603
+ path: capabilityPath,
3604
+ findings,
3605
+ passed: findings.length === 0 || findings.every((f) => f.severity === "low"),
3606
+ duration: Date.now() - startTime
3607
+ };
3608
+ }
3609
+ async function scanCapabilities(capabilities2, config2 = {}) {
3610
+ const settings = {
3611
+ ...DEFAULT_SCAN_SETTINGS,
3612
+ ...config2.scan
3613
+ };
3614
+ const results = [];
3615
+ for (const cap of capabilities2) {
3616
+ if (config2.trusted_sources && config2.trusted_sources.length > 0) {}
3617
+ const result = await scanCapability(cap.id, cap.path, settings);
3618
+ results.push(result);
3619
+ }
3620
+ const findingsByType = {
3621
+ unicode_bidi: 0,
3622
+ unicode_zero_width: 0,
3623
+ unicode_control: 0,
3624
+ symlink_escape: 0,
3625
+ symlink_absolute: 0,
3626
+ suspicious_script: 0,
3627
+ binary_file: 0
3628
+ };
3629
+ const findingsBySeverity = {
3630
+ low: 0,
3631
+ medium: 0,
3632
+ high: 0,
3633
+ critical: 0
3634
+ };
3635
+ let totalFindings = 0;
3636
+ let capabilitiesWithFindings = 0;
3637
+ for (const result of results) {
3638
+ if (result.findings.length > 0) {
3639
+ capabilitiesWithFindings++;
3640
+ }
3641
+ for (const finding of result.findings) {
3642
+ totalFindings++;
3643
+ findingsByType[finding.type]++;
3644
+ findingsBySeverity[finding.severity]++;
3645
+ }
3646
+ }
3647
+ return {
3648
+ totalCapabilities: capabilities2.length,
3649
+ capabilitiesWithFindings,
3650
+ totalFindings,
3651
+ findingsByType,
3652
+ findingsBySeverity,
3653
+ results,
3654
+ allPassed: results.every((r) => r.passed)
3655
+ };
3656
+ }
3657
+ function formatScanResults(summary, verbose = false) {
3658
+ const lines = [];
3659
+ lines.push("Security Scan Results");
3660
+ lines.push("=====================");
3661
+ lines.push("");
3662
+ if (summary.totalFindings === 0) {
3663
+ lines.push("✓ No security issues found");
3664
+ return lines.join(`
3665
+ `);
3666
+ }
3667
+ lines.push(`Found ${summary.totalFindings} issue(s) in ${summary.capabilitiesWithFindings} capability(ies)`);
3668
+ lines.push("");
3669
+ if (summary.findingsBySeverity.critical > 0) {
3670
+ lines.push(` ✗ Critical: ${summary.findingsBySeverity.critical}`);
3671
+ }
3672
+ if (summary.findingsBySeverity.high > 0) {
3673
+ lines.push(` ✗ High: ${summary.findingsBySeverity.high}`);
3674
+ }
3675
+ if (summary.findingsBySeverity.medium > 0) {
3676
+ lines.push(` ! Medium: ${summary.findingsBySeverity.medium}`);
3677
+ }
3678
+ if (summary.findingsBySeverity.low > 0) {
3679
+ lines.push(` · Low: ${summary.findingsBySeverity.low}`);
3680
+ }
3681
+ lines.push("");
3682
+ if (verbose) {
3683
+ for (const result of summary.results) {
3684
+ if (result.findings.length === 0)
3685
+ continue;
3686
+ lines.push(`${result.capabilityId}:`);
3687
+ for (const finding of result.findings) {
3688
+ const location = finding.line ? `:${finding.line}${finding.column ? `:${finding.column}` : ""}` : "";
3689
+ const severity = finding.severity.toUpperCase().padEnd(8);
3690
+ lines.push(` [${severity}] ${finding.file}${location}`);
3691
+ lines.push(` ${finding.message}`);
3692
+ if (finding.details) {
3693
+ lines.push(` ${finding.details}`);
3694
+ }
3695
+ }
3696
+ lines.push("");
3697
+ }
3698
+ }
3699
+ return lines.join(`
3700
+ `);
3701
+ }
3034
3702
  // src/templates/agents.ts
3035
3703
  function generateAgentsTemplate() {
3036
3704
  return `# Project Instructions
@@ -3202,6 +3870,7 @@ function getVersion() {
3202
3870
  return version;
3203
3871
  }
3204
3872
  export {
3873
+ writeSecurityAllows,
3205
3874
  writeProviderConfig,
3206
3875
  writeMcpJson,
3207
3876
  writeEnabledProviders,
@@ -3218,9 +3887,13 @@ export {
3218
3887
  sourceToGitUrl,
3219
3888
  setProfile,
3220
3889
  setActiveProfile,
3890
+ scanCapability,
3891
+ scanCapabilities,
3221
3892
  saveManifest,
3222
3893
  saveLockFile,
3223
3894
  resolveEnabledCapabilities,
3895
+ removeSecurityAllow,
3896
+ readSecurityAllows,
3224
3897
  readMcpJson,
3225
3898
  readEnabledProviders,
3226
3899
  readCapabilityIdFromPath,
@@ -3251,6 +3924,7 @@ export {
3251
3924
  loadCapability,
3252
3925
  loadBaseConfig,
3253
3926
  isValidMatcherPattern,
3927
+ isSecurityAllowed,
3254
3928
  isProviderEnabled,
3255
3929
  isPromptHookEvent,
3256
3930
  isMatcherEvent,
@@ -3271,6 +3945,8 @@ export {
3271
3945
  getHooksConfigPath,
3272
3946
  getEventsWithHooks,
3273
3947
  getEnabledCapabilities,
3948
+ getCapabilityAllows,
3949
+ getAllSecurityAllows,
3274
3950
  getActiveProviders,
3275
3951
  getActiveProfile,
3276
3952
  generateSkillTemplate,
@@ -3281,6 +3957,7 @@ export {
3281
3957
  generateClaudeTemplate,
3282
3958
  generateCapabilityToml2 as generateCapabilityToml,
3283
3959
  generateAgentsTemplate,
3960
+ formatScanResults,
3284
3961
  findDuplicateCommands,
3285
3962
  fetchCapabilitySource,
3286
3963
  fetchAllCapabilitySources,
@@ -3295,6 +3972,8 @@ export {
3295
3972
  countHooks,
3296
3973
  containsOmnidevVariables,
3297
3974
  containsClaudeVariables,
3975
+ clearCapabilityAllows,
3976
+ clearAllSecurityAllows,
3298
3977
  clearActiveProfileState,
3299
3978
  cleanupStaleResources,
3300
3979
  checkForUpdates,
@@ -3303,6 +3982,7 @@ export {
3303
3982
  buildManifestFromCapabilities,
3304
3983
  buildCommand,
3305
3984
  buildCapabilityRegistry,
3985
+ addSecurityAllow,
3306
3986
  VARIABLE_MAPPINGS,
3307
3987
  SESSION_START_MATCHERS,
3308
3988
  PROMPT_HOOK_EVENTS,
@@ -3313,6 +3993,8 @@ export {
3313
3993
  HOOK_EVENTS,
3314
3994
  HOOKS_DIRECTORY,
3315
3995
  HOOKS_CONFIG_FILENAME,
3996
+ DEFAULT_SECURITY_CONFIG,
3997
+ DEFAULT_SCAN_SETTINGS,
3316
3998
  DEFAULT_PROMPT_TIMEOUT,
3317
3999
  DEFAULT_COMMAND_TIMEOUT,
3318
4000
  COMMON_TOOL_MATCHERS