@anaclumos/taal 1.1.2
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/.claude/settings.json +15 -0
- package/.github/workflows/ci.yml +28 -0
- package/.github/workflows/publish.yml +42 -0
- package/AGENTS.md +123 -0
- package/README.md +568 -0
- package/biome.jsonc +24 -0
- package/lefthook.yml +12 -0
- package/package.json +52 -0
- package/src/commands/collect.ts +172 -0
- package/src/commands/diff.ts +127 -0
- package/src/commands/init.ts +66 -0
- package/src/commands/list.ts +80 -0
- package/src/commands/providers.ts +46 -0
- package/src/commands/sync.ts +111 -0
- package/src/commands/validate.ts +17 -0
- package/src/config/env.ts +49 -0
- package/src/config/loader.ts +88 -0
- package/src/config/parser.ts +44 -0
- package/src/config/schema.ts +67 -0
- package/src/errors/index.ts +43 -0
- package/src/index.ts +301 -0
- package/src/providers/antigravity.ts +24 -0
- package/src/providers/base.ts +70 -0
- package/src/providers/claude-code.ts +12 -0
- package/src/providers/claude-desktop.ts +19 -0
- package/src/providers/codex.ts +61 -0
- package/src/providers/continue.ts +62 -0
- package/src/providers/cursor.ts +25 -0
- package/src/providers/index.ts +34 -0
- package/src/providers/opencode.ts +42 -0
- package/src/providers/registry.ts +74 -0
- package/src/providers/types.ts +99 -0
- package/src/providers/utils.ts +106 -0
- package/src/providers/windsurf.ts +50 -0
- package/src/providers/zed.ts +35 -0
- package/src/scripts/generate-schema.ts +17 -0
- package/src/skills/copy.ts +58 -0
- package/src/skills/discovery.ts +87 -0
- package/src/skills/validator.ts +95 -0
- package/src/utils/atomic-write.ts +35 -0
- package/src/utils/backup.ts +27 -0
- package/taal.schema.json +91 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { JsonMap } from "@iarna/toml";
|
|
4
|
+
import { stringify as stringifyToml } from "@iarna/toml";
|
|
5
|
+
import type { McpServer } from "../config/schema.js";
|
|
6
|
+
import { atomicWrite } from "../utils/atomic-write.js";
|
|
7
|
+
import { backupConfig } from "../utils/backup.js";
|
|
8
|
+
import { BaseProvider } from "./base.js";
|
|
9
|
+
import { resolveConfigPath } from "./utils.js";
|
|
10
|
+
|
|
11
|
+
export class CodexProvider extends BaseProvider {
|
|
12
|
+
name = "codex";
|
|
13
|
+
configPath = (home: string) => join(home, ".codex", "config.toml");
|
|
14
|
+
format = "toml" as const;
|
|
15
|
+
mcpKey = "mcp_servers";
|
|
16
|
+
skillsPath = (home: string) => join(home, ".codex", "skills");
|
|
17
|
+
|
|
18
|
+
override async writeConfig(config: unknown, home?: string): Promise<void> {
|
|
19
|
+
const homeDir = home || homedir();
|
|
20
|
+
const path = resolveConfigPath(this.configPath, homeDir);
|
|
21
|
+
|
|
22
|
+
backupConfig(path);
|
|
23
|
+
|
|
24
|
+
const tomlContent = stringifyToml(config as JsonMap);
|
|
25
|
+
atomicWrite(path, tomlContent);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
transformMcpServers(
|
|
29
|
+
servers: Record<string, McpServer>
|
|
30
|
+
): Record<string, unknown> {
|
|
31
|
+
const transformed: Record<string, unknown> = {};
|
|
32
|
+
|
|
33
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
34
|
+
if (server.url) {
|
|
35
|
+
transformed[name] = {
|
|
36
|
+
url: server.url,
|
|
37
|
+
...(server.headers && { http_headers: server.headers }),
|
|
38
|
+
enabled: true,
|
|
39
|
+
startup_timeout_sec: 30,
|
|
40
|
+
...(server.overrides?.codex?.enabled_tools && {
|
|
41
|
+
enabled_tools: server.overrides.codex.enabled_tools,
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
} else if (server.command) {
|
|
45
|
+
transformed[name] = {
|
|
46
|
+
command: server.command,
|
|
47
|
+
...(server.args && { args: server.args }),
|
|
48
|
+
...(server.env && { env: server.env }),
|
|
49
|
+
enabled: true,
|
|
50
|
+
...(server.overrides?.codex?.enabled_tools && {
|
|
51
|
+
enabled_tools: server.overrides.codex.enabled_tools,
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
} else {
|
|
55
|
+
console.warn(`Skipping server "${name}" - missing command or url`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return transformed;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
4
|
+
import type { McpServer } from "../config/schema.js";
|
|
5
|
+
import { atomicWrite } from "../utils/atomic-write.js";
|
|
6
|
+
import { backupConfig } from "../utils/backup.js";
|
|
7
|
+
import { BaseProvider } from "./base.js";
|
|
8
|
+
import { resolveConfigPath } from "./utils.js";
|
|
9
|
+
|
|
10
|
+
export class ContinueProvider extends BaseProvider {
|
|
11
|
+
name = "continue";
|
|
12
|
+
configPath = (home: string) => join(home, ".continue", "config.yaml");
|
|
13
|
+
format = "yaml" as const;
|
|
14
|
+
mcpKey = "mcpServers";
|
|
15
|
+
|
|
16
|
+
override async writeConfig(config: unknown, home?: string): Promise<void> {
|
|
17
|
+
const homeDir = home || homedir();
|
|
18
|
+
const path = resolveConfigPath(this.configPath, homeDir);
|
|
19
|
+
|
|
20
|
+
backupConfig(path);
|
|
21
|
+
|
|
22
|
+
const yamlContent = stringifyYaml(config);
|
|
23
|
+
atomicWrite(path, yamlContent);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
transformMcpServers(servers: Record<string, McpServer>): unknown[] {
|
|
27
|
+
const transformed: unknown[] = [];
|
|
28
|
+
|
|
29
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
30
|
+
if (server.url) {
|
|
31
|
+
transformed.push({
|
|
32
|
+
name,
|
|
33
|
+
url: server.url,
|
|
34
|
+
...(server.headers && {
|
|
35
|
+
headers: this.transformEnvVars(server.headers),
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
} else if (server.command) {
|
|
39
|
+
transformed.push({
|
|
40
|
+
name,
|
|
41
|
+
command: server.command,
|
|
42
|
+
...(server.args && { args: server.args }),
|
|
43
|
+
...(server.env && { env: this.transformEnvVars(server.env) }),
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
console.warn(`Skipping server "${name}" - missing command or url`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return transformed;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private transformEnvVars(
|
|
54
|
+
obj: Record<string, string>
|
|
55
|
+
): Record<string, string> {
|
|
56
|
+
const transformed: Record<string, string> = {};
|
|
57
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
58
|
+
transformed[key] = value.replace(/\$\{([^}]+)\}/g, "${{ secrets.$1 }}");
|
|
59
|
+
}
|
|
60
|
+
return transformed;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { platform } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { BaseProvider, createStdioTransformer } from "./base.js";
|
|
4
|
+
|
|
5
|
+
export class CursorProvider extends BaseProvider {
|
|
6
|
+
name = "cursor";
|
|
7
|
+
configPath = (home: string) => {
|
|
8
|
+
const os = platform();
|
|
9
|
+
if (os === "darwin") {
|
|
10
|
+
return join(
|
|
11
|
+
home,
|
|
12
|
+
"Library",
|
|
13
|
+
"Application Support",
|
|
14
|
+
"Cursor",
|
|
15
|
+
"User",
|
|
16
|
+
"settings.json"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return join(home, ".config", "Cursor", "User", "settings.json");
|
|
20
|
+
};
|
|
21
|
+
format = "json" as const;
|
|
22
|
+
mcpKey = "mcpServers";
|
|
23
|
+
|
|
24
|
+
transformMcpServers = createStdioTransformer("Cursor");
|
|
25
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AntigravityProvider } from "./antigravity.js";
|
|
2
|
+
import { ClaudeCodeProvider } from "./claude-code.js";
|
|
3
|
+
import { ClaudeDesktopProvider } from "./claude-desktop.js";
|
|
4
|
+
import { CodexProvider } from "./codex.js";
|
|
5
|
+
import { ContinueProvider } from "./continue.js";
|
|
6
|
+
import { CursorProvider } from "./cursor.js";
|
|
7
|
+
import { OpenCodeProvider } from "./opencode.js";
|
|
8
|
+
import { registry as providerRegistry } from "./registry.js";
|
|
9
|
+
import { WindsurfProvider } from "./windsurf.js";
|
|
10
|
+
import { ZedProvider } from "./zed.js";
|
|
11
|
+
|
|
12
|
+
let initialized = false;
|
|
13
|
+
|
|
14
|
+
export function initializeProviders(): void {
|
|
15
|
+
if (initialized) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
providerRegistry.register(new ClaudeDesktopProvider());
|
|
20
|
+
providerRegistry.register(new ClaudeCodeProvider());
|
|
21
|
+
providerRegistry.register(new CursorProvider());
|
|
22
|
+
providerRegistry.register(new ContinueProvider());
|
|
23
|
+
providerRegistry.register(new ZedProvider());
|
|
24
|
+
providerRegistry.register(new OpenCodeProvider());
|
|
25
|
+
providerRegistry.register(new CodexProvider());
|
|
26
|
+
providerRegistry.register(new WindsurfProvider());
|
|
27
|
+
providerRegistry.register(new AntigravityProvider());
|
|
28
|
+
|
|
29
|
+
initialized = true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// biome-ignore lint/performance/noBarrelFile: Intentional barrel for provider initialization
|
|
33
|
+
export { registry } from "./registry.js";
|
|
34
|
+
export type { ConfigFormat, Provider, ProviderMetadata } from "./types.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import type { McpServer } from "../config/schema.js";
|
|
3
|
+
import { BaseProvider } from "./base.js";
|
|
4
|
+
|
|
5
|
+
export class OpenCodeProvider extends BaseProvider {
|
|
6
|
+
name = "opencode";
|
|
7
|
+
configPath = (home: string) =>
|
|
8
|
+
join(home, ".config", "opencode", "opencode.json");
|
|
9
|
+
format = "json" as const;
|
|
10
|
+
mcpKey = "mcp";
|
|
11
|
+
skillsPath = (home: string) => join(home, ".opencode", "skills");
|
|
12
|
+
|
|
13
|
+
transformMcpServers(
|
|
14
|
+
servers: Record<string, McpServer>
|
|
15
|
+
): Record<string, unknown> {
|
|
16
|
+
const transformed: Record<string, unknown> = {};
|
|
17
|
+
|
|
18
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
19
|
+
if (server.url) {
|
|
20
|
+
transformed[name] = {
|
|
21
|
+
type: "remote",
|
|
22
|
+
url: server.url,
|
|
23
|
+
...(server.headers && { headers: server.headers }),
|
|
24
|
+
enabled: true,
|
|
25
|
+
};
|
|
26
|
+
} else if (server.command) {
|
|
27
|
+
const command = [server.command, ...(server.args || [])];
|
|
28
|
+
|
|
29
|
+
transformed[name] = {
|
|
30
|
+
type: "local",
|
|
31
|
+
command,
|
|
32
|
+
...(server.env && { environment: server.env }),
|
|
33
|
+
enabled: true,
|
|
34
|
+
};
|
|
35
|
+
} else {
|
|
36
|
+
console.warn(`Skipping server "${name}" - missing command or url`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return transformed;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Provider } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Global provider registry
|
|
5
|
+
*/
|
|
6
|
+
class ProviderRegistry {
|
|
7
|
+
private readonly providers: Map<string, Provider> = new Map();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Register a provider
|
|
11
|
+
*/
|
|
12
|
+
register(provider: Provider): void {
|
|
13
|
+
this.providers.set(provider.name, provider);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get a provider by name
|
|
18
|
+
*/
|
|
19
|
+
get(name: string): Provider | undefined {
|
|
20
|
+
return this.providers.get(name);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get all registered providers (sorted by name for deterministic ordering)
|
|
25
|
+
*/
|
|
26
|
+
getAll(): Provider[] {
|
|
27
|
+
return Array.from(this.providers.values()).sort((a, b) =>
|
|
28
|
+
a.name.localeCompare(b.name)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get all provider names (sorted for deterministic ordering)
|
|
34
|
+
*/
|
|
35
|
+
getNames(): string[] {
|
|
36
|
+
return Array.from(this.providers.keys()).sort();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if a provider is registered
|
|
41
|
+
*/
|
|
42
|
+
has(name: string): boolean {
|
|
43
|
+
return this.providers.has(name);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get all installed providers (sorted by name for deterministic ordering)
|
|
48
|
+
*/
|
|
49
|
+
async getInstalled(): Promise<Provider[]> {
|
|
50
|
+
const all = this.getAll();
|
|
51
|
+
const installed: Provider[] = [];
|
|
52
|
+
|
|
53
|
+
for (const provider of all) {
|
|
54
|
+
if (await provider.isInstalled()) {
|
|
55
|
+
installed.push(provider);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return installed.sort((a, b) => a.name.localeCompare(b.name));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Clear all registered providers (mainly for testing)
|
|
64
|
+
*/
|
|
65
|
+
clear(): void {
|
|
66
|
+
this.providers.clear();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Export singleton instance
|
|
71
|
+
export const registry = new ProviderRegistry();
|
|
72
|
+
|
|
73
|
+
// Export class for testing
|
|
74
|
+
export { ProviderRegistry };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { McpServer } from "../config/schema.js";
|
|
2
|
+
|
|
3
|
+
export type ConfigFormat = "json" | "yaml" | "toml";
|
|
4
|
+
|
|
5
|
+
export interface StdioServerConfig {
|
|
6
|
+
command: string;
|
|
7
|
+
args?: string[];
|
|
8
|
+
env?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface HttpServerConfig {
|
|
12
|
+
url: string;
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface OpenCodeServerConfig {
|
|
17
|
+
type: "local" | "remote";
|
|
18
|
+
command?: string[];
|
|
19
|
+
url?: string;
|
|
20
|
+
environment?: Record<string, string>;
|
|
21
|
+
headers?: Record<string, string>;
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CodexServerConfig {
|
|
26
|
+
command?: string;
|
|
27
|
+
args?: string[];
|
|
28
|
+
env?: Record<string, string>;
|
|
29
|
+
url?: string;
|
|
30
|
+
http_headers?: Record<string, string>;
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
startup_timeout_sec?: number;
|
|
33
|
+
enabled_tools?: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface WindsurfHttpServerConfig extends HttpServerConfig {
|
|
37
|
+
transport: "streamable-http";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ProviderConfig {
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Provider interface - defines how to interact with each AI coding assistant
|
|
46
|
+
*/
|
|
47
|
+
export interface Provider {
|
|
48
|
+
/** Provider name (e.g., 'claude-desktop', 'cursor') */
|
|
49
|
+
name: string;
|
|
50
|
+
|
|
51
|
+
/** Path to provider's config file (can be function for dynamic paths) */
|
|
52
|
+
configPath: string | ((home: string) => string);
|
|
53
|
+
|
|
54
|
+
/** Config file format */
|
|
55
|
+
format: ConfigFormat;
|
|
56
|
+
|
|
57
|
+
/** Key in config where MCP servers are stored (e.g., 'mcpServers', 'context_servers') */
|
|
58
|
+
mcpKey: string;
|
|
59
|
+
|
|
60
|
+
/** Optional path to provider's skills directory */
|
|
61
|
+
skillsPath?: string | ((home: string) => string);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if provider is installed on this system
|
|
65
|
+
*/
|
|
66
|
+
isInstalled(home?: string): Promise<boolean>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Read provider's current config
|
|
70
|
+
*/
|
|
71
|
+
readConfig(home?: string): Promise<unknown>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Write config to provider's config file
|
|
75
|
+
* Should use atomic write and backup
|
|
76
|
+
*/
|
|
77
|
+
writeConfig(config: unknown, home?: string): Promise<void>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Transform TAAL MCP servers to provider-specific format
|
|
81
|
+
*/
|
|
82
|
+
transformMcpServers(servers: Record<string, McpServer>): unknown;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Optional: Transform skills to provider-specific format
|
|
86
|
+
*/
|
|
87
|
+
transformSkills?(skills: unknown[]): unknown;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Provider metadata for registration
|
|
92
|
+
*/
|
|
93
|
+
export interface ProviderMetadata {
|
|
94
|
+
name: string;
|
|
95
|
+
configPath: string | ((home: string) => string);
|
|
96
|
+
format: ConfigFormat;
|
|
97
|
+
mcpKey: string;
|
|
98
|
+
skillsPath?: string | ((home: string) => string);
|
|
99
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import type { JsonMap } from "@iarna/toml";
|
|
3
|
+
import { parse as parseToml, stringify as stringifyToml } from "@iarna/toml";
|
|
4
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
5
|
+
import { atomicWrite } from "../utils/atomic-write.js";
|
|
6
|
+
import { backupConfig } from "../utils/backup.js";
|
|
7
|
+
import type { ConfigFormat } from "./types.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Read and parse a JSON config file
|
|
11
|
+
*/
|
|
12
|
+
export function readJsonConfig(path: string): unknown {
|
|
13
|
+
if (!existsSync(path)) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const content = readFileSync(path, "utf-8");
|
|
19
|
+
return JSON.parse(content);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
throw new Error(`Failed to read JSON config at ${path}: ${error}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Read and parse a YAML config file
|
|
27
|
+
*/
|
|
28
|
+
export function readYamlConfig(path: string): unknown {
|
|
29
|
+
if (!existsSync(path)) {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const content = readFileSync(path, "utf-8");
|
|
35
|
+
return parseYaml(content);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new Error(`Failed to read YAML config at ${path}: ${error}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Read and parse a TOML config file
|
|
43
|
+
*/
|
|
44
|
+
export function readTomlConfig(path: string): unknown {
|
|
45
|
+
if (!existsSync(path)) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const content = readFileSync(path, "utf-8");
|
|
51
|
+
return parseToml(content);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(`Failed to read TOML config at ${path}: ${error}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Read config file based on format
|
|
59
|
+
*/
|
|
60
|
+
export function readConfig(path: string, format: ConfigFormat): unknown {
|
|
61
|
+
switch (format) {
|
|
62
|
+
case "json":
|
|
63
|
+
return readJsonConfig(path);
|
|
64
|
+
case "yaml":
|
|
65
|
+
return readYamlConfig(path);
|
|
66
|
+
case "toml":
|
|
67
|
+
return readTomlConfig(path);
|
|
68
|
+
default:
|
|
69
|
+
throw new Error(`Unsupported config format: ${format}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function writeConfig(
|
|
74
|
+
path: string,
|
|
75
|
+
format: ConfigFormat,
|
|
76
|
+
config: unknown
|
|
77
|
+
): void {
|
|
78
|
+
backupConfig(path);
|
|
79
|
+
|
|
80
|
+
let content: string;
|
|
81
|
+
switch (format) {
|
|
82
|
+
case "json":
|
|
83
|
+
content = JSON.stringify(config, null, 2);
|
|
84
|
+
break;
|
|
85
|
+
case "yaml":
|
|
86
|
+
content = stringifyYaml(config);
|
|
87
|
+
break;
|
|
88
|
+
case "toml":
|
|
89
|
+
content = stringifyToml(config as JsonMap);
|
|
90
|
+
break;
|
|
91
|
+
default:
|
|
92
|
+
throw new Error(`Unsupported config format: ${format}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
atomicWrite(path, content);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Resolve config path (handle both string and function)
|
|
100
|
+
*/
|
|
101
|
+
export function resolveConfigPath(
|
|
102
|
+
configPath: string | ((home: string) => string),
|
|
103
|
+
home: string
|
|
104
|
+
): string {
|
|
105
|
+
return typeof configPath === "function" ? configPath(home) : configPath;
|
|
106
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { platform } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { McpServer } from "../config/schema.js";
|
|
4
|
+
import { BaseProvider } from "./base.js";
|
|
5
|
+
|
|
6
|
+
export class WindsurfProvider extends BaseProvider {
|
|
7
|
+
name = "windsurf";
|
|
8
|
+
configPath = (home: string) => {
|
|
9
|
+
const os = platform();
|
|
10
|
+
if (os === "darwin") {
|
|
11
|
+
return join(
|
|
12
|
+
home,
|
|
13
|
+
"Library",
|
|
14
|
+
"Application Support",
|
|
15
|
+
"Windsurf",
|
|
16
|
+
"User",
|
|
17
|
+
"settings.json"
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return join(home, ".config", "Windsurf", "User", "settings.json");
|
|
21
|
+
};
|
|
22
|
+
format = "json" as const;
|
|
23
|
+
mcpKey = "mcpServers";
|
|
24
|
+
|
|
25
|
+
transformMcpServers(
|
|
26
|
+
servers: Record<string, McpServer>
|
|
27
|
+
): Record<string, unknown> {
|
|
28
|
+
const transformed: Record<string, unknown> = {};
|
|
29
|
+
|
|
30
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
31
|
+
if (server.url) {
|
|
32
|
+
transformed[name] = {
|
|
33
|
+
url: server.url,
|
|
34
|
+
transport: "streamable-http",
|
|
35
|
+
...(server.headers && { headers: server.headers }),
|
|
36
|
+
};
|
|
37
|
+
} else if (server.command) {
|
|
38
|
+
transformed[name] = {
|
|
39
|
+
command: server.command,
|
|
40
|
+
...(server.args && { args: server.args }),
|
|
41
|
+
...(server.env && { env: server.env }),
|
|
42
|
+
};
|
|
43
|
+
} else {
|
|
44
|
+
console.warn(`Skipping server "${name}" - missing command or url`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return transformed;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import type { McpServer } from "../config/schema.js";
|
|
3
|
+
import { BaseProvider } from "./base.js";
|
|
4
|
+
|
|
5
|
+
export class ZedProvider extends BaseProvider {
|
|
6
|
+
name = "zed";
|
|
7
|
+
configPath = (home: string) => join(home, ".config", "zed", "settings.json");
|
|
8
|
+
format = "json" as const;
|
|
9
|
+
mcpKey = "context_servers";
|
|
10
|
+
|
|
11
|
+
transformMcpServers(
|
|
12
|
+
servers: Record<string, McpServer>
|
|
13
|
+
): Record<string, unknown> {
|
|
14
|
+
const transformed: Record<string, unknown> = {};
|
|
15
|
+
|
|
16
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
17
|
+
if (server.url) {
|
|
18
|
+
transformed[name] = {
|
|
19
|
+
url: server.url,
|
|
20
|
+
...(server.headers && { headers: server.headers }),
|
|
21
|
+
};
|
|
22
|
+
} else if (server.command) {
|
|
23
|
+
transformed[name] = {
|
|
24
|
+
command: server.command,
|
|
25
|
+
...(server.args && { args: server.args }),
|
|
26
|
+
...(server.env && { env: server.env }),
|
|
27
|
+
};
|
|
28
|
+
} else {
|
|
29
|
+
console.warn(`Skipping server "${name}" - missing command or url`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return transformed;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
5
|
+
import { TaalConfigSchema } from "../config/schema.js";
|
|
6
|
+
|
|
7
|
+
// Generate JSON Schema from Zod schema
|
|
8
|
+
const jsonSchema = zodToJsonSchema(TaalConfigSchema, {
|
|
9
|
+
name: "TaalConfig",
|
|
10
|
+
$refStrategy: "none", // Inline all definitions for simplicity
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Write to root directory
|
|
14
|
+
const schemaPath = join(process.cwd(), "taal.schema.json");
|
|
15
|
+
writeFileSync(schemaPath, JSON.stringify(jsonSchema, null, 2));
|
|
16
|
+
|
|
17
|
+
console.log(`✓ Generated JSON Schema at ${schemaPath}`);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import type { Skill } from "./discovery.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Copy skills to a provider's skills directory
|
|
7
|
+
*
|
|
8
|
+
* @param skills - Array of skills to copy
|
|
9
|
+
* @param targetPath - Target directory path (provider's skills directory)
|
|
10
|
+
* @param options - Copy options
|
|
11
|
+
*/
|
|
12
|
+
export function copySkillsToProvider(
|
|
13
|
+
skills: Skill[],
|
|
14
|
+
targetPath: string,
|
|
15
|
+
options: { overwrite?: boolean } = {}
|
|
16
|
+
): void {
|
|
17
|
+
const { overwrite = true } = options;
|
|
18
|
+
|
|
19
|
+
// Create target directory if it doesn't exist
|
|
20
|
+
mkdirSync(targetPath, { recursive: true });
|
|
21
|
+
|
|
22
|
+
for (const skill of skills) {
|
|
23
|
+
const skillDirName = basename(skill.path);
|
|
24
|
+
const targetSkillPath = join(targetPath, skillDirName);
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Remove existing skill if overwrite is enabled
|
|
28
|
+
if (overwrite && existsSync(targetSkillPath)) {
|
|
29
|
+
rmSync(targetSkillPath, { recursive: true, force: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Copy skill directory recursively
|
|
33
|
+
cpSync(skill.path, targetSkillPath, {
|
|
34
|
+
recursive: true,
|
|
35
|
+
force: overwrite,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
console.log(`Copied skill "${skill.name}" to ${targetSkillPath}`);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.warn(`Failed to copy skill "${skill.name}": ${error}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Copy a single skill to a target directory
|
|
47
|
+
*
|
|
48
|
+
* @param skill - Skill to copy
|
|
49
|
+
* @param targetPath - Target directory path
|
|
50
|
+
* @param options - Copy options
|
|
51
|
+
*/
|
|
52
|
+
export function copySkill(
|
|
53
|
+
skill: Skill,
|
|
54
|
+
targetPath: string,
|
|
55
|
+
options: { overwrite?: boolean } = {}
|
|
56
|
+
): void {
|
|
57
|
+
copySkillsToProvider([skill], targetPath, options);
|
|
58
|
+
}
|