@omnidev-ai/core 0.4.0 → 0.5.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 +600 -664
- package/dist/index.js +1841 -1915
- package/dist/shared/chunk-1dqs11h6.js +20 -0
- package/dist/test-utils/index.d.ts +97 -101
- package/dist/test-utils/index.js +203 -234
- package/package.json +5 -3
- package/src/capability/AGENTS.md +58 -0
- package/src/capability/commands.ts +72 -0
- package/src/capability/docs.ts +48 -0
- package/src/capability/index.ts +20 -0
- package/src/capability/loader.ts +431 -0
- package/src/capability/registry.ts +55 -0
- package/src/capability/rules.ts +135 -0
- package/src/capability/skills.ts +58 -0
- package/src/capability/sources.ts +998 -0
- package/src/capability/subagents.ts +105 -0
- package/src/capability/yaml-parser.ts +81 -0
- package/src/config/AGENTS.md +46 -0
- package/src/config/capabilities.ts +54 -0
- package/src/config/env.ts +96 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.ts +207 -0
- package/src/config/parser.ts +55 -0
- package/src/config/profiles.ts +75 -0
- package/src/config/provider.ts +55 -0
- package/src/debug.ts +20 -0
- package/src/index.ts +37 -0
- package/src/mcp-json/index.ts +1 -0
- package/src/mcp-json/manager.ts +106 -0
- package/src/state/active-profile.ts +41 -0
- package/src/state/index.ts +3 -0
- package/src/state/manifest.ts +137 -0
- package/src/state/providers.ts +69 -0
- package/src/sync.ts +288 -0
- package/src/templates/agents.ts +14 -0
- package/src/templates/claude.ts +57 -0
- package/src/test-utils/helpers.ts +289 -0
- package/src/test-utils/index.ts +34 -0
- package/src/test-utils/mocks.ts +101 -0
- package/src/types/capability-export.ts +157 -0
- package/src/types/index.ts +314 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readActiveProfileState, writeActiveProfileState } from "../state/active-profile.js";
|
|
2
|
+
import type { OmniConfig, ProfileConfig } from "../types/index.js";
|
|
3
|
+
import { loadConfig, writeConfig } from "./loader.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gets the name of the currently active profile.
|
|
7
|
+
* Reads from state file first, falls back to config.toml for backwards compatibility.
|
|
8
|
+
* Returns null if no profile is set.
|
|
9
|
+
*/
|
|
10
|
+
export async function getActiveProfile(): Promise<string | null> {
|
|
11
|
+
// First check state file (new location)
|
|
12
|
+
const stateProfile = await readActiveProfileState();
|
|
13
|
+
if (stateProfile) {
|
|
14
|
+
return stateProfile;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Fall back to config.toml for backwards compatibility
|
|
18
|
+
const config = await loadConfig();
|
|
19
|
+
return config.active_profile ?? null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sets the active profile by writing to state file.
|
|
24
|
+
* @param name - The name of the profile to activate
|
|
25
|
+
*/
|
|
26
|
+
export async function setActiveProfile(name: string): Promise<void> {
|
|
27
|
+
await writeActiveProfileState(name);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolves the enabled capabilities for a given profile
|
|
32
|
+
*
|
|
33
|
+
* @param config - The merged OmniConfig
|
|
34
|
+
* @param profileName - The name of the profile to apply, or null to use active
|
|
35
|
+
* @returns Array of capability IDs that should be enabled
|
|
36
|
+
*/
|
|
37
|
+
export function resolveEnabledCapabilities(
|
|
38
|
+
config: OmniConfig,
|
|
39
|
+
profileName: string | null,
|
|
40
|
+
): string[] {
|
|
41
|
+
// Determine which profile to use
|
|
42
|
+
const profile = profileName
|
|
43
|
+
? config.profiles?.[profileName]
|
|
44
|
+
: config.profiles?.[config.active_profile ?? "default"];
|
|
45
|
+
|
|
46
|
+
const profileCapabilities = profile?.capabilities ?? [];
|
|
47
|
+
const alwaysEnabled = config.always_enabled_capabilities ?? [];
|
|
48
|
+
|
|
49
|
+
// Merge always-enabled capabilities with profile capabilities (no duplicates)
|
|
50
|
+
return [...new Set([...alwaysEnabled, ...profileCapabilities])];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Load a specific profile configuration from config.toml
|
|
55
|
+
* @param profileName - Name of the profile to load
|
|
56
|
+
* @returns ProfileConfig if found, undefined otherwise
|
|
57
|
+
*/
|
|
58
|
+
export async function loadProfileConfig(profileName: string): Promise<ProfileConfig | undefined> {
|
|
59
|
+
const config = await loadConfig();
|
|
60
|
+
return config.profiles?.[profileName];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Set a profile configuration in config.toml
|
|
65
|
+
* @param profileName - Name of the profile to set
|
|
66
|
+
* @param profileConfig - Profile configuration
|
|
67
|
+
*/
|
|
68
|
+
export async function setProfile(profileName: string, profileConfig: ProfileConfig): Promise<void> {
|
|
69
|
+
const config = await loadConfig();
|
|
70
|
+
if (!config.profiles) {
|
|
71
|
+
config.profiles = {};
|
|
72
|
+
}
|
|
73
|
+
config.profiles[profileName] = profileConfig;
|
|
74
|
+
await writeConfig(config);
|
|
75
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { parse } from "smol-toml";
|
|
3
|
+
import type { Provider, ProviderConfig } from "../types/index.js";
|
|
4
|
+
|
|
5
|
+
const PROVIDER_CONFIG_PATH = ".omni/provider.toml";
|
|
6
|
+
|
|
7
|
+
export async function loadProviderConfig(): Promise<ProviderConfig> {
|
|
8
|
+
if (!existsSync(PROVIDER_CONFIG_PATH)) {
|
|
9
|
+
return { provider: "claude" };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const content = await Bun.file(PROVIDER_CONFIG_PATH).text();
|
|
13
|
+
const parsed = parse(content) as unknown as ProviderConfig;
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function writeProviderConfig(config: ProviderConfig): Promise<void> {
|
|
18
|
+
const lines: string[] = [];
|
|
19
|
+
|
|
20
|
+
lines.push("# OmniDev Provider Configuration");
|
|
21
|
+
lines.push("# Selected AI provider(s) for this project");
|
|
22
|
+
lines.push("#");
|
|
23
|
+
lines.push("# This file controls which AI provider(s) you're using:");
|
|
24
|
+
lines.push("# - claude: Generates .claude/claude.md instruction file");
|
|
25
|
+
lines.push("# - codex: Generates AGENTS.md instruction file");
|
|
26
|
+
lines.push("# - both: Generates both instruction files");
|
|
27
|
+
lines.push("");
|
|
28
|
+
|
|
29
|
+
if (config.providers && config.providers.length > 1) {
|
|
30
|
+
lines.push("# Multiple providers enabled");
|
|
31
|
+
lines.push(`providers = [${config.providers.map((p) => `"${p}"`).join(", ")}]`);
|
|
32
|
+
} else if (config.providers && config.providers.length === 1) {
|
|
33
|
+
lines.push("# Single provider");
|
|
34
|
+
lines.push(`provider = "${config.providers[0]}"`);
|
|
35
|
+
} else if (config.provider) {
|
|
36
|
+
lines.push("# Single provider");
|
|
37
|
+
lines.push(`provider = "${config.provider}"`);
|
|
38
|
+
} else {
|
|
39
|
+
lines.push("# Default: Claude");
|
|
40
|
+
lines.push('provider = "claude"');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await Bun.write(PROVIDER_CONFIG_PATH, `${lines.join("\n")}\n`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function parseProviderFlag(flag: string): Provider[] {
|
|
47
|
+
const lower = flag.toLowerCase();
|
|
48
|
+
if (lower === "both") {
|
|
49
|
+
return ["claude", "codex"];
|
|
50
|
+
}
|
|
51
|
+
if (lower === "claude" || lower === "codex") {
|
|
52
|
+
return [lower as Provider];
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Invalid provider: ${flag}. Must be 'claude', 'codex', or 'both'.`);
|
|
55
|
+
}
|
package/src/debug.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug logger that writes to stdout when OMNIDEV_DEBUG=1
|
|
3
|
+
*/
|
|
4
|
+
export function debug(message: string, data?: unknown): void {
|
|
5
|
+
if (process.env["OMNIDEV_DEBUG"] !== "1") {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const timestamp = new Date().toISOString();
|
|
10
|
+
let logLine: string;
|
|
11
|
+
|
|
12
|
+
if (data !== undefined) {
|
|
13
|
+
logLine = `[${timestamp}] [omnidev] ${message} ${JSON.stringify(data, null, 2)}`;
|
|
14
|
+
} else {
|
|
15
|
+
logLine = `[${timestamp}] [omnidev] ${message}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Write to stdout
|
|
19
|
+
console.log(logLine);
|
|
20
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @omnidev-ai/core - Core functionality for OmniDev
|
|
3
|
+
*
|
|
4
|
+
* This package contains shared types, utilities, and core logic
|
|
5
|
+
* used across the CLI and capability tooling packages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Re-export @stricli/core for capabilities to use
|
|
9
|
+
// This ensures all capabilities use the same @stricli/core instance as the CLI
|
|
10
|
+
export { buildCommand, buildRouteMap } from "@stricli/core";
|
|
11
|
+
|
|
12
|
+
export const version = "0.1.0";
|
|
13
|
+
|
|
14
|
+
export function getVersion(): string {
|
|
15
|
+
return version;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Export capability system
|
|
19
|
+
export * from "./capability";
|
|
20
|
+
|
|
21
|
+
// Export config functionality
|
|
22
|
+
export * from "./config";
|
|
23
|
+
// Export MCP JSON management
|
|
24
|
+
export * from "./mcp-json";
|
|
25
|
+
// Export state management
|
|
26
|
+
export * from "./state";
|
|
27
|
+
// Export sync functionality
|
|
28
|
+
export * from "./sync";
|
|
29
|
+
|
|
30
|
+
// Export templates
|
|
31
|
+
export * from "./templates/agents";
|
|
32
|
+
export * from "./templates/claude";
|
|
33
|
+
// Export core types
|
|
34
|
+
export * from "./types";
|
|
35
|
+
|
|
36
|
+
// Export debug utilities
|
|
37
|
+
export * from "./debug";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./manager";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import type { LoadedCapability, McpConfig } from "../types";
|
|
3
|
+
import type { ResourceManifest } from "../state/manifest";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* MCP server configuration in .mcp.json
|
|
7
|
+
*/
|
|
8
|
+
export interface McpServerConfig {
|
|
9
|
+
command: string;
|
|
10
|
+
args?: string[];
|
|
11
|
+
env?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Structure of .mcp.json file
|
|
16
|
+
*/
|
|
17
|
+
export interface McpJsonConfig {
|
|
18
|
+
mcpServers: Record<string, McpServerConfig>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const MCP_JSON_PATH = ".mcp.json";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read .mcp.json or return empty config if doesn't exist
|
|
25
|
+
*/
|
|
26
|
+
export async function readMcpJson(): Promise<McpJsonConfig> {
|
|
27
|
+
if (!existsSync(MCP_JSON_PATH)) {
|
|
28
|
+
return { mcpServers: {} };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const content = await Bun.file(MCP_JSON_PATH).text();
|
|
33
|
+
const parsed = JSON.parse(content);
|
|
34
|
+
return {
|
|
35
|
+
mcpServers: parsed.mcpServers || {},
|
|
36
|
+
};
|
|
37
|
+
} catch {
|
|
38
|
+
// If file is invalid JSON, return empty config
|
|
39
|
+
return { mcpServers: {} };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Write .mcp.json, preserving non-OmniDev entries
|
|
45
|
+
*/
|
|
46
|
+
export async function writeMcpJson(config: McpJsonConfig): Promise<void> {
|
|
47
|
+
await Bun.write(MCP_JSON_PATH, JSON.stringify(config, null, 2));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build MCP server config from capability's mcp section
|
|
52
|
+
*/
|
|
53
|
+
function buildMcpServerConfig(mcp: McpConfig): McpServerConfig {
|
|
54
|
+
const config: McpServerConfig = {
|
|
55
|
+
command: mcp.command,
|
|
56
|
+
};
|
|
57
|
+
if (mcp.args) {
|
|
58
|
+
config.args = mcp.args;
|
|
59
|
+
}
|
|
60
|
+
if (mcp.env) {
|
|
61
|
+
config.env = mcp.env;
|
|
62
|
+
}
|
|
63
|
+
return config;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Sync .mcp.json with enabled capability MCP servers
|
|
68
|
+
*
|
|
69
|
+
* Each capability with an [mcp] section is registered using its capability ID.
|
|
70
|
+
* Uses the previous manifest to track which MCPs were managed by OmniDev.
|
|
71
|
+
*/
|
|
72
|
+
export async function syncMcpJson(
|
|
73
|
+
capabilities: LoadedCapability[],
|
|
74
|
+
previousManifest: ResourceManifest,
|
|
75
|
+
options: { silent?: boolean } = {},
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
const mcpJson = await readMcpJson();
|
|
78
|
+
|
|
79
|
+
// Collect all MCP server names from previous manifest
|
|
80
|
+
const previouslyManagedMcps = new Set<string>();
|
|
81
|
+
for (const resources of Object.values(previousManifest.capabilities)) {
|
|
82
|
+
for (const mcpName of resources.mcps) {
|
|
83
|
+
previouslyManagedMcps.add(mcpName);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Remove previously managed MCPs
|
|
88
|
+
for (const serverName of previouslyManagedMcps) {
|
|
89
|
+
delete mcpJson.mcpServers[serverName];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Add MCPs from all enabled capabilities
|
|
93
|
+
let addedCount = 0;
|
|
94
|
+
for (const cap of capabilities) {
|
|
95
|
+
if (cap.config.mcp) {
|
|
96
|
+
mcpJson.mcpServers[cap.id] = buildMcpServerConfig(cap.config.mcp);
|
|
97
|
+
addedCount++;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await writeMcpJson(mcpJson);
|
|
102
|
+
|
|
103
|
+
if (!options.silent) {
|
|
104
|
+
console.log(` - .mcp.json (${addedCount} MCP server(s))`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
const STATE_DIR = ".omni/state";
|
|
4
|
+
const ACTIVE_PROFILE_PATH = `${STATE_DIR}/active-profile`;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Read the active profile from state file.
|
|
8
|
+
* Returns null if no active profile is set in state.
|
|
9
|
+
*/
|
|
10
|
+
export async function readActiveProfileState(): Promise<string | null> {
|
|
11
|
+
if (!existsSync(ACTIVE_PROFILE_PATH)) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const content = await Bun.file(ACTIVE_PROFILE_PATH).text();
|
|
17
|
+
const trimmed = content.trim();
|
|
18
|
+
return trimmed || null;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Write the active profile to state file.
|
|
26
|
+
* @param profileName - The name of the profile to set as active
|
|
27
|
+
*/
|
|
28
|
+
export async function writeActiveProfileState(profileName: string): Promise<void> {
|
|
29
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
30
|
+
await Bun.write(ACTIVE_PROFILE_PATH, profileName);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Clear the active profile state (delete the state file).
|
|
35
|
+
*/
|
|
36
|
+
export async function clearActiveProfileState(): Promise<void> {
|
|
37
|
+
if (existsSync(ACTIVE_PROFILE_PATH)) {
|
|
38
|
+
const file = Bun.file(ACTIVE_PROFILE_PATH);
|
|
39
|
+
await file.delete();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
2
|
+
import type { LoadedCapability } from "../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resources provided by a single capability
|
|
6
|
+
*/
|
|
7
|
+
export interface CapabilityResources {
|
|
8
|
+
skills: string[];
|
|
9
|
+
rules: string[];
|
|
10
|
+
commands: string[];
|
|
11
|
+
subagents: string[];
|
|
12
|
+
mcps: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Manifest tracking which resources each capability provides.
|
|
17
|
+
* Used to clean up stale resources when capabilities are disabled.
|
|
18
|
+
*/
|
|
19
|
+
export interface ResourceManifest {
|
|
20
|
+
/** Schema version for future migrations */
|
|
21
|
+
version: 1;
|
|
22
|
+
/** Last sync timestamp (ISO 8601) */
|
|
23
|
+
syncedAt: string;
|
|
24
|
+
/** Map of capability ID → resources it provides */
|
|
25
|
+
capabilities: Record<string, CapabilityResources>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Result of cleaning up stale resources
|
|
30
|
+
*/
|
|
31
|
+
export interface CleanupResult {
|
|
32
|
+
deletedSkills: string[];
|
|
33
|
+
deletedRules: string[];
|
|
34
|
+
deletedCommands: string[];
|
|
35
|
+
deletedSubagents: string[];
|
|
36
|
+
deletedMcps: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const MANIFEST_PATH = ".omni/state/manifest.json";
|
|
40
|
+
const CURRENT_VERSION = 1;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Load the previous manifest from disk.
|
|
44
|
+
* Returns an empty manifest if the file doesn't exist.
|
|
45
|
+
*/
|
|
46
|
+
export async function loadManifest(): Promise<ResourceManifest> {
|
|
47
|
+
if (!existsSync(MANIFEST_PATH)) {
|
|
48
|
+
return {
|
|
49
|
+
version: CURRENT_VERSION,
|
|
50
|
+
syncedAt: new Date().toISOString(),
|
|
51
|
+
capabilities: {},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const content = await Bun.file(MANIFEST_PATH).text();
|
|
56
|
+
return JSON.parse(content) as ResourceManifest;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Save the manifest to disk.
|
|
61
|
+
*/
|
|
62
|
+
export async function saveManifest(manifest: ResourceManifest): Promise<void> {
|
|
63
|
+
mkdirSync(".omni/state", { recursive: true });
|
|
64
|
+
await Bun.write(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Build a manifest from the current registry capabilities.
|
|
69
|
+
*/
|
|
70
|
+
export function buildManifestFromCapabilities(capabilities: LoadedCapability[]): ResourceManifest {
|
|
71
|
+
const manifest: ResourceManifest = {
|
|
72
|
+
version: CURRENT_VERSION,
|
|
73
|
+
syncedAt: new Date().toISOString(),
|
|
74
|
+
capabilities: {},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
for (const cap of capabilities) {
|
|
78
|
+
const resources: CapabilityResources = {
|
|
79
|
+
skills: cap.skills.map((s) => s.name),
|
|
80
|
+
rules: cap.rules.map((r) => r.name),
|
|
81
|
+
commands: cap.commands.map((c) => c.name),
|
|
82
|
+
subagents: cap.subagents.map((s) => s.name),
|
|
83
|
+
mcps: cap.config.mcp ? [cap.id] : [],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
manifest.capabilities[cap.id] = resources;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return manifest;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Delete resources for capabilities that are no longer enabled.
|
|
94
|
+
* Compares the previous manifest against current capability IDs
|
|
95
|
+
* and removes files/directories for capabilities not in the current set.
|
|
96
|
+
*/
|
|
97
|
+
export async function cleanupStaleResources(
|
|
98
|
+
previousManifest: ResourceManifest,
|
|
99
|
+
currentCapabilityIds: Set<string>,
|
|
100
|
+
): Promise<CleanupResult> {
|
|
101
|
+
const result: CleanupResult = {
|
|
102
|
+
deletedSkills: [],
|
|
103
|
+
deletedRules: [],
|
|
104
|
+
deletedCommands: [],
|
|
105
|
+
deletedSubagents: [],
|
|
106
|
+
deletedMcps: [],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
for (const [capId, resources] of Object.entries(previousManifest.capabilities)) {
|
|
110
|
+
// Skip if capability is still enabled
|
|
111
|
+
if (currentCapabilityIds.has(capId)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Delete skills (directories)
|
|
116
|
+
for (const skillName of resources.skills) {
|
|
117
|
+
const skillDir = `.claude/skills/${skillName}`;
|
|
118
|
+
if (existsSync(skillDir)) {
|
|
119
|
+
rmSync(skillDir, { recursive: true });
|
|
120
|
+
result.deletedSkills.push(skillName);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Delete rules (individual files)
|
|
125
|
+
for (const ruleName of resources.rules) {
|
|
126
|
+
const rulePath = `.cursor/rules/omnidev-${ruleName}.mdc`;
|
|
127
|
+
if (existsSync(rulePath)) {
|
|
128
|
+
rmSync(rulePath);
|
|
129
|
+
result.deletedRules.push(ruleName);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Future: Delete commands and subagents if they become file-based
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import type { ProviderId } from "../types/index.js";
|
|
3
|
+
|
|
4
|
+
const STATE_DIR = ".omni/state";
|
|
5
|
+
const PROVIDERS_PATH = `${STATE_DIR}/providers.json`;
|
|
6
|
+
|
|
7
|
+
export interface ProvidersState {
|
|
8
|
+
enabled: ProviderId[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const DEFAULT_PROVIDERS: ProviderId[] = ["claude-code"];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Read the enabled providers from local state.
|
|
15
|
+
* Returns default providers if no state file exists.
|
|
16
|
+
*/
|
|
17
|
+
export async function readEnabledProviders(): Promise<ProviderId[]> {
|
|
18
|
+
if (!existsSync(PROVIDERS_PATH)) {
|
|
19
|
+
return DEFAULT_PROVIDERS;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const content = await Bun.file(PROVIDERS_PATH).text();
|
|
24
|
+
const state = JSON.parse(content) as ProvidersState;
|
|
25
|
+
return state.enabled.length > 0 ? state.enabled : DEFAULT_PROVIDERS;
|
|
26
|
+
} catch {
|
|
27
|
+
return DEFAULT_PROVIDERS;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Write enabled providers to local state.
|
|
33
|
+
* @param providers - List of provider IDs to enable
|
|
34
|
+
*/
|
|
35
|
+
export async function writeEnabledProviders(providers: ProviderId[]): Promise<void> {
|
|
36
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
37
|
+
const state: ProvidersState = { enabled: providers };
|
|
38
|
+
await Bun.write(PROVIDERS_PATH, JSON.stringify(state, null, 2));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Enable a specific provider.
|
|
43
|
+
* @param providerId - The provider to enable
|
|
44
|
+
*/
|
|
45
|
+
export async function enableProvider(providerId: ProviderId): Promise<void> {
|
|
46
|
+
const current = await readEnabledProviders();
|
|
47
|
+
if (!current.includes(providerId)) {
|
|
48
|
+
await writeEnabledProviders([...current, providerId]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Disable a specific provider.
|
|
54
|
+
* @param providerId - The provider to disable
|
|
55
|
+
*/
|
|
56
|
+
export async function disableProvider(providerId: ProviderId): Promise<void> {
|
|
57
|
+
const current = await readEnabledProviders();
|
|
58
|
+
const filtered = current.filter((p) => p !== providerId);
|
|
59
|
+
await writeEnabledProviders(filtered);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if a provider is enabled.
|
|
64
|
+
* @param providerId - The provider to check
|
|
65
|
+
*/
|
|
66
|
+
export async function isProviderEnabled(providerId: ProviderId): Promise<boolean> {
|
|
67
|
+
const current = await readEnabledProviders();
|
|
68
|
+
return current.includes(providerId);
|
|
69
|
+
}
|