@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,88 @@
|
|
|
1
|
+
import { exists, readFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { parse } from "yaml";
|
|
5
|
+
import { findEnvVarReferences, substituteEnvVars } from "./env.js";
|
|
6
|
+
import { type TaalConfig, TaalConfigSchema } from "./schema.js";
|
|
7
|
+
|
|
8
|
+
export interface LoadConfigResult {
|
|
9
|
+
config: TaalConfig | null;
|
|
10
|
+
errors: string[];
|
|
11
|
+
warnings: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function loadTaalConfig(
|
|
15
|
+
baseDir?: string
|
|
16
|
+
): Promise<LoadConfigResult> {
|
|
17
|
+
const home = baseDir || homedir();
|
|
18
|
+
const configPath = join(home, ".taal", "config.yaml");
|
|
19
|
+
|
|
20
|
+
const errors: string[] = [];
|
|
21
|
+
const warnings: string[] = [];
|
|
22
|
+
|
|
23
|
+
if (!(await exists(configPath))) {
|
|
24
|
+
return {
|
|
25
|
+
config: null,
|
|
26
|
+
errors: ["Config file not found"],
|
|
27
|
+
warnings: [],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let content: string;
|
|
32
|
+
try {
|
|
33
|
+
content = await readFile(configPath, "utf-8");
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return {
|
|
36
|
+
config: null,
|
|
37
|
+
errors: [
|
|
38
|
+
`Failed to read config: ${error instanceof Error ? error.message : error}`,
|
|
39
|
+
],
|
|
40
|
+
warnings: [],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let rawConfig: unknown;
|
|
45
|
+
try {
|
|
46
|
+
rawConfig = parse(content);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return {
|
|
49
|
+
config: null,
|
|
50
|
+
errors: [
|
|
51
|
+
`Failed to parse YAML: ${error instanceof Error ? error.message : error}`,
|
|
52
|
+
],
|
|
53
|
+
warnings,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const envVarRefs = findEnvVarReferences(rawConfig);
|
|
58
|
+
for (const varName of envVarRefs) {
|
|
59
|
+
if (!process.env[varName]) {
|
|
60
|
+
warnings.push(`Environment variable ${varName} is not set`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const configWithEnv = substituteEnvVars(rawConfig);
|
|
65
|
+
const result = TaalConfigSchema.safeParse(configWithEnv);
|
|
66
|
+
|
|
67
|
+
if (!result.success) {
|
|
68
|
+
for (const issue of result.error.issues) {
|
|
69
|
+
errors.push(`${issue.path.join(".")}: ${issue.message}`);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
config: null,
|
|
73
|
+
errors,
|
|
74
|
+
warnings,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
config: result.data,
|
|
80
|
+
errors: [],
|
|
81
|
+
warnings,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getConfigPath(baseDir?: string): string {
|
|
86
|
+
const home = baseDir || homedir();
|
|
87
|
+
return join(home, ".taal", "config.yaml");
|
|
88
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { parse } from "yaml";
|
|
3
|
+
import { substituteEnvVars } from "./env.js";
|
|
4
|
+
import { type TaalConfig, TaalConfigSchema } from "./schema.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Loads and validates TAAL configuration from a YAML file
|
|
8
|
+
*
|
|
9
|
+
* @param path - Path to the YAML configuration file
|
|
10
|
+
* @returns Validated TaalConfig object
|
|
11
|
+
* @throws Error if file cannot be read, YAML is invalid, or validation fails
|
|
12
|
+
*/
|
|
13
|
+
export function loadConfig(path: string): TaalConfig {
|
|
14
|
+
// 1. Read file
|
|
15
|
+
let fileContent: string;
|
|
16
|
+
try {
|
|
17
|
+
fileContent = readFileSync(path, "utf-8");
|
|
18
|
+
} catch (error) {
|
|
19
|
+
throw new Error(`Failed to read config file at ${path}: ${error}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 2. Parse YAML
|
|
23
|
+
let rawConfig: unknown;
|
|
24
|
+
try {
|
|
25
|
+
rawConfig = parse(fileContent);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
throw new Error(`Failed to parse YAML in ${path}: ${error}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 3. Substitute environment variables
|
|
31
|
+
const configWithEnv = substituteEnvVars(rawConfig);
|
|
32
|
+
|
|
33
|
+
// 4. Validate with Zod
|
|
34
|
+
const result = TaalConfigSchema.safeParse(configWithEnv);
|
|
35
|
+
|
|
36
|
+
if (!result.success) {
|
|
37
|
+
const errors = result.error.issues
|
|
38
|
+
.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`)
|
|
39
|
+
.join("\n");
|
|
40
|
+
throw new Error(`Config validation failed:\n${errors}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return result.data;
|
|
44
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Server Schema
|
|
5
|
+
* Supports either stdio (command-based) OR HTTP (url-based) servers, not both
|
|
6
|
+
*/
|
|
7
|
+
export const McpServerSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
// Stdio server fields
|
|
10
|
+
command: z.string().optional(),
|
|
11
|
+
args: z.array(z.string()).optional(),
|
|
12
|
+
env: z.record(z.string()).optional(),
|
|
13
|
+
|
|
14
|
+
// HTTP server fields
|
|
15
|
+
url: z.string().optional(),
|
|
16
|
+
headers: z.record(z.string()).optional(),
|
|
17
|
+
|
|
18
|
+
// Provider-specific overrides
|
|
19
|
+
overrides: z
|
|
20
|
+
.record(
|
|
21
|
+
z.object({
|
|
22
|
+
enabled_tools: z.array(z.string()).optional(),
|
|
23
|
+
})
|
|
24
|
+
)
|
|
25
|
+
.optional(),
|
|
26
|
+
})
|
|
27
|
+
.refine(
|
|
28
|
+
(data) => {
|
|
29
|
+
const hasStdio = !!data.command;
|
|
30
|
+
const hasHttp = !!data.url;
|
|
31
|
+
// Must be either stdio OR http, not both, not neither
|
|
32
|
+
return (hasStdio && !hasHttp) || (hasHttp && !hasStdio);
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
message: "Server must be either stdio (command) or http (url), not both",
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Skills Configuration Schema
|
|
41
|
+
*/
|
|
42
|
+
export const SkillsConfigSchema = z.object({
|
|
43
|
+
paths: z.array(z.string()),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Providers Configuration Schema
|
|
48
|
+
*/
|
|
49
|
+
export const ProvidersConfigSchema = z.object({
|
|
50
|
+
enabled: z.array(z.string()),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* TAAL Configuration Schema
|
|
55
|
+
*/
|
|
56
|
+
export const TaalConfigSchema = z.object({
|
|
57
|
+
version: z.literal("1"),
|
|
58
|
+
mcp: z.record(McpServerSchema).optional(),
|
|
59
|
+
skills: SkillsConfigSchema.optional(),
|
|
60
|
+
providers: ProvidersConfigSchema.optional(),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Export inferred types
|
|
64
|
+
export type McpServer = z.infer<typeof McpServerSchema>;
|
|
65
|
+
export type SkillsConfig = z.infer<typeof SkillsConfigSchema>;
|
|
66
|
+
export type ProvidersConfig = z.infer<typeof ProvidersConfigSchema>;
|
|
67
|
+
export type TaalConfig = z.infer<typeof TaalConfigSchema>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class TaalError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
|
|
4
|
+
constructor(message: string, code: string) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "TaalError";
|
|
7
|
+
this.code = code;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class ConfigError extends TaalError {
|
|
12
|
+
constructor(message: string) {
|
|
13
|
+
super(message, "CONFIG_ERROR");
|
|
14
|
+
this.name = "ConfigError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class ProviderError extends TaalError {
|
|
19
|
+
readonly providerName: string;
|
|
20
|
+
|
|
21
|
+
constructor(message: string, providerName: string) {
|
|
22
|
+
super(message, "PROVIDER_ERROR");
|
|
23
|
+
this.name = "ProviderError";
|
|
24
|
+
this.providerName = providerName;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class ValidationError extends TaalError {
|
|
29
|
+
readonly errors: string[];
|
|
30
|
+
|
|
31
|
+
constructor(message: string, errors: string[]) {
|
|
32
|
+
super(message, "VALIDATION_ERROR");
|
|
33
|
+
this.name = "ValidationError";
|
|
34
|
+
this.errors = errors;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function formatError(error: unknown): string {
|
|
39
|
+
if (error instanceof Error) {
|
|
40
|
+
return error.message;
|
|
41
|
+
}
|
|
42
|
+
return String(error);
|
|
43
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { exists, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import YAML from "yaml";
|
|
8
|
+
import { collect } from "./commands/collect";
|
|
9
|
+
import { diff } from "./commands/diff";
|
|
10
|
+
import { init } from "./commands/init";
|
|
11
|
+
import { list } from "./commands/list";
|
|
12
|
+
import { providers } from "./commands/providers";
|
|
13
|
+
import { sync } from "./commands/sync";
|
|
14
|
+
import { validate } from "./commands/validate";
|
|
15
|
+
|
|
16
|
+
const program = new Command();
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.name("taal")
|
|
20
|
+
.description(
|
|
21
|
+
"CLI to sync MCP server configs and Agent Skills across AI providers"
|
|
22
|
+
)
|
|
23
|
+
.version("1.0.0");
|
|
24
|
+
|
|
25
|
+
program
|
|
26
|
+
.command("init")
|
|
27
|
+
.description("Initialize TAAL configuration")
|
|
28
|
+
.option("-f, --force", "Overwrite existing configuration")
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
try {
|
|
31
|
+
await init(homedir(), options);
|
|
32
|
+
console.log("✓ TAAL initialized successfully");
|
|
33
|
+
console.log(` Config: ${homedir()}/.taal/config.yaml`);
|
|
34
|
+
console.log(` Skills: ${homedir()}/.taal/skills/`);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
program
|
|
42
|
+
.command("collect")
|
|
43
|
+
.description("Import existing MCP configs from installed providers")
|
|
44
|
+
.action(async () => {
|
|
45
|
+
try {
|
|
46
|
+
console.log("Scanning installed providers...");
|
|
47
|
+
const result = await collect();
|
|
48
|
+
|
|
49
|
+
console.log(
|
|
50
|
+
`\n✓ Found ${result.summary.totalServers} servers from ${result.summary.providersWithConfigs} providers`
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (result.conflicts.length > 0) {
|
|
54
|
+
console.log("\n⚠ Conflicts detected:");
|
|
55
|
+
for (const conflict of result.conflicts) {
|
|
56
|
+
console.log(
|
|
57
|
+
` - "${conflict.serverName}" found in: ${conflict.providers.join(", ")}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const configPath = join(homedir(), ".taal", "config.yaml");
|
|
63
|
+
interface TaalConfigFile {
|
|
64
|
+
version: string;
|
|
65
|
+
mcp: Record<string, unknown>;
|
|
66
|
+
skills?: { paths: string[] };
|
|
67
|
+
providers?: { enabled: string[] };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let existingConfig: TaalConfigFile = {
|
|
71
|
+
version: "1",
|
|
72
|
+
mcp: {},
|
|
73
|
+
skills: { paths: ["~/.taal/skills"] },
|
|
74
|
+
providers: { enabled: [] },
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if (await exists(configPath)) {
|
|
78
|
+
const content = await readFile(configPath, "utf-8");
|
|
79
|
+
existingConfig = YAML.parse(content) as TaalConfigFile;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
existingConfig.mcp = { ...existingConfig.mcp, ...result.servers };
|
|
83
|
+
|
|
84
|
+
await writeFile(configPath, YAML.stringify(existingConfig), "utf-8");
|
|
85
|
+
console.log(`\n✓ Updated config: ${configPath}`);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
program
|
|
93
|
+
.command("validate")
|
|
94
|
+
.description("Validate TAAL configuration")
|
|
95
|
+
.action(async () => {
|
|
96
|
+
try {
|
|
97
|
+
const result = await validate();
|
|
98
|
+
|
|
99
|
+
if (result.warnings.length > 0) {
|
|
100
|
+
console.log(chalk.yellow("\nWarnings:"));
|
|
101
|
+
for (const warning of result.warnings) {
|
|
102
|
+
console.log(chalk.yellow(` ⚠ ${warning}`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (result.valid) {
|
|
107
|
+
console.log(chalk.green("\n✓ Configuration is valid"));
|
|
108
|
+
process.exit(0);
|
|
109
|
+
} else {
|
|
110
|
+
console.log(chalk.red("\n✗ Configuration is invalid\n"));
|
|
111
|
+
console.log(chalk.red("Errors:"));
|
|
112
|
+
for (const error of result.errors) {
|
|
113
|
+
console.log(chalk.red(` • ${error}`));
|
|
114
|
+
}
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
program
|
|
124
|
+
.command("diff [provider]")
|
|
125
|
+
.description("Show what would change without writing")
|
|
126
|
+
.action(async (provider?: string) => {
|
|
127
|
+
try {
|
|
128
|
+
const result = await diff(undefined, provider);
|
|
129
|
+
|
|
130
|
+
if (result.error) {
|
|
131
|
+
console.error(chalk.red(`Error: ${result.error}`));
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!result.hasChanges) {
|
|
136
|
+
console.log(chalk.green("\n✓ No changes detected"));
|
|
137
|
+
process.exit(0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log(chalk.bold("\nChanges:\n"));
|
|
141
|
+
|
|
142
|
+
const byProvider = new Map<string, typeof result.changes>();
|
|
143
|
+
for (const change of result.changes) {
|
|
144
|
+
if (!byProvider.has(change.provider)) {
|
|
145
|
+
byProvider.set(change.provider, []);
|
|
146
|
+
}
|
|
147
|
+
byProvider.get(change.provider)?.push(change);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (const [providerName, changes] of byProvider) {
|
|
151
|
+
console.log(chalk.bold(`${providerName}:`));
|
|
152
|
+
for (const change of changes) {
|
|
153
|
+
if (change.type === "add") {
|
|
154
|
+
console.log(chalk.green(` + ${change.serverName}`));
|
|
155
|
+
} else if (change.type === "remove") {
|
|
156
|
+
console.log(chalk.red(` - ${change.serverName}`));
|
|
157
|
+
} else {
|
|
158
|
+
console.log(chalk.yellow(` ~ ${change.serverName}`));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
console.log();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
process.exit(1);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
program
|
|
172
|
+
.command("sync [provider]")
|
|
173
|
+
.description("Sync MCP configs and skills to providers")
|
|
174
|
+
.action(async (provider?: string) => {
|
|
175
|
+
try {
|
|
176
|
+
console.log(chalk.bold("Syncing..."));
|
|
177
|
+
const result = await sync(undefined, provider);
|
|
178
|
+
|
|
179
|
+
if (result.error) {
|
|
180
|
+
console.error(chalk.red(`\nError: ${result.error}`));
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (result.synced.length > 0) {
|
|
185
|
+
console.log(
|
|
186
|
+
chalk.green(`\n✓ Synced to ${result.synced.length} provider(s):`)
|
|
187
|
+
);
|
|
188
|
+
for (const p of result.synced) {
|
|
189
|
+
console.log(chalk.green(` • ${p}`));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (result.failed.length > 0) {
|
|
194
|
+
console.log(
|
|
195
|
+
chalk.red(`\n✗ Failed to sync ${result.failed.length} provider(s):`)
|
|
196
|
+
);
|
|
197
|
+
for (const f of result.failed) {
|
|
198
|
+
console.log(chalk.red(` • ${f.provider}: ${f.error}`));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
process.exit(result.success ? 0 : 1);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
program
|
|
210
|
+
.command("list")
|
|
211
|
+
.description("List configured MCP servers and skills")
|
|
212
|
+
.action(async () => {
|
|
213
|
+
try {
|
|
214
|
+
const result = await list();
|
|
215
|
+
|
|
216
|
+
if (result.error) {
|
|
217
|
+
console.error(chalk.red(`Error: ${result.error}`));
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log(chalk.bold("\nMCP Servers:\n"));
|
|
222
|
+
if (result.servers.length === 0) {
|
|
223
|
+
console.log(chalk.dim(" No servers configured"));
|
|
224
|
+
} else {
|
|
225
|
+
for (const server of result.servers) {
|
|
226
|
+
const type =
|
|
227
|
+
server.type === "stdio"
|
|
228
|
+
? chalk.blue("[stdio]")
|
|
229
|
+
: chalk.green("[http]");
|
|
230
|
+
const detail = server.command || server.url || "";
|
|
231
|
+
console.log(
|
|
232
|
+
` ${type} ${chalk.bold(server.name)} ${chalk.dim(detail)}`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
console.log(chalk.bold("\nSkills:\n"));
|
|
238
|
+
if (result.skills.length === 0) {
|
|
239
|
+
console.log(chalk.dim(" No skills found"));
|
|
240
|
+
} else {
|
|
241
|
+
for (const skill of result.skills) {
|
|
242
|
+
console.log(` • ${chalk.bold(skill.name)} ${chalk.dim(skill.path)}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
console.log(chalk.bold("\nEnabled Providers:\n"));
|
|
247
|
+
if (result.enabledProviders.length === 0) {
|
|
248
|
+
console.log(chalk.dim(" No providers enabled"));
|
|
249
|
+
} else {
|
|
250
|
+
for (const provider of result.enabledProviders) {
|
|
251
|
+
console.log(` • ${provider}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
console.log();
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
program
|
|
263
|
+
.command("providers")
|
|
264
|
+
.description("List all supported providers")
|
|
265
|
+
.action(async () => {
|
|
266
|
+
try {
|
|
267
|
+
const result = await providers();
|
|
268
|
+
|
|
269
|
+
console.log(chalk.bold("\nSupported Providers:\n"));
|
|
270
|
+
|
|
271
|
+
for (const provider of result.providers) {
|
|
272
|
+
const status: string[] = [];
|
|
273
|
+
|
|
274
|
+
if (provider.installed) {
|
|
275
|
+
status.push(chalk.green("installed"));
|
|
276
|
+
} else {
|
|
277
|
+
status.push(chalk.dim("not installed"));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (provider.enabled) {
|
|
281
|
+
status.push(chalk.blue("enabled"));
|
|
282
|
+
} else {
|
|
283
|
+
status.push(chalk.dim("disabled"));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const format = chalk.yellow(`[${provider.format}]`);
|
|
287
|
+
|
|
288
|
+
console.log(
|
|
289
|
+
` ${chalk.bold(provider.name)} ${format} ${status.join(", ")}`
|
|
290
|
+
);
|
|
291
|
+
console.log(chalk.dim(` ${provider.configPath}`));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
console.log();
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
program.parse();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { platform } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { BaseProvider, createStdioTransformer } from "./base.js";
|
|
4
|
+
|
|
5
|
+
export class AntigravityProvider extends BaseProvider {
|
|
6
|
+
name = "antigravity";
|
|
7
|
+
configPath = (home: string) => {
|
|
8
|
+
const os = platform();
|
|
9
|
+
if (os === "darwin") {
|
|
10
|
+
return join(
|
|
11
|
+
home,
|
|
12
|
+
"Library",
|
|
13
|
+
"Application Support",
|
|
14
|
+
"Antigravity",
|
|
15
|
+
"settings.json"
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return join(home, ".config", "antigravity", "settings.json");
|
|
19
|
+
};
|
|
20
|
+
format = "json" as const;
|
|
21
|
+
mcpKey = "mcpServers";
|
|
22
|
+
|
|
23
|
+
transformMcpServers = createStdioTransformer("Antigravity");
|
|
24
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import type { McpServer } from "../config/schema.js";
|
|
5
|
+
import type { ConfigFormat, Provider } from "./types.js";
|
|
6
|
+
import {
|
|
7
|
+
readConfig,
|
|
8
|
+
resolveConfigPath,
|
|
9
|
+
writeConfig as writeConfigUtil,
|
|
10
|
+
} from "./utils.js";
|
|
11
|
+
|
|
12
|
+
export abstract class BaseProvider implements Provider {
|
|
13
|
+
abstract name: string;
|
|
14
|
+
abstract configPath: string | ((home: string) => string);
|
|
15
|
+
abstract format: ConfigFormat;
|
|
16
|
+
abstract mcpKey: string;
|
|
17
|
+
skillsPath?: string | ((home: string) => string);
|
|
18
|
+
|
|
19
|
+
async isInstalled(home?: string): Promise<boolean> {
|
|
20
|
+
const homeDir = home || homedir();
|
|
21
|
+
const configDir = dirname(resolveConfigPath(this.configPath, homeDir));
|
|
22
|
+
return existsSync(configDir);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async readConfig(home?: string): Promise<unknown> {
|
|
26
|
+
const homeDir = home || homedir();
|
|
27
|
+
const path = resolveConfigPath(this.configPath, homeDir);
|
|
28
|
+
return readConfig(path, this.format);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async writeConfig(config: unknown, home?: string): Promise<void> {
|
|
32
|
+
const homeDir = home || homedir();
|
|
33
|
+
const path = resolveConfigPath(this.configPath, homeDir);
|
|
34
|
+
writeConfigUtil(path, this.format, config);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
abstract transformMcpServers(
|
|
38
|
+
servers: Record<string, McpServer>
|
|
39
|
+
): Record<string, unknown> | unknown[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createStdioTransformer(providerName: string) {
|
|
43
|
+
return function transformMcpServers(
|
|
44
|
+
servers: Record<string, McpServer>
|
|
45
|
+
): Record<string, unknown> {
|
|
46
|
+
const transformed: Record<string, unknown> = {};
|
|
47
|
+
|
|
48
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
49
|
+
if (server.url) {
|
|
50
|
+
console.warn(
|
|
51
|
+
`Skipping HTTP server "${name}" - ${providerName} only supports stdio servers`
|
|
52
|
+
);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!server.command) {
|
|
57
|
+
console.warn(`Skipping server "${name}" - missing command`);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
transformed[name] = {
|
|
62
|
+
command: server.command,
|
|
63
|
+
...(server.args && { args: server.args }),
|
|
64
|
+
...(server.env && { env: server.env }),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return transformed;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { BaseProvider, createStdioTransformer } from "./base.js";
|
|
3
|
+
|
|
4
|
+
export class ClaudeCodeProvider extends BaseProvider {
|
|
5
|
+
name = "claude-code";
|
|
6
|
+
configPath = (home: string) => join(home, ".claude", "settings.json");
|
|
7
|
+
format = "json" as const;
|
|
8
|
+
mcpKey = "mcpServers";
|
|
9
|
+
skillsPath = (home: string) => join(home, ".claude", "skills");
|
|
10
|
+
|
|
11
|
+
transformMcpServers = createStdioTransformer("Claude Code");
|
|
12
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { BaseProvider, createStdioTransformer } from "./base.js";
|
|
3
|
+
|
|
4
|
+
export class ClaudeDesktopProvider extends BaseProvider {
|
|
5
|
+
name = "claude-desktop";
|
|
6
|
+
configPath = (home: string) =>
|
|
7
|
+
join(
|
|
8
|
+
home,
|
|
9
|
+
"Library",
|
|
10
|
+
"Application Support",
|
|
11
|
+
"Claude",
|
|
12
|
+
"claude_desktop_config.json"
|
|
13
|
+
);
|
|
14
|
+
format = "json" as const;
|
|
15
|
+
mcpKey = "mcpServers";
|
|
16
|
+
skillsPath = (home: string) => join(home, ".claude", "skills");
|
|
17
|
+
|
|
18
|
+
transformMcpServers = createStdioTransformer("Claude Desktop");
|
|
19
|
+
}
|