@ema.co/mcp-toolkit 2026.1.21 → 2026.1.24

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.

Files changed (48) hide show
  1. package/README.md +33 -9
  2. package/dist/mcp/handlers/action/index.js +148 -0
  3. package/dist/mcp/handlers/action-executor.js +350 -0
  4. package/dist/mcp/handlers/data/dashboard-clone.js +352 -0
  5. package/dist/mcp/handlers/data/index.js +381 -0
  6. package/dist/mcp/handlers/env/index.js +44 -0
  7. package/dist/mcp/handlers/index.js +23 -2
  8. package/dist/mcp/handlers/knowledge/index.js +247 -0
  9. package/dist/mcp/handlers/persona/analyze.js +18 -0
  10. package/dist/mcp/handlers/persona/create.js +94 -8
  11. package/dist/mcp/handlers/persona/get.js +3 -2
  12. package/dist/mcp/handlers/persona/sanitize.js +12 -2
  13. package/dist/mcp/handlers/persona/update.js +17 -7
  14. package/dist/mcp/handlers/reference/index.js +291 -0
  15. package/dist/mcp/handlers/sync/index.js +129 -0
  16. package/dist/mcp/handlers/template/index.js +61 -0
  17. package/dist/mcp/handlers/utils.js +15 -6
  18. package/dist/mcp/handlers-consolidated.js +379 -1081
  19. package/dist/mcp/prompts.js +6 -3
  20. package/dist/mcp/resources.js +55 -6
  21. package/dist/mcp/server.js +198 -90
  22. package/dist/mcp/tools-consolidated.js +5 -2
  23. package/dist/mcp/tools-v2.js +335 -153
  24. package/dist/mcp/workflow-operations.js +100 -0
  25. package/dist/sdk/generation-schema.js +5 -5
  26. package/dist/sdk/guidance.js +560 -0
  27. package/dist/sdk/index.js +7 -0
  28. package/dist/sdk/knowledge.js +145 -135
  29. package/dist/sdk/sanitizer.js +174 -2
  30. package/dist/sdk/structural-rules.js +1 -1
  31. package/dist/sdk/validation-rules.js +1 -1
  32. package/dist/sdk/workflow-generator.js +29 -3
  33. package/dist/sdk/workflow-transformer.js +47 -2
  34. package/docs/CODEBASE-ANALYSIS-2026-01-23.md +936 -0
  35. package/docs/CODEBASE-ANALYSIS-PRIORITIZED.md +774 -0
  36. package/docs/dashboard-operations.md +246 -0
  37. package/docs/lessons-learned.md +30 -0
  38. package/docs/mcp-tools-guide.md +68 -6
  39. package/docs/migration/action-composition-migration.md +270 -0
  40. package/docs/naming-conventions.md +85 -25
  41. package/docs/proposals/action-composition.md +490 -0
  42. package/docs/proposals/explicit-method-restructure.md +328 -0
  43. package/docs/proposals/self-contained-guidance.md +427 -0
  44. package/docs/release-impact.md +102 -0
  45. package/package.json +7 -2
  46. package/resources/templates/auto-builder-rules.md +8 -6
  47. package/resources/templates/demo-scenarios/test-published-package.md +2 -2
  48. package/resources/templates/voice-ai/workflow-prompt.md +11 -10
package/README.md CHANGED
@@ -7,7 +7,7 @@ MCP (Model Context Protocol) server for managing Ema AI Employees. Works with Cu
7
7
  ### Option 1: npx (Recommended)
8
8
 
9
9
  ```bash
10
- npx @ema.co/mcp-toolkit
10
+ npx -y @ema.co/mcp-toolkit@latest
11
11
  ```
12
12
 
13
13
  ### Option 2: Global Install
@@ -63,7 +63,7 @@ Then reload: `source ~/.zshrc`
63
63
  "mcpServers": {
64
64
  "ema": {
65
65
  "command": "npx",
66
- "args": ["@ema.co/mcp-toolkit"],
66
+ "args": ["-y", "@ema.co/mcp-toolkit@latest"],
67
67
  "env": {
68
68
  "EMA_ENV_NAME": "demo",
69
69
  "EMA_PROD_BEARER_TOKEN": "${env:EMA_PROD_BEARER_TOKEN}",
@@ -77,6 +77,8 @@ Then reload: `source ~/.zshrc`
77
77
  ```
78
78
 
79
79
  > **Important**:
80
+ > - Use `@latest` to always get the newest version (npx caches aggressively without it)
81
+ > - The `-y` flag auto-confirms the npx install prompt
80
82
  > - Use `${env:VAR_NAME}` syntax to reference shell environment variables
81
83
  > - After changing mcp.json, restart the MCP server: `Cmd+Shift+P` → "MCP: Restart Server"
82
84
  > - After changing ~/.zshrc, reload it AND restart Cursor for changes to take effect
@@ -88,7 +90,7 @@ Then reload: `source ~/.zshrc`
88
90
  "mcpServers": {
89
91
  "ema": {
90
92
  "command": "npx",
91
- "args": ["@ema.co/mcp-toolkit"],
93
+ "args": ["-y", "@ema.co/mcp-toolkit@latest"],
92
94
  "env": {
93
95
  "EMA_ENV_NAME": "demo",
94
96
  "EMA_DEMO_BEARER_TOKEN": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
@@ -238,13 +240,35 @@ reference(type="patterns", pattern="intent-routing") // Get pattern
238
240
 
239
241
  ## Dynamic Resources
240
242
 
241
- | Resource | Source |
242
- |----------|--------|
243
- | `ema://catalog/agents` | Live from API |
244
- | `ema://catalog/templates` | Live from API |
243
+ | Resource | Purpose |
244
+ |----------|---------|
245
+ | `ema://catalog/agents` | Live action catalog from API |
246
+ | `ema://catalog/templates` | Persona templates from API |
245
247
  | `ema://catalog/patterns` | Workflow patterns |
246
- | `ema://rules/anti-patterns` | Validation rules |
247
- | `ema://rules/mcp-usage` | MCP usage guide for agents |
248
+ | `ema://docs/usage-guide` | Complete usage guide (generated) |
249
+ | `ema://guidance/rules` | Structured rules as JSON |
250
+ | `ema://guidance/cursor-rule` | Export as Cursor .mdc rule |
251
+ | `ema://guidance/server-instructions` | Server instructions text |
252
+
253
+ ---
254
+
255
+ ## Self-Contained Guidance
256
+
257
+ The MCP is fully self-contained—no external configuration files needed.
258
+
259
+ **How it works:**
260
+ - Server instructions are injected on MCP init (system prompt)
261
+ - Tools include usage tips in their descriptions
262
+ - Responses include `_tip` and `_next_step` fields with contextual guidance
263
+ - `env()` returns a getting_started guide with workflow patterns
264
+ - `persona(..., analyze=true)` includes workflow_guidance with state-specific tips
265
+
266
+ **For other services:**
267
+ - Read `ema://guidance/rules` (JSON) for programmatic consumption
268
+ - Read `ema://docs/usage-guide` (markdown) for documentation
269
+ - Read `ema://guidance/cursor-rule` (.mdc) for IDE integration
270
+
271
+ All guidance flows from a single source (`src/sdk/guidance.ts`).
248
272
 
249
273
  ---
250
274
 
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Action Handler
3
+ *
4
+ * Provides action/agent lookup, filtering, and documentation.
5
+ */
6
+ import { AGENT_CATALOG, getAgentByName, suggestAgentsForUseCase, } from "../../../sdk/knowledge.js";
7
+ // Deprecated param mappings for backwards compatibility
8
+ const DEPRECATED_PARAMS = {
9
+ identifier: { newName: "id", message: "'identifier' is deprecated, use 'id' instead (will be removed in v2.0.0)" },
10
+ };
11
+ function checkDeprecatedParams(args) {
12
+ const warnings = [];
13
+ for (const [oldName, info] of Object.entries(DEPRECATED_PARAMS)) {
14
+ if (args[oldName] !== undefined) {
15
+ warnings.push(info.message);
16
+ }
17
+ }
18
+ return warnings;
19
+ }
20
+ /**
21
+ * Handle action tool requests - list, get, filter, and search actions
22
+ */
23
+ export async function handleAction(args, client) {
24
+ // Check for deprecated params and log warnings
25
+ const deprecationWarnings = checkDeprecatedParams(args);
26
+ for (const warning of deprecationWarnings) {
27
+ console.warn(`[action] Deprecation: ${warning}`);
28
+ }
29
+ const id = args.id;
30
+ const identifier = args.identifier; // deprecated alias for 'id'
31
+ const idOrName = id ?? identifier;
32
+ // Categories list
33
+ if (args.categories) {
34
+ const categories = [...new Set(Object.values(AGENT_CATALOG).map(a => a.category))];
35
+ return { categories, count: categories.length };
36
+ }
37
+ // Suggest for use case
38
+ if (args.suggest) {
39
+ const suggestions = suggestAgentsForUseCase(args.suggest);
40
+ return { suggestions, use_case: args.suggest };
41
+ }
42
+ // Get single action
43
+ if (idOrName) {
44
+ // Try API first
45
+ try {
46
+ const actions = await client.listActions();
47
+ const action = actions.find(a => a.id === idOrName ||
48
+ a.name === idOrName ||
49
+ a.name?.toLowerCase() === idOrName.toLowerCase());
50
+ const result = action ? {
51
+ id: action.id,
52
+ name: action.name,
53
+ category: action.category,
54
+ enabled: action.enabled,
55
+ inputs: action.inputs,
56
+ outputs: action.outputs,
57
+ source: "api",
58
+ } : {};
59
+ // Include docs if requested or if not found in API
60
+ if (args.include_docs || !action) {
61
+ const doc = getAgentByName(idOrName);
62
+ if (doc) {
63
+ result.documentation = doc;
64
+ result.source = action ? "api+docs" : "docs";
65
+ }
66
+ }
67
+ if (Object.keys(result).length === 0) {
68
+ return { error: `Action not found: ${idOrName}` };
69
+ }
70
+ return result;
71
+ }
72
+ catch {
73
+ // Fallback to docs only
74
+ const doc = getAgentByName(idOrName);
75
+ if (doc) {
76
+ return { ...doc, source: "docs" };
77
+ }
78
+ return { error: `Action not found: ${idOrName}` };
79
+ }
80
+ }
81
+ // List actions
82
+ try {
83
+ let actions = await client.listActions();
84
+ // Apply filters
85
+ if (args.query) {
86
+ const q = args.query.toLowerCase();
87
+ actions = actions.filter(a => a.name?.toLowerCase().includes(q));
88
+ }
89
+ if (args.category) {
90
+ actions = actions.filter(a => a.category === args.category);
91
+ }
92
+ if (args.enabled !== undefined) {
93
+ actions = actions.filter(a => a.enabled === args.enabled);
94
+ }
95
+ // Filter by persona workflow
96
+ if (args.persona_id) {
97
+ const persona = await client.getPersonaById(args.persona_id);
98
+ if (persona?.workflow_id) {
99
+ const workflowActionIds = await client.listActionsFromWorkflow(persona.workflow_id);
100
+ const actionIdSet = new Set(workflowActionIds);
101
+ actions = actions.filter(a => actionIdSet.has(a.id));
102
+ }
103
+ }
104
+ // Apply limit
105
+ const limit = args.limit || 100;
106
+ actions = actions.slice(0, limit);
107
+ // Include docs if requested
108
+ if (args.include_docs) {
109
+ return {
110
+ count: actions.length,
111
+ actions: actions.map(a => ({
112
+ ...a,
113
+ documentation: a.name ? getAgentByName(a.name) : undefined,
114
+ })),
115
+ };
116
+ }
117
+ return {
118
+ count: actions.length,
119
+ actions: actions.map(a => ({
120
+ id: a.id,
121
+ name: a.name,
122
+ category: a.category,
123
+ enabled: a.enabled,
124
+ })),
125
+ };
126
+ }
127
+ catch (e) {
128
+ // Fallback to catalog only
129
+ let agents = Object.entries(AGENT_CATALOG);
130
+ if (args.category) {
131
+ agents = agents.filter(([_, a]) => a.category === args.category);
132
+ }
133
+ if (args.query) {
134
+ const q = args.query.toLowerCase();
135
+ agents = agents.filter(([name]) => name.toLowerCase().includes(q));
136
+ }
137
+ return {
138
+ count: agents.length,
139
+ actions: agents.map(([name, agent]) => ({
140
+ name,
141
+ category: agent.category,
142
+ description: agent.description,
143
+ source: "catalog",
144
+ })),
145
+ note: "Live API unavailable, showing catalog data",
146
+ };
147
+ }
148
+ }
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Action Executor - Executes composable action sequences
3
+ *
4
+ * Replaces flag-based parameters with explicit action composition.
5
+ *
6
+ * Example:
7
+ * ```
8
+ * persona(
9
+ * method="create",
10
+ * from="source-id",
11
+ * name="Clone",
12
+ * actions=[
13
+ * {tool:"data", args:{method:"copy", from:"$source"}},
14
+ * {tool:"data", args:{method:"sanitize", examples:["Acme"]}},
15
+ * {tool:"snapshot", args:{message:"Clone ready"}},
16
+ * ]
17
+ * )
18
+ * ```
19
+ *
20
+ * Context variables:
21
+ * - $source: The `from` parameter (template/persona ID being cloned)
22
+ * - $target: The ID of the created/modified persona
23
+ * - $env: Current environment name
24
+ */
25
+ // ─────────────────────────────────────────────────────────────────────────────
26
+ // Action Aliases
27
+ // ─────────────────────────────────────────────────────────────────────────────
28
+ /**
29
+ * Built-in action aliases for common patterns
30
+ */
31
+ export const ACTION_ALIASES = {
32
+ // Copy data from source to target
33
+ "copy-data": [
34
+ { tool: "data", args: { method: "copy", from: "$source" } },
35
+ ],
36
+ // Copy and sanitize data
37
+ "copy-and-sanitize": [
38
+ { tool: "data", args: { method: "copy", from: "$source" } },
39
+ { tool: "data", args: { method: "sanitize" } },
40
+ ],
41
+ // Standard demo setup: copy, sanitize, snapshot
42
+ "standard-demo-setup": [
43
+ { tool: "data", args: { method: "copy", from: "$source" } },
44
+ { tool: "data", args: { method: "sanitize" } },
45
+ { tool: "snapshot", args: { message: "Demo setup complete" } },
46
+ ],
47
+ // Snapshot only
48
+ "create-snapshot": [
49
+ { tool: "snapshot", args: { message: "Checkpoint" } },
50
+ ],
51
+ // Validate workflow
52
+ "validate-workflow": [
53
+ { tool: "validate", args: { checks: ["workflow"] } },
54
+ ],
55
+ };
56
+ // ─────────────────────────────────────────────────────────────────────────────
57
+ // Context Substitution
58
+ // ─────────────────────────────────────────────────────────────────────────────
59
+ /**
60
+ * Substitute context variables in action args
61
+ *
62
+ * Variables:
63
+ * - $source → context.source
64
+ * - $target → context.target
65
+ * - $env → context.env
66
+ */
67
+ function substituteContext(action, context) {
68
+ // Deep clone args to avoid mutation
69
+ const args = JSON.parse(JSON.stringify(action.args));
70
+ function substitute(obj) {
71
+ for (const [key, value] of Object.entries(obj)) {
72
+ if (typeof value === "string") {
73
+ obj[key] = value
74
+ .replace(/\$source/g, context.source ?? "")
75
+ .replace(/\$target/g, context.target ?? "")
76
+ .replace(/\$env/g, context.env ?? "");
77
+ }
78
+ else if (Array.isArray(value)) {
79
+ for (let i = 0; i < value.length; i++) {
80
+ if (typeof value[i] === "string") {
81
+ value[i] = value[i]
82
+ .replace(/\$source/g, context.source ?? "")
83
+ .replace(/\$target/g, context.target ?? "")
84
+ .replace(/\$env/g, context.env ?? "");
85
+ }
86
+ else if (typeof value[i] === "object" && value[i] !== null) {
87
+ substitute(value[i]);
88
+ }
89
+ }
90
+ }
91
+ else if (typeof value === "object" && value !== null) {
92
+ substitute(value);
93
+ }
94
+ }
95
+ }
96
+ substitute(args);
97
+ return { tool: action.tool, args };
98
+ }
99
+ /**
100
+ * Expand aliases into action specs
101
+ */
102
+ function expandAliases(actions) {
103
+ const expanded = [];
104
+ for (const action of actions) {
105
+ if (typeof action === "string") {
106
+ // It's an alias
107
+ const aliasActions = ACTION_ALIASES[action];
108
+ if (aliasActions) {
109
+ expanded.push(...aliasActions);
110
+ }
111
+ else {
112
+ // Unknown alias - will be reported as error during execution
113
+ console.warn(`[action-executor] Unknown alias: ${action}`);
114
+ }
115
+ }
116
+ else {
117
+ // It's an action spec
118
+ expanded.push(action);
119
+ }
120
+ }
121
+ return expanded;
122
+ }
123
+ // ─────────────────────────────────────────────────────────────────────────────
124
+ // Action Handlers
125
+ // ─────────────────────────────────────────────────────────────────────────────
126
+ /**
127
+ * Execute a data action using the real data handler
128
+ */
129
+ async function executeDataAction(args, context, client) {
130
+ const method = args.method;
131
+ const targetId = context.target;
132
+ if (!targetId) {
133
+ throw new Error("No target persona ID available for data action");
134
+ }
135
+ // Substitute context variables in args
136
+ const resolvedArgs = { ...args };
137
+ if (typeof resolvedArgs.from === "string") {
138
+ resolvedArgs.from = resolvedArgs.from
139
+ .replace(/\$source/g, context.source ?? "")
140
+ .replace(/\$target/g, context.target ?? "");
141
+ }
142
+ // Import and use the unified data handler
143
+ const { handleData } = await import("./data/index.js");
144
+ const result = await handleData({
145
+ persona_id: targetId,
146
+ data: { method, ...resolvedArgs },
147
+ mode: "data", // Legacy mode marker
148
+ }, client);
149
+ // Check for errors in result
150
+ if (result.error) {
151
+ throw new Error(result.error);
152
+ }
153
+ return result;
154
+ }
155
+ /**
156
+ * Execute a snapshot action
157
+ */
158
+ async function executeSnapshotAction(args, context, client) {
159
+ const message = args.message ?? "Action-created snapshot";
160
+ const targetId = context.target;
161
+ if (!targetId) {
162
+ throw new Error("No target persona ID available for snapshot action");
163
+ }
164
+ // For now, return a placeholder
165
+ // Full implementation would call the version storage API
166
+ return {
167
+ status: "snapshot_created",
168
+ persona_id: targetId,
169
+ message,
170
+ timestamp: new Date().toISOString(),
171
+ note: "Full snapshot integration pending version storage implementation",
172
+ };
173
+ }
174
+ /**
175
+ * Execute a validate action
176
+ */
177
+ async function executeValidateAction(args, context, client) {
178
+ const checks = args.checks ?? ["workflow", "config"];
179
+ const targetId = context.target;
180
+ if (!targetId) {
181
+ throw new Error("No target persona ID available for validate action");
182
+ }
183
+ // Fetch persona and run validation
184
+ const persona = await client.getPersonaById(targetId);
185
+ if (!persona) {
186
+ throw new Error(`Persona not found: ${targetId}`);
187
+ }
188
+ const results = {};
189
+ if (checks.includes("workflow")) {
190
+ const workflowDef = persona.workflow_def;
191
+ results.workflow = {
192
+ has_workflow: !!persona.workflow_def,
193
+ node_count: workflowDef?.nodes?.length ?? 0,
194
+ };
195
+ }
196
+ if (checks.includes("config")) {
197
+ const protoConfig = persona.proto_config;
198
+ results.config = {
199
+ has_proto_config: !!persona.proto_config,
200
+ widget_count: protoConfig?.widgets?.length ?? 0,
201
+ };
202
+ }
203
+ return {
204
+ status: "validated",
205
+ persona_id: targetId,
206
+ checks,
207
+ results,
208
+ };
209
+ }
210
+ /**
211
+ * Execute a single action
212
+ */
213
+ async function executeAction(action, context, client) {
214
+ switch (action.tool) {
215
+ case "data":
216
+ return executeDataAction(action.args, context, client);
217
+ case "snapshot":
218
+ return executeSnapshotAction(action.args, context, client);
219
+ case "validate":
220
+ return executeValidateAction(action.args, context, client);
221
+ default:
222
+ throw new Error(`Unknown action tool: ${action.tool}`);
223
+ }
224
+ }
225
+ // ─────────────────────────────────────────────────────────────────────────────
226
+ // Main Executor
227
+ // ─────────────────────────────────────────────────────────────────────────────
228
+ /**
229
+ * Execute a sequence of actions with context substitution
230
+ *
231
+ * @param actions - Array of action specs or alias strings
232
+ * @param context - Execution context with source/target IDs
233
+ * @param client - EmaClient for API calls
234
+ * @returns Results of all action executions
235
+ */
236
+ export async function executeActions(actions, context, client) {
237
+ const results = [];
238
+ let succeeded = 0;
239
+ let failed = 0;
240
+ let stoppedOnError = false;
241
+ // Expand aliases
242
+ const expanded = expandAliases(actions);
243
+ // Execute in sequence
244
+ for (const action of expanded) {
245
+ const startTime = Date.now();
246
+ // Substitute context variables
247
+ const substituted = substituteContext(action, context);
248
+ try {
249
+ const result = await executeAction(substituted, context, client);
250
+ results.push({
251
+ tool: action.tool,
252
+ status: "success",
253
+ result,
254
+ duration_ms: Date.now() - startTime,
255
+ });
256
+ succeeded++;
257
+ }
258
+ catch (error) {
259
+ results.push({
260
+ tool: action.tool,
261
+ status: "error",
262
+ error: error instanceof Error ? error.message : String(error),
263
+ duration_ms: Date.now() - startTime,
264
+ });
265
+ failed++;
266
+ stoppedOnError = true;
267
+ break; // Stop on first error
268
+ }
269
+ }
270
+ return {
271
+ executed: results.length,
272
+ succeeded,
273
+ failed,
274
+ results,
275
+ stopped_on_error: stoppedOnError ? true : undefined,
276
+ };
277
+ }
278
+ /**
279
+ * Check if actions array contains any valid actions
280
+ */
281
+ export function hasActions(actions) {
282
+ return Array.isArray(actions) && actions.length > 0;
283
+ }
284
+ /**
285
+ * Validate actions array structure
286
+ */
287
+ export function validateActions(actions) {
288
+ const errors = [];
289
+ if (!Array.isArray(actions)) {
290
+ return { valid: false, errors: ["actions must be an array"] };
291
+ }
292
+ for (let i = 0; i < actions.length; i++) {
293
+ const action = actions[i];
294
+ if (typeof action === "string") {
295
+ // Alias - check if known
296
+ if (!ACTION_ALIASES[action]) {
297
+ errors.push(`actions[${i}]: unknown alias "${action}". Valid: ${Object.keys(ACTION_ALIASES).join(", ")}`);
298
+ }
299
+ }
300
+ else if (typeof action === "object" && action !== null) {
301
+ // Action spec - check required fields
302
+ const spec = action;
303
+ if (!spec.tool) {
304
+ errors.push(`actions[${i}]: missing required field "tool"`);
305
+ }
306
+ else if (!["data", "snapshot", "validate"].includes(spec.tool)) {
307
+ errors.push(`actions[${i}]: invalid tool "${spec.tool}". Valid: data, snapshot, validate`);
308
+ }
309
+ if (!spec.args || typeof spec.args !== "object") {
310
+ errors.push(`actions[${i}]: missing or invalid "args" object`);
311
+ }
312
+ }
313
+ else {
314
+ errors.push(`actions[${i}]: must be object or string, got ${typeof action}`);
315
+ }
316
+ }
317
+ return { valid: errors.length === 0, errors };
318
+ }
319
+ /**
320
+ * Get list of available aliases with descriptions
321
+ */
322
+ export function getAvailableAliases() {
323
+ return [
324
+ {
325
+ name: "copy-data",
326
+ description: "Copy data from source to target persona",
327
+ actions: ACTION_ALIASES["copy-data"],
328
+ },
329
+ {
330
+ name: "copy-and-sanitize",
331
+ description: "Copy data then sanitize PII",
332
+ actions: ACTION_ALIASES["copy-and-sanitize"],
333
+ },
334
+ {
335
+ name: "standard-demo-setup",
336
+ description: "Copy, sanitize, and create snapshot",
337
+ actions: ACTION_ALIASES["standard-demo-setup"],
338
+ },
339
+ {
340
+ name: "create-snapshot",
341
+ description: "Create a checkpoint snapshot",
342
+ actions: ACTION_ALIASES["create-snapshot"],
343
+ },
344
+ {
345
+ name: "validate-workflow",
346
+ description: "Run workflow validation checks",
347
+ actions: ACTION_ALIASES["validate-workflow"],
348
+ },
349
+ ];
350
+ }