# @cc-switch/sdk

Node SDK for MCP and Skill management, mirroring CC Switch's unified storage + per-app sync architecture.

## Features

- **MCP Servers**: CRUD + app enable/disable + import from apps (Claude, Claudecode, OpenCode)
- **Skills**: Install/uninstall + directory sync (copy/symlink) + import from apps
- **Config Backup/Restore**: Auto backup before first Opencode/Claudecode sync + restore methods
- **Extensible**: Placeholder adapters for Codex/Gemini/OpenClaw (easy to implement)

## Installation

```bash
npm install @cc-switch/sdk
```

## Quick Start

```typescript
import { CcSwitchSdk } from "@cc-switch/sdk";

const sdk = new CcSwitchSdk();
await sdk.load();

// Create MCP server
const server = await sdk.createMcpServer({
  name: "my-mcp",
  server: { type: "stdio", command: "node", args: ["server.js"] },
  apps: { claude: true },
});

// Toggle app
await sdk.toggleMcpApp(server.id, "opencode", true);

// Import from apps
await sdk.importMcpFromApps();

// Install skill
await sdk.installSkill({
  sourceDir: "/path/to/skill",
  name: "my-skill",
  apps: { claude: true, opencode: true },
});
```

## Configuration

```typescript
const sdk = new CcSwitchSdk({
  rootDir: "/custom/path",        // Base directory
  skillSyncMethod: "symlink",     // "auto" | "copy" | "symlink"
  paths: {
    stateFile: "...",
    backupDir: "...",               // backup directory
    claudeConfigFile: "...",
    claudeSkillsDir: "...",
    claudecodeConfigFile: "...",    // claudecode mcp config
    claudecodeSkillsDir: "...",
    opencodeConfigFile: "...",
    opencodeSkillsDir: "...",
    ssotSkillsDir: "...",
  },
  // Custom adapters
  adapters: {
    mcp: { codex: new MyCodexAdapter() },
    skill: { codex: new MyCodexSkillAdapter() },
  },
});
```

---

## API Reference

### Class: `CcSwitchSdk`

Main SDK class for managing MCP servers and Skills.

#### Constructor

```typescript
constructor(options?: SdkOptions)
```

**Parameters:**
- `options.rootDir` (string, optional): Base directory for all SDK operations
- `options.skillSyncMethod` ("auto" | "copy" | "symlink", optional): Method for syncing skill directories
- `options.paths` (Partial\<SdkPaths\>, optional): Custom paths for config files
- `options.adapters` (AdapterOverrides, optional): Custom adapters for MCP and Skill management

---

### MCP Server Management

#### `load(): Promise<void>`

Load SDK state from disk. **Must be called before any operations.**

```typescript
await sdk.load();
```

---

#### `save(): Promise<void>`

Manually save SDK state to disk.

```typescript
await sdk.save();
```

---

#### `createMcpServer(input: CreateMcpInput): Promise<McpServer>`

Create a new MCP server and sync to enabled apps.

**Parameters:**
- `input.name` (string): Unique server name
- `input.server` (McpServerSpec): Server configuration
- `input.apps` (Partial\<AppFlags\>, optional): Target apps
- `input.description` (string, optional): Server description

**Returns:** `McpServer` - Created server object

```typescript
const server = await sdk.createMcpServer({
  name: "aws-mcp",
  server: {
    type: "stdio",
    command: "node",
    args: ["index.js"],
    env: { API_KEY: "secret" }
  },
  apps: { claude: true, opencode: true },
  description: "AWS MCP Server"
});
```

---

#### `getAllMcpServers(): McpServer[]`

Get all MCP servers.

```typescript
const servers = sdk.getAllMcpServers();
// Returns: McpServer[]
```

---

#### `getMcpServerById(id: string): McpServer | undefined`

Get MCP server by ID.

```typescript
const server = sdk.getMcpServerById("mcp_abc123");
```

---

#### `updateMcpServer(id: string, patch: UpdateMcpInput): Promise<McpServer>`

Update MCP server configuration.

**Parameters:**
- `id`: Server ID
- `patch`: Partial server update

```typescript
const updated = await sdk.updateMcpServer(server.id, {
  description: "Updated description",
  apps: { claudecode: true }
});
```

---

#### `deleteMcpServer(id: string): Promise<void>`

Delete MCP server and remove from all apps.

```typescript
await sdk.deleteMcpServer(server.id);
```

---

#### `toggleMcpApp(serverId: string, appId: AppId, enabled: boolean): Promise<McpServer>`

Enable or disable MCP server for a specific app.

```typescript
await sdk.toggleMcpApp(server.id, "opencode", true);
```

---

#### `importMcpFromApps(): Promise<number>`

Import MCP servers from all apps into SDK state.

**Returns:** Number of imported servers

```typescript
const count = await sdk.importMcpFromApps();
console.log(`Imported ${count} servers`);
```

---

### Skill Management

#### `installSkill(input: CreateSkillInput): Promise<InstalledSkill>`

Install a skill and sync to enabled apps.

**Parameters:**
- `input.sourceDir` (string): Source directory containing skill files
- `input.name` (string): Skill name
- `input.apps` (Partial\<AppFlags\>, optional): Target apps
- `input.description` (string, optional): Skill description

```typescript
const skill = await sdk.installSkill({
  sourceDir: "/path/to/skill/source",
  name: "frontend-design",
  apps: { claude: true, opencode: true },
  description: "Frontend design skill"
});
```

---

#### `getAllSkills(): InstalledSkill[]`

Get all installed skills.

```typescript
const skills = sdk.getAllSkills();
```

---

#### `getSkillById(id: string): InstalledSkill | undefined`

Get skill by ID.

```typescript
const skill = sdk.getSkillById("skill_abc123");
```

---

#### `updateSkill(id: string, patch: UpdateSkillInput): Promise<InstalledSkill>`

Update skill configuration.

```typescript
const updated = await sdk.updateSkill(skill.id, {
  description: "Updated description"
});
```

---

#### `uninstallSkill(id: string): Promise<void>`

Uninstall skill and remove from all apps.

```typescript
await sdk.uninstallSkill(skill.id);
```

---

#### `toggleSkillApp(skillId: string, appId: AppId, enabled: boolean): Promise<InstalledSkill>`

Enable or disable skill for a specific app.

```typescript
await sdk.toggleSkillApp(skill.id, "claude", false);
```

---

#### `importSkillsFromApps(): Promise<number>`

Import skills from all apps into SDK state.

```typescript
const count = await sdk.importSkillsFromApps();
```

---

### Backup & Restore

#### `backupAiConfigs(appIds?: AppId[]): Promise<void>`

Backup AI config files before modification.

```typescript
await sdk.backupAiConfigs(["opencode", "claudecode"]);
```

---

#### `restoreAiConfig(appId: AppId): Promise<boolean>`

Restore AI config from backup.

**Returns:** `true` if restored successfully

```typescript
const restored = await sdk.restoreAiConfig("opencode");
if (restored) {
  console.log("Config restored");
}
```

---

#### `restoreAiConfigs(appIds?: AppId[]): Promise<Record<string, boolean>>`

Restore multiple AI configs.

```typescript
const results = await sdk.restoreAiConfigs(["opencode", "claudecode"]);
// Returns: { opencode: true, claudecode: false }
```

---

### Utility Methods

#### `getPaths(): SdkPaths`

Get current SDK paths configuration.

```typescript
const paths = sdk.getPaths();
console.log(paths.stateFile);
```

---

## Types

### `AppId`

```typescript
type AppId = "claude" | "claudecode" | "codex" | "gemini" | "opencode" | "openclaw";
```

### `McpServerSpec`

```typescript
interface McpServerSpec {
  type?: "stdio" | "sse" | "http";
  command?: string;
  args?: string[];
  env?: Record<string, string>;
  cwd?: string;
  url?: string;
  headers?: Record<string, string>;
}
```

### `McpServer`

```typescript
interface McpServer {
  id: string;
  name: string;
  server: McpServerSpec;
  description?: string;
  apps: AppFlags;
}
```

### `InstalledSkill`

```typescript
interface InstalledSkill {
  id: string;
  name: string;
  directory: string;
  description?: string;
  apps: AppFlags;
  installedAt: number;
}
```

### `AppFlags`

```typescript
interface AppFlags {
  claude: boolean;
  claudecode: boolean;
  codex: boolean;
  gemini: boolean;
  opencode: boolean;
  openclaw: boolean;
}
```

---

## Extending

### Custom MCP Adapter

Implement `IMcpAdapter` to add support for new apps:

```typescript
import type { IMcpAdapter, McpServer } from "@cc-switch/sdk";

class MyCodexAdapter implements IMcpAdapter {
  readonly appId = "codex" as const;

  async importServers(): Promise<McpServer[]> {
    // Read existing MCP servers from Codex config
    // Return array of McpServer objects
  }

  async syncServer(server: McpServer): Promise<void> {
    // Write MCP server to Codex config
  }

  async removeServer(server: McpServer): Promise<void> {
    // Remove MCP server from Codex config
  }
}
```

### Custom Skill Adapter

Implement `ISkillAdapter` for skill management:

```typescript
import type { ISkillAdapter, InstalledSkill, SkillSyncMethod } from "@cc-switch/sdk";

class MyCodexSkillAdapter implements ISkillAdapter {
  readonly appId = "codex" as const;

  async importSkills(): Promise<Array<Pick<InstalledSkill, "id" | "name" | "directory" | "description">>> {
    // Scan skill directories
    // Return array of skill info
  }

  async syncSkill(skill: InstalledSkill, sourceDir: string, method: SkillSyncMethod): Promise<void> {
    // Copy or symlink skill directory
  }

  async removeSkill(skill: InstalledSkill): Promise<void> {
    // Remove skill directory
  }
}
```

### Using Custom Adapters

```typescript
const sdk = new CcSwitchSdk({
  adapters: {
    mcp: { codex: new MyCodexAdapter() },
    skill: { codex: new MyCodexSkillAdapter() }
  }
});
```

---

## Error Handling

SDK throws `SdkError` for known errors:

```typescript
import { SdkError } from "@cc-switch/sdk";

try {
  await sdk.updateMcpServer("invalid-id", {});
} catch (err) {
  if (err instanceof SdkError) {
    console.log(err.code);    // "MCP_NOT_FOUND"
    console.log(err.message); // "MCP server not found: invalid-id"
    console.log(err.details); // { id: "invalid-id" }
  }
}
```

**Error Codes:**
- `MCP_NOT_FOUND`: MCP server not found
- `SKILL_NOT_FOUND`: Skill not found

---

## Scripts

```bash
pnpm typecheck   # TypeScript check
pnpm build       # Compile
pnpm test        # Run tests
```

---

## Architecture

```
┌─────────────────────────────────────┐
│         CcSwitchSdk (Core)          │
│  - State Management                 │
│  - CRUD Operations                  │
│  - Import/Export                    │
└──────────────┬──────────────────────┘
               │
    ┌──────────┴──────────┐
    │                     │
┌───▼────┐          ┌────▼───┐
│ MCP    │          │ Skill  │
│ Service│          │ Service│
└───┬────┘          └────┬───┘
    │                    │
┌───▼────────────────────▼───┐
│      Adapters Layer        │
│  - Claude / Claudecode     │
│  - OpenCode                │
│  - Codex / Gemini / ...    │
└────────────────────────────┘
```

---

## License

MIT