ema-mcp-toolkit 0.2.0

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.
Files changed (49) hide show
  1. package/README.md +338 -0
  2. package/config.example.yaml +32 -0
  3. package/dist/cli/index.js +333 -0
  4. package/dist/config.js +136 -0
  5. package/dist/emaClient.js +398 -0
  6. package/dist/index.js +109 -0
  7. package/dist/mcp/handlers-consolidated.js +851 -0
  8. package/dist/mcp/index.js +15 -0
  9. package/dist/mcp/prompts.js +1753 -0
  10. package/dist/mcp/resources.js +624 -0
  11. package/dist/mcp/server.js +4585 -0
  12. package/dist/mcp/tools-consolidated.js +590 -0
  13. package/dist/mcp/tools-legacy.js +736 -0
  14. package/dist/models.js +8 -0
  15. package/dist/scheduler.js +21 -0
  16. package/dist/sdk/client.js +788 -0
  17. package/dist/sdk/config.js +136 -0
  18. package/dist/sdk/contracts.js +429 -0
  19. package/dist/sdk/generation-schema.js +189 -0
  20. package/dist/sdk/index.js +39 -0
  21. package/dist/sdk/knowledge.js +2780 -0
  22. package/dist/sdk/models.js +8 -0
  23. package/dist/sdk/state.js +88 -0
  24. package/dist/sdk/sync-options.js +216 -0
  25. package/dist/sdk/sync.js +220 -0
  26. package/dist/sdk/validation-rules.js +355 -0
  27. package/dist/sdk/workflow-generator.js +291 -0
  28. package/dist/sdk/workflow-intent.js +1585 -0
  29. package/dist/state.js +88 -0
  30. package/dist/sync.js +416 -0
  31. package/dist/syncOptions.js +216 -0
  32. package/dist/ui.js +334 -0
  33. package/docs/advisor-comms-assistant-fixes.md +175 -0
  34. package/docs/api-contracts.md +216 -0
  35. package/docs/auto-builder-analysis.md +271 -0
  36. package/docs/data-architecture.md +166 -0
  37. package/docs/ema-auto-builder-guide.html +394 -0
  38. package/docs/ema-user-guide.md +1121 -0
  39. package/docs/mcp-tools-guide.md +149 -0
  40. package/docs/naming-conventions.md +218 -0
  41. package/docs/tool-consolidation-proposal.md +427 -0
  42. package/package.json +95 -0
  43. package/resources/templates/chat-ai/README.md +119 -0
  44. package/resources/templates/chat-ai/persona-config.json +111 -0
  45. package/resources/templates/dashboard-ai/README.md +156 -0
  46. package/resources/templates/dashboard-ai/persona-config.json +180 -0
  47. package/resources/templates/voice-ai/README.md +123 -0
  48. package/resources/templates/voice-ai/persona-config.json +74 -0
  49. package/resources/templates/voice-ai/workflow-prompt.md +120 -0
package/README.md ADDED
@@ -0,0 +1,338 @@
1
+ # Ema MCP Toolkit
2
+
3
+ A comprehensive toolkit for managing Ema AI Employees via **MCP Server** (for AI assistants like Cursor/Claude), **CLI**, and **SDK**.
4
+
5
+ ## Installation
6
+
7
+ ### Option 1: npx (Recommended - No Installation)
8
+
9
+ ```bash
10
+ # Run directly without installing
11
+ npx ema-mcp-toolkit
12
+ ```
13
+
14
+ ### Option 2: Global Install
15
+
16
+ ```bash
17
+ npm install -g ema-mcp-toolkit
18
+ ema-mcp # Start MCP server
19
+ ema # CLI
20
+ ```
21
+
22
+ ### Option 3: Clone & Build (Development)
23
+
24
+ ```bash
25
+ git clone https://github.com/ema-co/ema-mcp-toolkit.git
26
+ cd ema-mcp-toolkit
27
+ npm install
28
+ npm run build
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Quick Start
34
+
35
+ ### 1. Set Your Token
36
+
37
+ ```bash
38
+ # Minimal setup - just one token for demo environment
39
+ export EMA_BEARER_TOKEN="your-token-here"
40
+
41
+ # Or use environment-specific naming (auto-detected)
42
+ export EMA_DEMO_BEARER_TOKEN="your-demo-token"
43
+ export EMA_DEV_BEARER_TOKEN="your-dev-token"
44
+ ```
45
+
46
+ ### 2. Add to Your AI Assistant
47
+
48
+ **Cursor** (`~/.cursor/mcp.json`):
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "ema": {
54
+ "command": "npx",
55
+ "args": ["ema-mcp-toolkit"],
56
+ "env": {
57
+ "EMA_DEMO_BEARER_TOKEN": "your-demo-token",
58
+ "EMA_DEV_BEARER_TOKEN": "your-dev-token"
59
+ }
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
66
+
67
+ ```json
68
+ {
69
+ "mcpServers": {
70
+ "ema": {
71
+ "command": "npx",
72
+ "args": ["ema-mcp-toolkit"],
73
+ "env": {
74
+ "EMA_DEMO_BEARER_TOKEN": "your-demo-token"
75
+ }
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### 3. Start Using
82
+
83
+ Ask your AI assistant:
84
+ - "List my AI Employees"
85
+ - "Analyze the workflow for Customer Support Bot"
86
+ - "Create a new Voice AI for appointment scheduling"
87
+
88
+ ---
89
+
90
+ ## Configuration
91
+
92
+ ### Zero-Config Mode (Recommended)
93
+
94
+ No config file needed! The toolkit auto-detects environments from environment variables:
95
+
96
+ ```bash
97
+ # Pattern: EMA_<ENV>_BEARER_TOKEN
98
+ export EMA_DEMO_BEARER_TOKEN="..." # → creates "demo" environment
99
+ export EMA_DEV_BEARER_TOKEN="..." # → creates "dev" environment
100
+ export EMA_STAGING_BEARER_TOKEN="..."# → creates "staging" environment
101
+ export EMA_PROD_BEARER_TOKEN="..." # → creates "prod" environment
102
+
103
+ # Optional: Custom URLs (auto-detected if not set)
104
+ export EMA_DEV_BASE_URL="https://api.custom-dev.ema.co"
105
+
106
+ # Optional: Set default environment
107
+ export EMA_ENV_NAME="dev"
108
+ ```
109
+
110
+ **Well-known URLs** (automatic):
111
+ | Environment | URL |
112
+ |-------------|-----|
113
+ | `demo` | `https://api.demo.ema.co` |
114
+ | `dev` | `https://api.dev.ema.co` |
115
+ | `staging` | `https://api.staging.ema.co` |
116
+ | `prod` | `https://api.ema.co` |
117
+
118
+ ### Config File (Advanced)
119
+
120
+ For complex setups, create `ema.config.yaml`:
121
+
122
+ ```yaml
123
+ environments:
124
+ - name: demo
125
+ baseUrl: https://api.demo.ema.co
126
+ bearerTokenEnv: EMA_DEMO_BEARER_TOKEN # References env var
127
+ isMaster: true
128
+
129
+ - name: dev
130
+ baseUrl: https://api.dev.ema.co
131
+ bearerTokenEnv: EMA_DEV_BEARER_TOKEN
132
+ ```
133
+
134
+ Then point to it:
135
+ ```bash
136
+ export EMA_AGENT_SYNC_CONFIG=./ema.config.yaml
137
+ ```
138
+
139
+ ### JSON Config via Environment (CI/CD)
140
+
141
+ For CI/CD, pass config as JSON:
142
+
143
+ ```bash
144
+ export EMA_CONFIG_JSON='{"environments":[{"name":"demo","baseUrl":"https://api.demo.ema.co","bearerTokenEnv":"EMA_DEMO_BEARER_TOKEN","isMaster":true}]}'
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Getting Your Token
150
+
151
+ 1. Log in to [Ema Platform](https://app.ema.co)
152
+ 2. Open browser DevTools → Network tab
153
+ 3. Make any API request and find the `Authorization: Bearer ...` header
154
+ 4. Copy the token (without "Bearer " prefix)
155
+
156
+ **Note**: Tokens expire. The toolkit supports automatic token refresh if you configure a refresh callback in the SDK.
157
+
158
+ ---
159
+
160
+ ## Usage
161
+
162
+ ### MCP Server (AI Assistants)
163
+
164
+ The MCP server provides **9 consolidated tools**, **15 prompts**, and **dynamic resources**.
165
+
166
+ #### Tools
167
+
168
+ | Tool | Purpose |
169
+ |------|---------|
170
+ | `env` | List available environments |
171
+ | `persona` | AI Employee management (get/list/create/update/compare) |
172
+ | `workflow` | Generate/analyze/deploy/optimize/explain/extend workflows |
173
+ | `action` | Agent lookup, docs, and recommendations |
174
+ | `template` | Patterns, widgets, qualifying questions |
175
+ | `knowledge` | Data sources + embedding (upload/list/delete/toggle) |
176
+ | `reference` | Concepts, guidance, validation, common mistakes |
177
+ | `sync` | Sync across environments |
178
+ | `demo` | Demo/RAG document utilities |
179
+
180
+ #### Resources (Dynamic)
181
+
182
+ | Resource | Source |
183
+ |----------|--------|
184
+ | `ema://catalog/agents` | Live from API |
185
+ | `ema://catalog/templates` | Live from API |
186
+ | `ema://catalog/patterns` | Workflow patterns |
187
+ | `ema://rules/anti-patterns` | Validation rules |
188
+
189
+ ### CLI
190
+
191
+ ```bash
192
+ # List personas
193
+ ema personas list
194
+
195
+ # Sync a persona
196
+ ema sync persona "My Bot" --target dev --dry-run
197
+
198
+ # Check sync status
199
+ ema sync status
200
+ ```
201
+
202
+ ### SDK (Programmatic)
203
+
204
+ ```typescript
205
+ import { EmaClient } from "ema-mcp-toolkit";
206
+
207
+ const client = new EmaClient({
208
+ name: "demo",
209
+ baseUrl: "https://api.demo.ema.co",
210
+ bearerToken: process.env.EMA_DEMO_BEARER_TOKEN!,
211
+ });
212
+
213
+ // List AI Employees
214
+ const personas = await client.getPersonasForTenant();
215
+
216
+ // Get full persona with workflow
217
+ const persona = await client.getPersonaById("uuid");
218
+
219
+ // List available templates
220
+ const templates = await client.getPersonaTemplates();
221
+
222
+ // List available agents/actions
223
+ const actions = await client.listActions();
224
+ ```
225
+
226
+ ---
227
+
228
+ ## Testing
229
+
230
+ ```bash
231
+ # Run all tests
232
+ npm test
233
+
234
+ # Run specific test
235
+ npm test -- --run workflow
236
+
237
+ # Type check
238
+ npm run typecheck
239
+
240
+ # Build
241
+ npm run build
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Development
247
+
248
+ ### Project Structure
249
+
250
+ ```
251
+ src/
252
+ ├── mcp/
253
+ │ ├── server.ts # MCP server entry
254
+ │ ├── prompts.ts # Prompt definitions
255
+ │ └── resources.ts # Resource registry
256
+ ├── sdk/
257
+ │ ├── client.ts # EmaClient API wrapper
258
+ │ ├── config.ts # Config loading
259
+ │ ├── models.ts # TypeScript types
260
+ │ └── knowledge.ts # Agent catalog, patterns
261
+ └── cli/
262
+ └── index.ts # CLI entry
263
+ ```
264
+
265
+ ### Adding Tools/Prompts
266
+
267
+ See `docs/naming-conventions.md` for naming rules.
268
+
269
+ ```typescript
270
+ // In src/mcp/prompts.ts
271
+ my_thing_action: {
272
+ definition: {
273
+ name: "my_thing_action", // {noun}_{action} pattern
274
+ description: "...",
275
+ arguments: [...],
276
+ },
277
+ render: (args) => [...],
278
+ },
279
+ ```
280
+
281
+ ### Auto-Fix Capabilities
282
+
283
+ The `workflow(mode="optimize")` can automatically fix:
284
+
285
+ | Issue | Fix |
286
+ |-------|-----|
287
+ | `wrong_input_source` (email_to) | Adds `entity_extraction`, wires email |
288
+ | `wrong_input_source` (query) | Rewires to correct trigger output |
289
+ | `missing_workflow_output` | Adds results mapping |
290
+
291
+ ---
292
+
293
+ ## Environment Variables Reference
294
+
295
+ | Variable | Required | Description |
296
+ |----------|----------|-------------|
297
+ | `EMA_BEARER_TOKEN` | Yes* | Default token (if no env-specific tokens) |
298
+ | `EMA_<ENV>_BEARER_TOKEN` | Yes* | Token for specific environment |
299
+ | `EMA_<ENV>_BASE_URL` | No | Custom URL (auto-detected) |
300
+ | `EMA_ENV_NAME` | No | Default environment name |
301
+ | `EMA_AGENT_SYNC_CONFIG` | No | Path to config file |
302
+ | `EMA_CONFIG_JSON` | No | Config as JSON string |
303
+ | `EMA_ENABLE_LEGACY_TOOLS` | No | Enable deprecated tools |
304
+
305
+ *At least one token required.
306
+
307
+ ---
308
+
309
+ ## Troubleshooting
310
+
311
+ ### "Missing token for environment X"
312
+
313
+ Set the corresponding environment variable:
314
+ ```bash
315
+ export EMA_X_BEARER_TOKEN="your-token"
316
+ ```
317
+
318
+ ### "Token expired" / 401 errors
319
+
320
+ Tokens expire after ~1 hour. Get a fresh token from the Ema dashboard.
321
+
322
+ ### MCP server not connecting
323
+
324
+ 1. Check your MCP config path is correct
325
+ 2. Verify the token env vars are set
326
+ 3. Try running manually: `npx ema-mcp-toolkit`
327
+
328
+ ### Tests failing
329
+
330
+ ```bash
331
+ npm run build && npm test
332
+ ```
333
+
334
+ ---
335
+
336
+ ## License
337
+
338
+ Proprietary - Ema Inc.
@@ -0,0 +1,32 @@
1
+ # Ema Toolkit Configuration Example
2
+ # Copy to ema.config.yaml and customize
3
+
4
+ environments:
5
+ - name: demo
6
+ baseUrl: https://api.demo.ema.co
7
+ bearerTokenEnv: EMA_DEMO_BEARER_TOKEN
8
+ isMaster: true
9
+
10
+ - name: dev
11
+ baseUrl: https://api.dev.ema.co
12
+ bearerTokenEnv: EMA_DEV_BEARER_TOKEN
13
+
14
+ # Add more environments as needed:
15
+ # - name: staging
16
+ # baseUrl: https://api.staging.ema.co
17
+ # bearerTokenEnv: EMA_STAGING_BEARER_TOKEN
18
+ #
19
+ # - name: prod
20
+ # baseUrl: https://api.ema.co
21
+ # bearerTokenEnv: EMA_PROD_BEARER_TOKEN
22
+
23
+ # Optional: Service mode settings (for central scheduler)
24
+ # service:
25
+ # stateDbPath: ./ema-state.sqlite3
26
+ # scheduler:
27
+ # intervalSeconds: 300
28
+ # # cron: "0 */4 * * *"
29
+
30
+ # Global settings
31
+ dryRun: false
32
+ verbose: true
@@ -0,0 +1,333 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Ema Agent Sync CLI
4
+ *
5
+ * Commands:
6
+ * sync run - Run a full sync
7
+ * sync status [name] - Check sync status for a persona
8
+ * sync persona <name> - Sync a specific persona by name
9
+ * personas list - List all personas from master
10
+ * agents list - List all agents (actions)
11
+ * config validate - Validate config file
12
+ */
13
+ import { loadConfig } from "../sdk/config.js";
14
+ import { EmaClient } from "../sdk/client.js";
15
+ import { SyncSDK } from "../sdk/sync.js";
16
+ function printUsage() {
17
+ console.log(`
18
+ Ema Agent Sync CLI
19
+
20
+ Usage: ema <command> [subcommand] [options]
21
+
22
+ Commands:
23
+ sync run Run a full sync from master to targets
24
+ sync status [persona-name] Check sync status (optionally for a specific persona)
25
+ sync persona <name> Sync a specific persona by name
26
+
27
+ personas list List all AI Employees from master environment
28
+ personas get <id> Get details of a specific AI Employee
29
+
30
+ agents list List all Agents (actions) from master environment
31
+
32
+ config validate [path] Validate a config file (default: ./config.yaml)
33
+
34
+ help Show this help message
35
+
36
+ Options:
37
+ --config, -c <path> Path to config file (default: ./config.yaml)
38
+ --dry-run Don't make actual changes
39
+ --json Output as JSON
40
+
41
+ Environment Variables:
42
+ EMA_AGENT_SYNC_CONFIG Path to config file
43
+ EMA_*_BEARER_TOKEN Bearer tokens for each environment (as configured)
44
+
45
+ Examples:
46
+ ema sync run
47
+ ema sync persona "My AI Employee"
48
+ ema personas list --json
49
+ ema agents list
50
+ ema config validate ./config.yaml
51
+ `);
52
+ }
53
+ function getEnvOrThrow(name) {
54
+ const v = process.env[name];
55
+ if (!v)
56
+ throw new Error(`Missing environment variable: ${name}`);
57
+ return v;
58
+ }
59
+ async function runSyncCommand(subcommand, args, options) {
60
+ const cfg = loadConfig(options.configPath);
61
+ if (options.dryRun)
62
+ cfg.dryRun = true;
63
+ const sdk = new SyncSDK(cfg);
64
+ try {
65
+ switch (subcommand) {
66
+ case "run": {
67
+ console.log("Starting sync...");
68
+ const result = await sdk.runSync();
69
+ if (options.json) {
70
+ console.log(JSON.stringify(result, null, 2));
71
+ }
72
+ else {
73
+ console.log(`\nSync complete:`);
74
+ console.log(` Run ID: ${result.runId}`);
75
+ console.log(` Scanned: ${result.scanned}`);
76
+ console.log(` Changed: ${result.changed}`);
77
+ console.log(` Synced: ${result.synced}`);
78
+ console.log(` Skipped: ${result.skipped}`);
79
+ }
80
+ break;
81
+ }
82
+ case "status": {
83
+ const personaName = args[0];
84
+ if (personaName) {
85
+ const persona = await sdk.getMasterPersonaByName(personaName);
86
+ if (!persona) {
87
+ console.error(`Persona not found: ${personaName}`);
88
+ process.exit(1);
89
+ }
90
+ const status = await sdk.getPersonaSyncStatus(persona.id);
91
+ if (options.json) {
92
+ console.log(JSON.stringify(status, null, 2));
93
+ }
94
+ else {
95
+ console.log(`Persona: ${status?.personaName} (${status?.personaId})`);
96
+ console.log(`Fingerprint: ${status?.fingerprint.substring(0, 16)}...`);
97
+ console.log(`In Sync: ${status?.isSynced ? "Yes" : "No"}`);
98
+ console.log(`\nTarget Mappings:`);
99
+ for (const m of status?.targetMappings ?? []) {
100
+ console.log(` ${m.targetEnv}: ${m.targetPersonaId} (${m.inSync ? "synced" : "out of sync"})`);
101
+ }
102
+ }
103
+ }
104
+ else {
105
+ // Show overall sync status
106
+ const master = sdk.getMasterEnvironment();
107
+ const personas = await sdk.listMasterPersonas();
108
+ console.log(`Master: ${master.name} (${master.baseUrl})`);
109
+ console.log(`Total personas: ${personas.length}`);
110
+ console.log(`\nTarget environments:`);
111
+ for (const env of sdk.getEnvironments()) {
112
+ if (!env.isMaster) {
113
+ console.log(` - ${env.name} (${env.baseUrl})`);
114
+ }
115
+ }
116
+ }
117
+ break;
118
+ }
119
+ case "persona": {
120
+ const personaName = args[0];
121
+ if (!personaName) {
122
+ console.error("Error: Persona name required");
123
+ console.error("Usage: ema sync persona <name>");
124
+ process.exit(1);
125
+ }
126
+ console.log(`Syncing persona: ${personaName}`);
127
+ const result = await sdk.syncPersonaByName(personaName);
128
+ if (options.json) {
129
+ console.log(JSON.stringify(result, null, 2));
130
+ }
131
+ else {
132
+ if (result.success) {
133
+ console.log(`✓ Synced to: ${result.synced.join(", ") || "already in sync"}`);
134
+ }
135
+ else {
136
+ console.error(`✗ Failed: ${result.errors.join(", ")}`);
137
+ process.exit(1);
138
+ }
139
+ }
140
+ break;
141
+ }
142
+ default:
143
+ console.error(`Unknown sync subcommand: ${subcommand}`);
144
+ process.exit(1);
145
+ }
146
+ }
147
+ finally {
148
+ sdk.close();
149
+ }
150
+ }
151
+ async function runPersonasCommand(subcommand, args, options) {
152
+ const cfg = loadConfig(options.configPath);
153
+ const sdk = new SyncSDK(cfg);
154
+ try {
155
+ switch (subcommand) {
156
+ case "list": {
157
+ const personas = await sdk.listMasterPersonas();
158
+ if (options.json) {
159
+ console.log(JSON.stringify(personas.map(p => ({
160
+ id: p.id,
161
+ name: p.name,
162
+ description: p.description,
163
+ status: p.status,
164
+ template_id: p.template_id,
165
+ workflow_id: p.workflow_id,
166
+ })), null, 2));
167
+ }
168
+ else {
169
+ console.log(`AI Employees (${personas.length}):\n`);
170
+ for (const p of personas) {
171
+ console.log(` ${p.name || "(unnamed)"}`);
172
+ console.log(` ID: ${p.id}`);
173
+ if (p.description)
174
+ console.log(` Description: ${p.description.substring(0, 60)}...`);
175
+ console.log("");
176
+ }
177
+ }
178
+ break;
179
+ }
180
+ case "get": {
181
+ const id = args[0];
182
+ if (!id) {
183
+ console.error("Error: Persona ID required");
184
+ process.exit(1);
185
+ }
186
+ const persona = await sdk.getMasterPersona(id);
187
+ if (!persona) {
188
+ console.error(`Persona not found: ${id}`);
189
+ process.exit(1);
190
+ }
191
+ if (options.json) {
192
+ console.log(JSON.stringify(persona, null, 2));
193
+ }
194
+ else {
195
+ console.log(`Name: ${persona.name}`);
196
+ console.log(`ID: ${persona.id}`);
197
+ console.log(`Description: ${persona.description}`);
198
+ console.log(`Status: ${persona.status}`);
199
+ console.log(`Template ID: ${persona.template_id}`);
200
+ console.log(`Workflow ID: ${persona.workflow_id}`);
201
+ }
202
+ break;
203
+ }
204
+ default:
205
+ console.error(`Unknown personas subcommand: ${subcommand}`);
206
+ process.exit(1);
207
+ }
208
+ }
209
+ finally {
210
+ sdk.close();
211
+ }
212
+ }
213
+ async function runAgentsCommand(subcommand, args, options) {
214
+ const cfg = loadConfig(options.configPath);
215
+ const master = cfg.environments.find((e) => e.isMaster);
216
+ if (!master)
217
+ throw new Error("No master environment configured");
218
+ const env = {
219
+ name: master.name,
220
+ baseUrl: master.baseUrl,
221
+ bearerToken: getEnvOrThrow(master.bearerTokenEnv),
222
+ };
223
+ const client = new EmaClient(env);
224
+ switch (subcommand) {
225
+ case "list": {
226
+ const actions = await client.listAgents();
227
+ if (options.json) {
228
+ console.log(JSON.stringify(actions, null, 2));
229
+ }
230
+ else {
231
+ console.log(`Agents (${actions.length}):\n`);
232
+ for (const a of actions) {
233
+ console.log(` ${a.name || a.id}`);
234
+ if (a.description)
235
+ console.log(` ${a.description.substring(0, 60)}...`);
236
+ if (a.category)
237
+ console.log(` Category: ${a.category}`);
238
+ console.log("");
239
+ }
240
+ }
241
+ break;
242
+ }
243
+ default:
244
+ console.error(`Unknown agents subcommand: ${subcommand}`);
245
+ process.exit(1);
246
+ }
247
+ }
248
+ async function runConfigCommand(subcommand, args, options) {
249
+ switch (subcommand) {
250
+ case "validate": {
251
+ const path = args[0] || "./config.yaml";
252
+ try {
253
+ const cfg = loadConfig(path);
254
+ if (options.json) {
255
+ console.log(JSON.stringify({ valid: true, config: cfg }, null, 2));
256
+ }
257
+ else {
258
+ console.log(`✓ Config is valid: ${path}`);
259
+ console.log(` Master: ${cfg.environments.find((e) => e.isMaster)?.name}`);
260
+ console.log(` Environments: ${cfg.environments.map((e) => e.name).join(", ")}`);
261
+ console.log(` Routing rules: ${cfg.routing?.length ?? 0}`);
262
+ console.log(` Dry run: ${cfg.dryRun}`);
263
+ }
264
+ }
265
+ catch (e) {
266
+ if (options.json) {
267
+ console.log(JSON.stringify({ valid: false, error: String(e) }, null, 2));
268
+ }
269
+ else {
270
+ console.error(`✗ Invalid config: ${e instanceof Error ? e.message : e}`);
271
+ }
272
+ process.exit(1);
273
+ }
274
+ break;
275
+ }
276
+ default:
277
+ console.error(`Unknown config subcommand: ${subcommand}`);
278
+ process.exit(1);
279
+ }
280
+ }
281
+ async function main() {
282
+ const args = process.argv.slice(2);
283
+ // Parse options
284
+ let configPath = process.env.EMA_AGENT_SYNC_CONFIG || "./config.yaml";
285
+ let dryRun = false;
286
+ let json = false;
287
+ const filteredArgs = [];
288
+ for (let i = 0; i < args.length; i++) {
289
+ const arg = args[i];
290
+ if (arg === "--config" || arg === "-c") {
291
+ configPath = args[++i];
292
+ }
293
+ else if (arg === "--dry-run") {
294
+ dryRun = true;
295
+ }
296
+ else if (arg === "--json") {
297
+ json = true;
298
+ }
299
+ else {
300
+ filteredArgs.push(arg);
301
+ }
302
+ }
303
+ const [command, subcommand, ...restArgs] = filteredArgs;
304
+ if (!command || command === "help" || command === "--help" || command === "-h") {
305
+ printUsage();
306
+ return;
307
+ }
308
+ try {
309
+ switch (command) {
310
+ case "sync":
311
+ await runSyncCommand((subcommand || "run"), restArgs, { configPath, dryRun, json });
312
+ break;
313
+ case "personas":
314
+ await runPersonasCommand((subcommand || "list"), restArgs, { configPath, json });
315
+ break;
316
+ case "agents":
317
+ await runAgentsCommand((subcommand || "list"), restArgs, { configPath, json });
318
+ break;
319
+ case "config":
320
+ await runConfigCommand((subcommand || "validate"), restArgs, { json });
321
+ break;
322
+ default:
323
+ console.error(`Unknown command: ${command}`);
324
+ printUsage();
325
+ process.exit(1);
326
+ }
327
+ }
328
+ catch (e) {
329
+ console.error(`Error: ${e instanceof Error ? e.message : e}`);
330
+ process.exit(1);
331
+ }
332
+ }
333
+ main();