@anaclumos/taal 1.1.8 → 1.1.9
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/package.json +2 -1
- package/src/commands/collect.ts +12 -9
- package/src/commands/diff.ts +9 -25
- package/src/commands/list.ts +14 -17
- package/src/commands/sync.ts +3 -2
- package/src/config/env.ts +6 -4
- package/src/config/loader.ts +3 -2
- package/src/config/parser.ts +7 -2
- package/src/errors/index.ts +3 -1
- package/src/index.ts +8 -7
- package/src/providers/continue.ts +4 -5
- package/src/skills/validator.ts +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anaclumos/taal",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.9",
|
|
4
4
|
"description": "CLI tool to sync MCP server configs and Agent Skills across AI coding assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@iarna/toml": "^2.2.5",
|
|
40
40
|
"chalk": "^5.3.0",
|
|
41
41
|
"commander": "^12.0.0",
|
|
42
|
+
"es-toolkit": "^1.44.0",
|
|
42
43
|
"jsonc-parser": "^3.3.1",
|
|
43
44
|
"yaml": "^2.3.4",
|
|
44
45
|
"zod": "^3.22.4"
|
package/src/commands/collect.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { isEqual, isPlainObject, isString, uniqWith } from "es-toolkit";
|
|
4
5
|
import type { McpServer } from "../config/schema.js";
|
|
5
6
|
import { initializeProviders, registry } from "../providers/index.js";
|
|
6
7
|
import { copySkillsToProvider } from "../skills/copy.js";
|
|
@@ -95,10 +96,12 @@ export async function collect(baseDir?: string): Promise<CollectResult> {
|
|
|
95
96
|
for (const [serverName, sources] of serverSources.entries()) {
|
|
96
97
|
if (sources.length > 1) {
|
|
97
98
|
// Check if configs are actually different
|
|
98
|
-
const
|
|
99
|
-
|
|
99
|
+
const uniqueConfigs = uniqWith(
|
|
100
|
+
sources.map((s) => s.config),
|
|
101
|
+
isEqual
|
|
102
|
+
);
|
|
100
103
|
|
|
101
|
-
if (uniqueConfigs.
|
|
104
|
+
if (uniqueConfigs.length > 1) {
|
|
102
105
|
conflicts.push({
|
|
103
106
|
serverName,
|
|
104
107
|
providers: sources.map((s) => s.provider),
|
|
@@ -159,14 +162,14 @@ export async function collect(baseDir?: string): Promise<CollectResult> {
|
|
|
159
162
|
* Convert provider-specific server config to TAAL format
|
|
160
163
|
*/
|
|
161
164
|
function convertToTaalFormat(config: unknown): McpServer | null {
|
|
162
|
-
if (!config
|
|
165
|
+
if (!isPlainObject(config)) {
|
|
163
166
|
return null;
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
const obj = config as Record<string, unknown>;
|
|
167
170
|
|
|
168
171
|
// HTTP server
|
|
169
|
-
if (obj.url &&
|
|
172
|
+
if (obj.url && isString(obj.url)) {
|
|
170
173
|
return {
|
|
171
174
|
url: obj.url,
|
|
172
175
|
headers:
|
|
@@ -176,7 +179,7 @@ function convertToTaalFormat(config: unknown): McpServer | null {
|
|
|
176
179
|
}
|
|
177
180
|
|
|
178
181
|
// Stdio server
|
|
179
|
-
if (obj.command &&
|
|
182
|
+
if (obj.command && isString(obj.command)) {
|
|
180
183
|
const server: McpServer = {
|
|
181
184
|
command: obj.command,
|
|
182
185
|
};
|
|
@@ -185,9 +188,9 @@ function convertToTaalFormat(config: unknown): McpServer | null {
|
|
|
185
188
|
server.args = obj.args as string[];
|
|
186
189
|
}
|
|
187
190
|
|
|
188
|
-
if (obj.env &&
|
|
191
|
+
if (obj.env && isPlainObject(obj.env)) {
|
|
189
192
|
server.env = obj.env as Record<string, string>;
|
|
190
|
-
} else if (obj.environment &&
|
|
193
|
+
} else if (obj.environment && isPlainObject(obj.environment)) {
|
|
191
194
|
server.env = obj.environment as Record<string, string>;
|
|
192
195
|
}
|
|
193
196
|
|
|
@@ -202,7 +205,7 @@ function convertToTaalFormat(config: unknown): McpServer | null {
|
|
|
202
205
|
args,
|
|
203
206
|
};
|
|
204
207
|
|
|
205
|
-
if (obj.environment &&
|
|
208
|
+
if (obj.environment && isPlainObject(obj.environment)) {
|
|
206
209
|
server.env = obj.environment as Record<string, string>;
|
|
207
210
|
}
|
|
208
211
|
|
package/src/commands/diff.ts
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
+
import { isEqual } from "es-toolkit";
|
|
3
|
+
import { isError } from "es-toolkit/predicate";
|
|
2
4
|
import { loadTaalConfig } from "../config/loader.js";
|
|
3
5
|
import { initializeProviders, registry } from "../providers/index.js";
|
|
4
6
|
|
|
5
|
-
function sortObjectKeys(obj: unknown): unknown {
|
|
6
|
-
if (obj === null || typeof obj !== "object") {
|
|
7
|
-
return obj;
|
|
8
|
-
}
|
|
9
|
-
if (Array.isArray(obj)) {
|
|
10
|
-
return obj.map(sortObjectKeys);
|
|
11
|
-
}
|
|
12
|
-
const sorted: Record<string, unknown> = {};
|
|
13
|
-
for (const key of Object.keys(obj).sort()) {
|
|
14
|
-
sorted[key] = sortObjectKeys((obj as Record<string, unknown>)[key]);
|
|
15
|
-
}
|
|
16
|
-
return sorted;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
7
|
export interface DiffChange {
|
|
20
8
|
type: "add" | "remove" | "modify";
|
|
21
9
|
serverName: string;
|
|
@@ -87,16 +75,12 @@ export async function diff(
|
|
|
87
75
|
|
|
88
76
|
for (const key of taalKeys) {
|
|
89
77
|
if (currentKeys.has(key)) {
|
|
90
|
-
const currentValue =
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
)
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
if (currentValue !== newValue) {
|
|
78
|
+
const currentValue = currentServers[key];
|
|
79
|
+
const newValue = (transformedServers as Record<string, unknown>)[
|
|
80
|
+
key
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
if (!isEqual(currentValue, newValue)) {
|
|
100
84
|
changes.push({
|
|
101
85
|
type: "modify",
|
|
102
86
|
serverName: key,
|
|
@@ -139,7 +123,7 @@ export async function diff(
|
|
|
139
123
|
return {
|
|
140
124
|
hasChanges: false,
|
|
141
125
|
changes: [],
|
|
142
|
-
error: error
|
|
126
|
+
error: isError(error) ? error.message : String(error),
|
|
143
127
|
};
|
|
144
128
|
}
|
|
145
129
|
}
|
package/src/commands/list.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
+
import { compact } from "es-toolkit/array";
|
|
3
|
+
import { isError } from "es-toolkit/predicate";
|
|
2
4
|
import { loadTaalConfig } from "../config/loader.js";
|
|
3
5
|
import { discoverSkills } from "../skills/discovery.js";
|
|
4
6
|
|
|
@@ -37,22 +39,17 @@ export async function list(baseDir?: string): Promise<ListResult> {
|
|
|
37
39
|
const config = result.config;
|
|
38
40
|
|
|
39
41
|
try {
|
|
40
|
-
const servers
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
type: "stdio",
|
|
52
|
-
command: server.command,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
}
|
|
42
|
+
const servers = compact(
|
|
43
|
+
Object.entries(config.mcp || {}).map(([name, server]) => {
|
|
44
|
+
if (server.url) {
|
|
45
|
+
return { name, type: "http" as const, url: server.url };
|
|
46
|
+
}
|
|
47
|
+
if (server.command) {
|
|
48
|
+
return { name, type: "stdio" as const, command: server.command };
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
})
|
|
52
|
+
);
|
|
56
53
|
|
|
57
54
|
const skillPaths = config.skills?.paths || [];
|
|
58
55
|
const discoveredSkills = await discoverSkills(skillPaths, home);
|
|
@@ -74,7 +71,7 @@ export async function list(baseDir?: string): Promise<ListResult> {
|
|
|
74
71
|
servers: [],
|
|
75
72
|
skills: [],
|
|
76
73
|
enabledProviders: [],
|
|
77
|
-
error: error
|
|
74
|
+
error: isError(error) ? error.message : String(error),
|
|
78
75
|
};
|
|
79
76
|
}
|
|
80
77
|
}
|
package/src/commands/sync.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
+
import { isError } from "es-toolkit/predicate";
|
|
2
3
|
import { loadTaalConfig } from "../config/loader.js";
|
|
3
4
|
import { initializeProviders, registry } from "../providers/index.js";
|
|
4
5
|
import { copySkillsToProvider } from "../skills/copy.js";
|
|
@@ -90,7 +91,7 @@ export async function sync(
|
|
|
90
91
|
} catch (error) {
|
|
91
92
|
failed.push({
|
|
92
93
|
provider: provider.name,
|
|
93
|
-
error: error
|
|
94
|
+
error: isError(error) ? error.message : String(error),
|
|
94
95
|
});
|
|
95
96
|
}
|
|
96
97
|
}
|
|
@@ -105,7 +106,7 @@ export async function sync(
|
|
|
105
106
|
success: false,
|
|
106
107
|
synced: [],
|
|
107
108
|
failed: [],
|
|
108
|
-
error: error
|
|
109
|
+
error: isError(error) ? error.message : String(error),
|
|
109
110
|
};
|
|
110
111
|
}
|
|
111
112
|
}
|
package/src/config/env.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { isPlainObject, isString } from "es-toolkit";
|
|
2
|
+
|
|
1
3
|
export function findEnvVarReferences(value: unknown): string[] {
|
|
2
4
|
const refs = new Set<string>();
|
|
3
5
|
|
|
4
|
-
if (
|
|
6
|
+
if (isString(value)) {
|
|
5
7
|
const matches = value.matchAll(/\$\{([^}]+)\}/g);
|
|
6
8
|
for (const match of matches) {
|
|
7
9
|
refs.add(match[1]);
|
|
@@ -12,7 +14,7 @@ export function findEnvVarReferences(value: unknown): string[] {
|
|
|
12
14
|
refs.add(ref);
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
|
-
} else if (value
|
|
17
|
+
} else if (isPlainObject(value)) {
|
|
16
18
|
for (const v of Object.values(value)) {
|
|
17
19
|
for (const ref of findEnvVarReferences(v)) {
|
|
18
20
|
refs.add(ref);
|
|
@@ -24,7 +26,7 @@ export function findEnvVarReferences(value: unknown): string[] {
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export function substituteEnvVars(value: unknown): unknown {
|
|
27
|
-
if (
|
|
29
|
+
if (isString(value)) {
|
|
28
30
|
return value.replace(/\$\{([^}]+)\}/g, (match, varName) => {
|
|
29
31
|
const envValue = process.env[varName];
|
|
30
32
|
if (envValue === undefined) {
|
|
@@ -39,7 +41,7 @@ export function substituteEnvVars(value: unknown): unknown {
|
|
|
39
41
|
return value.map(substituteEnvVars);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
if (value
|
|
44
|
+
if (isPlainObject(value)) {
|
|
43
45
|
return Object.fromEntries(
|
|
44
46
|
Object.entries(value).map(([k, v]) => [k, substituteEnvVars(v)])
|
|
45
47
|
);
|
package/src/config/loader.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { exists, readFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { isError } from "es-toolkit/predicate";
|
|
4
5
|
import { parse } from "yaml";
|
|
5
6
|
import { findEnvVarReferences, substituteEnvVars } from "./env.js";
|
|
6
7
|
import { type TaalConfig, TaalConfigSchema } from "./schema.js";
|
|
@@ -35,7 +36,7 @@ export async function loadTaalConfig(
|
|
|
35
36
|
return {
|
|
36
37
|
config: null,
|
|
37
38
|
errors: [
|
|
38
|
-
`Failed to read config: ${error
|
|
39
|
+
`Failed to read config: ${isError(error) ? error.message : String(error)}`,
|
|
39
40
|
],
|
|
40
41
|
warnings: [],
|
|
41
42
|
};
|
|
@@ -48,7 +49,7 @@ export async function loadTaalConfig(
|
|
|
48
49
|
return {
|
|
49
50
|
config: null,
|
|
50
51
|
errors: [
|
|
51
|
-
`Failed to parse YAML: ${error
|
|
52
|
+
`Failed to parse YAML: ${isError(error) ? error.message : String(error)}`,
|
|
52
53
|
],
|
|
53
54
|
warnings,
|
|
54
55
|
};
|
package/src/config/parser.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import { isError } from "es-toolkit/predicate";
|
|
2
3
|
import { parse } from "yaml";
|
|
3
4
|
import { substituteEnvVars } from "./env.js";
|
|
4
5
|
import { type TaalConfig, TaalConfigSchema } from "./schema.js";
|
|
@@ -16,7 +17,9 @@ export function loadConfig(path: string): TaalConfig {
|
|
|
16
17
|
try {
|
|
17
18
|
fileContent = readFileSync(path, "utf-8");
|
|
18
19
|
} catch (error) {
|
|
19
|
-
throw new Error(
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Failed to read config file at ${path}: ${isError(error) ? error.message : String(error)}`
|
|
22
|
+
);
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
// 2. Parse YAML
|
|
@@ -24,7 +27,9 @@ export function loadConfig(path: string): TaalConfig {
|
|
|
24
27
|
try {
|
|
25
28
|
rawConfig = parse(fileContent);
|
|
26
29
|
} catch (error) {
|
|
27
|
-
throw new Error(
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Failed to parse YAML in ${path}: ${isError(error) ? error.message : String(error)}`
|
|
32
|
+
);
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
// 3. Substitute environment variables
|
package/src/errors/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isError } from "es-toolkit/predicate";
|
|
2
|
+
|
|
1
3
|
export class TaalError extends Error {
|
|
2
4
|
readonly code: string;
|
|
3
5
|
|
|
@@ -36,7 +38,7 @@ export class ValidationError extends TaalError {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export function formatError(error: unknown): string {
|
|
39
|
-
if (error
|
|
41
|
+
if (isError(error)) {
|
|
40
42
|
return error.message;
|
|
41
43
|
}
|
|
42
44
|
return String(error);
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { Command } from "commander";
|
|
7
|
+
import { isError } from "es-toolkit/predicate";
|
|
7
8
|
import YAML from "yaml";
|
|
8
9
|
import { collect } from "./commands/collect";
|
|
9
10
|
import { diff } from "./commands/diff";
|
|
@@ -46,7 +47,7 @@ program
|
|
|
46
47
|
);
|
|
47
48
|
}
|
|
48
49
|
} catch (error) {
|
|
49
|
-
console.error("Error:", error
|
|
50
|
+
console.error("Error:", isError(error) ? error.message : error);
|
|
50
51
|
process.exit(1);
|
|
51
52
|
}
|
|
52
53
|
});
|
|
@@ -101,7 +102,7 @@ program
|
|
|
101
102
|
await writeFile(configPath, YAML.stringify(existingConfig), "utf-8");
|
|
102
103
|
console.log(`\n✓ Updated config: ${configPath}`);
|
|
103
104
|
} catch (error) {
|
|
104
|
-
console.error("Error:", error
|
|
105
|
+
console.error("Error:", isError(error) ? error.message : error);
|
|
105
106
|
process.exit(1);
|
|
106
107
|
}
|
|
107
108
|
});
|
|
@@ -132,7 +133,7 @@ program
|
|
|
132
133
|
process.exit(1);
|
|
133
134
|
}
|
|
134
135
|
} catch (error) {
|
|
135
|
-
console.error("Error:", error
|
|
136
|
+
console.error("Error:", isError(error) ? error.message : error);
|
|
136
137
|
process.exit(1);
|
|
137
138
|
}
|
|
138
139
|
});
|
|
@@ -180,7 +181,7 @@ program
|
|
|
180
181
|
|
|
181
182
|
process.exit(1);
|
|
182
183
|
} catch (error) {
|
|
183
|
-
console.error("Error:", error
|
|
184
|
+
console.error("Error:", isError(error) ? error.message : error);
|
|
184
185
|
process.exit(1);
|
|
185
186
|
}
|
|
186
187
|
});
|
|
@@ -218,7 +219,7 @@ program
|
|
|
218
219
|
|
|
219
220
|
process.exit(result.success ? 0 : 1);
|
|
220
221
|
} catch (error) {
|
|
221
|
-
console.error("Error:", error
|
|
222
|
+
console.error("Error:", isError(error) ? error.message : error);
|
|
222
223
|
process.exit(1);
|
|
223
224
|
}
|
|
224
225
|
});
|
|
@@ -271,7 +272,7 @@ program
|
|
|
271
272
|
|
|
272
273
|
console.log();
|
|
273
274
|
} catch (error) {
|
|
274
|
-
console.error("Error:", error
|
|
275
|
+
console.error("Error:", isError(error) ? error.message : error);
|
|
275
276
|
process.exit(1);
|
|
276
277
|
}
|
|
277
278
|
});
|
|
@@ -310,7 +311,7 @@ program
|
|
|
310
311
|
|
|
311
312
|
console.log();
|
|
312
313
|
} catch (error) {
|
|
313
|
-
console.error("Error:", error
|
|
314
|
+
console.error("Error:", isError(error) ? error.message : error);
|
|
314
315
|
process.exit(1);
|
|
315
316
|
}
|
|
316
317
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { mapValues } from "es-toolkit/object";
|
|
3
4
|
import { stringify as stringifyYaml } from "yaml";
|
|
4
5
|
import type { McpServer } from "../config/schema.js";
|
|
5
6
|
import { atomicWrite } from "../utils/atomic-write.js";
|
|
@@ -53,10 +54,8 @@ export class ContinueProvider extends BaseProvider {
|
|
|
53
54
|
private transformEnvVars(
|
|
54
55
|
obj: Record<string, string>
|
|
55
56
|
): Record<string, string> {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
return transformed;
|
|
57
|
+
return mapValues(obj, (value) =>
|
|
58
|
+
value.replace(/\$\{([^}]+)\}/g, "${{ secrets.$1 }}")
|
|
59
|
+
);
|
|
61
60
|
}
|
|
62
61
|
}
|
package/src/skills/validator.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { isPlainObject, isString } from "es-toolkit";
|
|
3
4
|
import { parse as parseYaml } from "yaml";
|
|
4
5
|
|
|
5
6
|
interface SkillFrontmatter {
|
|
@@ -72,7 +73,7 @@ export function validateSkill(skillPath: string): ValidationResult {
|
|
|
72
73
|
};
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
if (!parsed
|
|
76
|
+
if (!isPlainObject(parsed)) {
|
|
76
77
|
return {
|
|
77
78
|
valid: false,
|
|
78
79
|
error: "Frontmatter must be a YAML object",
|
|
@@ -81,7 +82,7 @@ export function validateSkill(skillPath: string): ValidationResult {
|
|
|
81
82
|
|
|
82
83
|
const frontmatter = parsed as SkillFrontmatter;
|
|
83
84
|
|
|
84
|
-
if (!frontmatter.name
|
|
85
|
+
if (!(frontmatter.name && isString(frontmatter.name))) {
|
|
85
86
|
return {
|
|
86
87
|
valid: false,
|
|
87
88
|
error: 'Frontmatter must contain a "name" field (string)',
|