@ema.co/mcp-toolkit 1.4.0 → 1.4.3

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.

Potentially problematic release.


This version of @ema.co/mcp-toolkit might be problematic. Click here for more details.

@@ -1,216 +0,0 @@
1
- /**
2
- * Sync Options Configuration
3
- *
4
- * Hierarchical configuration for sync behavior, loaded from:
5
- * 1. .ema.yaml in repo root (shared team defaults)
6
- * 2. ~/.ema.yaml in user home (personal overrides)
7
- * 3. Tool parameters (runtime overrides)
8
- *
9
- * Resolution order (lowest → highest priority):
10
- * Built-in defaults → defaults → targets[env] → personas[name] → tool params
11
- */
12
- import fs from "node:fs";
13
- import path from "node:path";
14
- import os from "node:os";
15
- import yaml from "js-yaml";
16
- import { z } from "zod";
17
- // ─────────────────────────────────────────────────────────────────────────────
18
- // Schema
19
- // ─────────────────────────────────────────────────────────────────────────────
20
- const SyncBehaviorSchema = z.object({
21
- sync_status: z.boolean().optional(),
22
- dry_run: z.boolean().optional(),
23
- }).strict();
24
- const RoutingRuleSchema = z.object({
25
- match: z.object({
26
- names: z.array(z.string()).optional().default([]),
27
- ids: z.array(z.string()).optional().default([]),
28
- }).strict(),
29
- targets: z.array(z.string()).min(1),
30
- }).strict();
31
- const SyncOptionsConfigSchema = z.object({
32
- defaults: SyncBehaviorSchema.optional().default({}),
33
- targets: z.record(SyncBehaviorSchema).optional().default({}),
34
- personas: z.record(SyncBehaviorSchema).optional().default({}),
35
- routing: z.array(RoutingRuleSchema).optional().default([]),
36
- }).strict();
37
- // ─────────────────────────────────────────────────────────────────────────────
38
- // Built-in defaults
39
- // ─────────────────────────────────────────────────────────────────────────────
40
- const BUILTIN_DEFAULTS = {
41
- sync_status: false,
42
- dry_run: false,
43
- };
44
- // ─────────────────────────────────────────────────────────────────────────────
45
- // Config loading
46
- // ─────────────────────────────────────────────────────────────────────────────
47
- let cachedSyncOptions = null;
48
- function loadYamlFile(filePath) {
49
- try {
50
- const content = fs.readFileSync(filePath, "utf8");
51
- return yaml.load(content);
52
- }
53
- catch {
54
- return null;
55
- }
56
- }
57
- function findRepoRoot() {
58
- // Start from current working directory and look for .git or package.json
59
- let dir = process.cwd();
60
- while (dir !== path.dirname(dir)) {
61
- if (fs.existsSync(path.join(dir, ".git")) || fs.existsSync(path.join(dir, "package.json"))) {
62
- return dir;
63
- }
64
- dir = path.dirname(dir);
65
- }
66
- return process.cwd();
67
- }
68
- /**
69
- * Load and merge sync options from all config sources.
70
- * Caches result for performance.
71
- */
72
- export function loadSyncOptions() {
73
- if (cachedSyncOptions)
74
- return cachedSyncOptions;
75
- const repoRoot = findRepoRoot();
76
- const homeDir = os.homedir();
77
- // Load from repo .ema.yaml
78
- const repoConfigPath = path.join(repoRoot, ".ema.yaml");
79
- const repoConfig = loadYamlFile(repoConfigPath);
80
- // Load from ~/.ema.yaml
81
- const userConfigPath = path.join(homeDir, ".ema.yaml");
82
- const userConfig = loadYamlFile(userConfigPath);
83
- // Merge configs (user overrides repo)
84
- const merged = {
85
- defaults: {},
86
- targets: {},
87
- personas: {},
88
- routing: [],
89
- };
90
- // Apply repo config
91
- if (repoConfig && typeof repoConfig === "object") {
92
- const parsed = SyncOptionsConfigSchema.safeParse(repoConfig);
93
- if (parsed.success) {
94
- merged.defaults = { ...merged.defaults, ...parsed.data.defaults };
95
- merged.targets = { ...merged.targets, ...parsed.data.targets };
96
- merged.personas = { ...merged.personas, ...parsed.data.personas };
97
- merged.routing = [...(parsed.data.routing ?? [])];
98
- }
99
- }
100
- // Apply user config (overrides repo)
101
- if (userConfig && typeof userConfig === "object") {
102
- const parsed = SyncOptionsConfigSchema.safeParse(userConfig);
103
- if (parsed.success) {
104
- merged.defaults = { ...merged.defaults, ...parsed.data.defaults };
105
- // Deep merge targets
106
- for (const [env, behavior] of Object.entries(parsed.data.targets ?? {})) {
107
- merged.targets[env] = { ...merged.targets[env], ...behavior };
108
- }
109
- // Deep merge personas
110
- for (const [name, behavior] of Object.entries(parsed.data.personas ?? {})) {
111
- merged.personas[name] = { ...merged.personas[name], ...behavior };
112
- }
113
- // Prepend user routing rules (higher priority)
114
- merged.routing = [...(parsed.data.routing ?? []), ...merged.routing];
115
- }
116
- }
117
- cachedSyncOptions = merged;
118
- return merged;
119
- }
120
- /**
121
- * Clear the cached sync options (for testing or config reload).
122
- */
123
- export function clearSyncOptionsCache() {
124
- cachedSyncOptions = null;
125
- }
126
- /**
127
- * Resolve sync behavior for a specific persona and target environment.
128
- *
129
- * Resolution order (lowest → highest priority):
130
- * 1. Built-in defaults
131
- * 2. Config defaults
132
- * 3. Config targets[targetEnv]
133
- * 4. Config personas[personaName]
134
- * 5. Runtime overrides (passed as parameter)
135
- */
136
- export function resolveSyncBehavior(opts) {
137
- const { personaName, targetEnv, overrides } = opts;
138
- const config = loadSyncOptions();
139
- // Start with built-in defaults
140
- const resolved = { ...BUILTIN_DEFAULTS };
141
- // Apply config defaults
142
- if (config.defaults) {
143
- if (config.defaults.sync_status !== undefined)
144
- resolved.sync_status = config.defaults.sync_status;
145
- if (config.defaults.dry_run !== undefined)
146
- resolved.dry_run = config.defaults.dry_run;
147
- }
148
- // Apply target env overrides
149
- const targetConfig = config.targets?.[targetEnv];
150
- if (targetConfig) {
151
- if (targetConfig.sync_status !== undefined)
152
- resolved.sync_status = targetConfig.sync_status;
153
- if (targetConfig.dry_run !== undefined)
154
- resolved.dry_run = targetConfig.dry_run;
155
- }
156
- // Apply persona overrides
157
- if (personaName) {
158
- const personaConfig = config.personas?.[personaName];
159
- if (personaConfig) {
160
- if (personaConfig.sync_status !== undefined)
161
- resolved.sync_status = personaConfig.sync_status;
162
- if (personaConfig.dry_run !== undefined)
163
- resolved.dry_run = personaConfig.dry_run;
164
- }
165
- }
166
- // Apply runtime overrides
167
- if (overrides) {
168
- if (overrides.sync_status !== undefined)
169
- resolved.sync_status = overrides.sync_status;
170
- if (overrides.dry_run !== undefined)
171
- resolved.dry_run = overrides.dry_run;
172
- }
173
- return resolved;
174
- }
175
- // ─────────────────────────────────────────────────────────────────────────────
176
- // Routing
177
- // ─────────────────────────────────────────────────────────────────────────────
178
- /**
179
- * Check if a string matches a glob pattern (simple implementation).
180
- * Supports * as wildcard.
181
- */
182
- function matchGlob(pattern, value) {
183
- const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
184
- return regex.test(value);
185
- }
186
- /**
187
- * Get target environments for a persona based on routing rules.
188
- */
189
- export function getRoutingTargets(personaName, personaId) {
190
- const config = loadSyncOptions();
191
- const targets = new Set();
192
- for (const rule of config.routing ?? []) {
193
- // Check name patterns
194
- for (const pattern of rule.match.names ?? []) {
195
- if (matchGlob(pattern, personaName)) {
196
- rule.targets.forEach((t) => targets.add(t));
197
- break;
198
- }
199
- }
200
- // Check IDs
201
- if (rule.match.ids?.includes(personaId)) {
202
- rule.targets.forEach((t) => targets.add(t));
203
- }
204
- }
205
- return [...targets];
206
- }
207
- /**
208
- * Validate sync options config.
209
- */
210
- export function validateSyncOptions(input) {
211
- const result = SyncOptionsConfigSchema.safeParse(input);
212
- if (!result.success) {
213
- return { ok: false, errors: result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`) };
214
- }
215
- return { ok: true, config: result.data };
216
- }