@ema.co/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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +321 -0
  3. package/config.example.yaml +32 -0
  4. package/dist/cli/index.js +333 -0
  5. package/dist/config.js +136 -0
  6. package/dist/emaClient.js +398 -0
  7. package/dist/index.js +109 -0
  8. package/dist/mcp/handlers-consolidated.js +851 -0
  9. package/dist/mcp/index.js +15 -0
  10. package/dist/mcp/prompts.js +1753 -0
  11. package/dist/mcp/resources.js +624 -0
  12. package/dist/mcp/server.js +4723 -0
  13. package/dist/mcp/tools-consolidated.js +590 -0
  14. package/dist/mcp/tools-legacy.js +736 -0
  15. package/dist/models.js +8 -0
  16. package/dist/scheduler.js +21 -0
  17. package/dist/sdk/client.js +788 -0
  18. package/dist/sdk/config.js +136 -0
  19. package/dist/sdk/contracts.js +429 -0
  20. package/dist/sdk/generation-schema.js +189 -0
  21. package/dist/sdk/index.js +39 -0
  22. package/dist/sdk/knowledge.js +2780 -0
  23. package/dist/sdk/models.js +8 -0
  24. package/dist/sdk/state.js +88 -0
  25. package/dist/sdk/sync-options.js +216 -0
  26. package/dist/sdk/sync.js +220 -0
  27. package/dist/sdk/validation-rules.js +355 -0
  28. package/dist/sdk/workflow-generator.js +291 -0
  29. package/dist/sdk/workflow-intent.js +1585 -0
  30. package/dist/state.js +88 -0
  31. package/dist/sync.js +416 -0
  32. package/dist/syncOptions.js +216 -0
  33. package/dist/ui.js +334 -0
  34. package/docs/advisor-comms-assistant-fixes.md +175 -0
  35. package/docs/api-contracts.md +216 -0
  36. package/docs/auto-builder-analysis.md +271 -0
  37. package/docs/data-architecture.md +166 -0
  38. package/docs/ema-auto-builder-guide.html +394 -0
  39. package/docs/ema-user-guide.md +1121 -0
  40. package/docs/mcp-tools-guide.md +149 -0
  41. package/docs/naming-conventions.md +218 -0
  42. package/docs/tool-consolidation-proposal.md +427 -0
  43. package/package.json +98 -0
  44. package/resources/templates/chat-ai/README.md +119 -0
  45. package/resources/templates/chat-ai/persona-config.json +111 -0
  46. package/resources/templates/dashboard-ai/README.md +156 -0
  47. package/resources/templates/dashboard-ai/persona-config.json +180 -0
  48. package/resources/templates/voice-ai/README.md +123 -0
  49. package/resources/templates/voice-ai/persona-config.json +74 -0
  50. package/resources/templates/voice-ai/workflow-prompt.md +120 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2025 Ema Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,321 @@
1
+ # @ema.co/mcp-toolkit
2
+
3
+ MCP (Model Context Protocol) server for managing Ema AI Employees. Works with Cursor, Claude Desktop, and other MCP clients.
4
+
5
+ ## Installation
6
+
7
+ ### Option 1: npx (Recommended)
8
+
9
+ ```bash
10
+ # Run directly without installing
11
+ npx @ema.co/mcp-toolkit
12
+ ```
13
+
14
+ ### Option 2: Global Install
15
+
16
+ ```bash
17
+ npm install -g @ema.co/mcp-toolkit
18
+ ema-mcp # Start MCP server
19
+ ```
20
+
21
+ ### Option 3: Clone & Build (Development)
22
+
23
+ ```bash
24
+ git clone https://github.com/Ema-Unlimited/@ema.co/mcp-toolkit.git
25
+ cd @ema.co/mcp-toolkit
26
+ npm install
27
+ npm run build
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Quick Start
33
+
34
+ ### 1. Set Your Credentials
35
+
36
+ **Option A: API Key (Recommended)**
37
+
38
+ API keys are stable and auto-refresh (24h token validity):
39
+
40
+ ```bash
41
+ export EMA_API_KEY="your-api-key"
42
+ # Or for specific environment:
43
+ export EMA_PROD_API_KEY="your-prod-api-key"
44
+ ```
45
+
46
+ **Option B: Bearer Token**
47
+
48
+ ```bash
49
+ export EMA_BEARER_TOKEN="your-token"
50
+ # Or for specific environment:
51
+ export EMA_PROD_BEARER_TOKEN="your-prod-token"
52
+ ```
53
+
54
+ ### 2. Add to Your AI Assistant
55
+
56
+ **Cursor** (`~/.cursor/mcp.json`):
57
+
58
+ ```json
59
+ {
60
+ "mcpServers": {
61
+ "ema": {
62
+ "command": "npx",
63
+ "args": ["@ema.co/mcp-toolkit"],
64
+ "env": {
65
+ "EMA_API_KEY": "${EMA_API_KEY}"
66
+ }
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ > **Note**: Use `${ENV_VAR}` syntax to reference environment variables from your shell. Set them in your `~/.zshrc` or `~/.bashrc`.
73
+
74
+ **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "ema": {
80
+ "command": "npx",
81
+ "args": ["@ema.co/mcp-toolkit"],
82
+ "env": {
83
+ "EMA_API_KEY": "your-api-key-here"
84
+ }
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ ### 3. Start Using
91
+
92
+ Ask your AI assistant:
93
+ - "List my AI Employees"
94
+ - "Analyze the workflow for Customer Support Bot"
95
+ - "Create a new Voice AI for appointment scheduling"
96
+
97
+ ---
98
+
99
+ ## Authentication
100
+
101
+ ### Option 1: API Key (Recommended)
102
+
103
+ API keys provide stable authentication with automatic JWT refresh:
104
+
105
+ 1. Get your API key from the Ema team
106
+ 2. Set the environment variable:
107
+ ```bash
108
+ export EMA_API_KEY="your-api-key"
109
+ ```
110
+
111
+ The toolkit automatically:
112
+ - Exchanges the API key for a JWT token
113
+ - Caches the token (valid 24 hours)
114
+ - Refreshes before expiry
115
+
116
+ ### Option 2: Bearer Token
117
+
118
+ For quick testing or when API keys aren't available:
119
+
120
+ 1. Log in to [Ema Platform](https://app.ema.co)
121
+ 2. Open browser DevTools → Network tab
122
+ 3. Find any API request's `Authorization: Bearer ...` header
123
+ 4. Copy the token (without "Bearer " prefix)
124
+
125
+ ```bash
126
+ export EMA_BEARER_TOKEN="your-token"
127
+ ```
128
+
129
+ > **Note**: Bearer tokens expire after ~1 hour. Use API keys for production.
130
+
131
+ ### Multiple Environments
132
+
133
+ Use environment-specific credentials:
134
+
135
+ ```bash
136
+ # API keys (preferred)
137
+ export EMA_PROD_API_KEY="..."
138
+ export EMA_DEMO_API_KEY="..."
139
+ export EMA_DEV_API_KEY="..."
140
+
141
+ # Or bearer tokens
142
+ export EMA_PROD_BEARER_TOKEN="..."
143
+ export EMA_DEMO_BEARER_TOKEN="..."
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Configuration
149
+
150
+ ### Zero-Config Mode (Default)
151
+
152
+ No config file needed! The toolkit auto-detects environments from environment variables:
153
+
154
+ ```bash
155
+ # Pattern: EMA_<ENV>_API_KEY or EMA_<ENV>_BEARER_TOKEN
156
+ export EMA_PROD_API_KEY="..." # → creates "prod" environment
157
+ export EMA_DEMO_API_KEY="..." # → creates "demo" environment
158
+ export EMA_DEV_BEARER_TOKEN="..." # → creates "dev" environment
159
+ ```
160
+
161
+ **Default Environment**:
162
+ - External users: `prod`
163
+ - Ema employees: `demo` (auto-detected if demo token exists)
164
+ - Override: `export EMA_ENV_NAME="dev"`
165
+
166
+ **Well-known URLs** (automatic):
167
+ | Environment | URL |
168
+ |-------------|-----|
169
+ | `prod` | `https://api.ema.co` |
170
+ | `demo` | `https://api.demo.ema.co` |
171
+ | `dev` | `https://api.dev.ema.co` |
172
+ | `staging` | `https://api.staging.ema.co` |
173
+
174
+ ### Config File (Advanced)
175
+
176
+ For complex setups, create `ema.config.yaml`:
177
+
178
+ ```yaml
179
+ environments:
180
+ - name: prod
181
+ baseUrl: https://api.ema.co
182
+ bearerTokenEnv: EMA_PROD_BEARER_TOKEN
183
+ isMaster: true
184
+
185
+ - name: dev
186
+ baseUrl: https://api.dev.ema.co
187
+ bearerTokenEnv: EMA_DEV_BEARER_TOKEN
188
+ ```
189
+
190
+ ---
191
+
192
+ ## MCP Tools
193
+
194
+ | Tool | Purpose |
195
+ |------|---------|
196
+ | `env` | List available environments |
197
+ | `persona` | AI Employee management (get/list/create/update/compare) |
198
+ | `workflow` | Generate/analyze/deploy/optimize/explain/extend workflows |
199
+ | `action` | Agent lookup, docs, and recommendations |
200
+ | `template` | Patterns, widgets, qualifying questions |
201
+ | `knowledge` | Data sources + embedding (upload/list/delete/toggle) |
202
+ | `reference` | Concepts, guidance, validation, common mistakes |
203
+ | `sync` | Sync across environments |
204
+ | `demo` | Demo/RAG document utilities |
205
+
206
+ ## Dynamic Resources
207
+
208
+ | Resource | Source |
209
+ |----------|--------|
210
+ | `ema://catalog/agents` | Live from API |
211
+ | `ema://catalog/templates` | Live from API |
212
+ | `ema://catalog/patterns` | Workflow patterns |
213
+ | `ema://rules/anti-patterns` | Validation rules |
214
+
215
+ ---
216
+
217
+ ## CLI
218
+
219
+ ```bash
220
+ # List personas
221
+ ema personas list
222
+
223
+ # Sync a persona
224
+ ema sync persona "My Bot" --target dev --dry-run
225
+
226
+ # Check sync status
227
+ ema sync status
228
+ ```
229
+
230
+ ---
231
+
232
+ ## SDK
233
+
234
+ ```typescript
235
+ import { EmaClient } from "@ema.co/mcp-toolkit";
236
+
237
+ const client = new EmaClient({
238
+ name: "prod",
239
+ baseUrl: "https://api.ema.co",
240
+ bearerToken: process.env.EMA_PROD_BEARER_TOKEN!,
241
+ });
242
+
243
+ // List AI Employees
244
+ const personas = await client.getPersonasForTenant();
245
+
246
+ // Get full persona with workflow
247
+ const persona = await client.getPersonaById("uuid");
248
+
249
+ // List available templates
250
+ const templates = await client.getPersonaTemplates();
251
+
252
+ // List available agents/actions
253
+ const actions = await client.listActions();
254
+ ```
255
+
256
+ ---
257
+
258
+ ## Development
259
+
260
+ ```bash
261
+ # Build
262
+ npm run build
263
+
264
+ # Test
265
+ npm test
266
+
267
+ # Type check
268
+ npm run typecheck
269
+ ```
270
+
271
+ ---
272
+
273
+ ## Environment Variables
274
+
275
+ | Variable | Required | Description |
276
+ |----------|----------|-------------|
277
+ | `EMA_API_KEY` | Yes* | API key (auto-exchanges for JWT) |
278
+ | `EMA_BEARER_TOKEN` | Yes* | Bearer token (direct auth) |
279
+ | `EMA_<ENV>_API_KEY` | Yes* | API key for specific environment |
280
+ | `EMA_<ENV>_BEARER_TOKEN` | Yes* | Token for specific environment |
281
+ | `EMA_ENV_NAME` | No | Default environment name |
282
+ | `EMA_EMPLOYEE` | No | Set to "true" to default to demo |
283
+ | `EMA_AGENT_SYNC_CONFIG` | No | Path to config file |
284
+
285
+ *At least one credential required.
286
+
287
+ ---
288
+
289
+ ## Troubleshooting
290
+
291
+ ### "Missing token for environment X"
292
+
293
+ Set the corresponding environment variable:
294
+ ```bash
295
+ export EMA_X_API_KEY="your-api-key"
296
+ # or
297
+ export EMA_X_BEARER_TOKEN="your-token"
298
+ ```
299
+
300
+ ### "API key not yet initialized"
301
+
302
+ API keys are initialized asynchronously at startup. Either:
303
+ - Wait a moment and retry
304
+ - Use a bearer token instead
305
+
306
+ ### "Token expired" / 401 errors
307
+
308
+ - **API keys**: Should auto-refresh. Check your API key is valid.
309
+ - **Bearer tokens**: Expire after ~1 hour. Get a fresh token or switch to API keys.
310
+
311
+ ### MCP server not connecting
312
+
313
+ 1. Verify credentials are set: `echo $EMA_API_KEY`
314
+ 2. Try running manually: `npx @ema.co/mcp-toolkit`
315
+ 3. Check MCP config path is correct
316
+
317
+ ---
318
+
319
+ ## License
320
+
321
+ MIT - see [LICENSE](LICENSE)
@@ -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();