@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anaclumos/taal",
3
- "version": "1.1.8",
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"
@@ -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 configStrings = sources.map((s) => JSON.stringify(s.config));
99
- const uniqueConfigs = new Set(configStrings);
99
+ const uniqueConfigs = uniqWith(
100
+ sources.map((s) => s.config),
101
+ isEqual
102
+ );
100
103
 
101
- if (uniqueConfigs.size > 1) {
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 || typeof config !== "object") {
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 && typeof obj.url === "string") {
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 && typeof obj.command === "string") {
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 && typeof obj.env === "object") {
191
+ if (obj.env && isPlainObject(obj.env)) {
189
192
  server.env = obj.env as Record<string, string>;
190
- } else if (obj.environment && typeof obj.environment === "object") {
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 && typeof obj.environment === "object") {
208
+ if (obj.environment && isPlainObject(obj.environment)) {
206
209
  server.env = obj.environment as Record<string, string>;
207
210
  }
208
211
 
@@ -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 = JSON.stringify(
91
- sortObjectKeys(currentServers[key])
92
- );
93
- const newValue = JSON.stringify(
94
- sortObjectKeys(
95
- (transformedServers as Record<string, unknown>)[key]
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 instanceof Error ? error.message : String(error),
126
+ error: isError(error) ? error.message : String(error),
143
127
  };
144
128
  }
145
129
  }
@@ -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: ServerInfo[] = [];
41
- for (const [name, server] of Object.entries(config.mcp || {})) {
42
- if (server.url) {
43
- servers.push({
44
- name,
45
- type: "http",
46
- url: server.url,
47
- });
48
- } else if (server.command) {
49
- servers.push({
50
- name,
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 instanceof Error ? error.message : String(error),
74
+ error: isError(error) ? error.message : String(error),
78
75
  };
79
76
  }
80
77
  }
@@ -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 instanceof Error ? error.message : String(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 instanceof Error ? error.message : String(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 (typeof value === "string") {
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 && typeof value === "object") {
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 (typeof value === "string") {
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 && typeof value === "object") {
44
+ if (isPlainObject(value)) {
43
45
  return Object.fromEntries(
44
46
  Object.entries(value).map(([k, v]) => [k, substituteEnvVars(v)])
45
47
  );
@@ -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 instanceof Error ? error.message : 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 instanceof Error ? error.message : error}`,
52
+ `Failed to parse YAML: ${isError(error) ? error.message : String(error)}`,
52
53
  ],
53
54
  warnings,
54
55
  };
@@ -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(`Failed to read config file at ${path}: ${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(`Failed to parse YAML in ${path}: ${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
@@ -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 instanceof 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 instanceof Error ? error.message : 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 instanceof Error ? error.message : 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 instanceof Error ? error.message : 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 instanceof Error ? error.message : 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 instanceof Error ? error.message : 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 instanceof Error ? error.message : 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 instanceof Error ? error.message : 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
- 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;
57
+ return mapValues(obj, (value) =>
58
+ value.replace(/\$\{([^}]+)\}/g, "${{ secrets.$1 }}")
59
+ );
61
60
  }
62
61
  }
@@ -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 || typeof parsed !== "object") {
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 || typeof frontmatter.name !== "string") {
85
+ if (!(frontmatter.name && isString(frontmatter.name))) {
85
86
  return {
86
87
  valid: false,
87
88
  error: 'Frontmatter must contain a "name" field (string)',