@claude-flow/cli 3.0.0-alpha.1 → 3.0.0-alpha.10

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 (164) hide show
  1. package/.agentic-flow/intelligence.json +4 -3
  2. package/.claude-flow/agents/store.json +16 -0
  3. package/.claude-flow/daemon-state.json +135 -0
  4. package/.claude-flow/daemon-test.log +0 -0
  5. package/.claude-flow/daemon.log +0 -0
  6. package/.claude-flow/daemon2.log +0 -0
  7. package/.claude-flow/daemon3.log +0 -0
  8. package/.claude-flow/hive-mind/state.json +51 -0
  9. package/.claude-flow/metrics/codebase-map.json +11 -0
  10. package/.claude-flow/metrics/consolidation.json +6 -0
  11. package/.claude-flow/metrics/performance.json +3 -3
  12. package/.claude-flow/metrics/security-audit.json +10 -0
  13. package/.claude-flow/metrics/task-metrics.json +3 -3
  14. package/.claude-flow/metrics/test-gaps.json +6 -0
  15. package/agents/architect.yaml +11 -0
  16. package/agents/coder.yaml +11 -0
  17. package/agents/reviewer.yaml +10 -0
  18. package/agents/security-architect.yaml +10 -0
  19. package/agents/tester.yaml +10 -0
  20. package/bin/cli.js +0 -0
  21. package/dist/src/commands/agent.d.ts.map +1 -1
  22. package/dist/src/commands/agent.js +43 -27
  23. package/dist/src/commands/agent.js.map +1 -1
  24. package/dist/src/commands/config.js +2 -2
  25. package/dist/src/commands/config.js.map +1 -1
  26. package/dist/src/commands/daemon.d.ts +8 -0
  27. package/dist/src/commands/daemon.d.ts.map +1 -0
  28. package/dist/src/commands/daemon.js +545 -0
  29. package/dist/src/commands/daemon.js.map +1 -0
  30. package/dist/src/commands/hive-mind.d.ts.map +1 -1
  31. package/dist/src/commands/hive-mind.js +252 -35
  32. package/dist/src/commands/hive-mind.js.map +1 -1
  33. package/dist/src/commands/hooks.js +1 -1
  34. package/dist/src/commands/hooks.js.map +1 -1
  35. package/dist/src/commands/index.d.ts +1 -0
  36. package/dist/src/commands/index.d.ts.map +1 -1
  37. package/dist/src/commands/index.js +4 -1
  38. package/dist/src/commands/index.js.map +1 -1
  39. package/dist/src/commands/mcp.js +1 -1
  40. package/dist/src/commands/mcp.js.map +1 -1
  41. package/dist/src/commands/memory.d.ts.map +1 -1
  42. package/dist/src/commands/memory.js +234 -168
  43. package/dist/src/commands/memory.js.map +1 -1
  44. package/dist/src/commands/migrate.js +1 -1
  45. package/dist/src/commands/migrate.js.map +1 -1
  46. package/dist/src/commands/process.d.ts.map +1 -1
  47. package/dist/src/commands/process.js +95 -20
  48. package/dist/src/commands/process.js.map +1 -1
  49. package/dist/src/commands/start.js +2 -2
  50. package/dist/src/commands/start.js.map +1 -1
  51. package/dist/src/commands/status.d.ts.map +1 -1
  52. package/dist/src/commands/status.js +26 -2
  53. package/dist/src/commands/status.js.map +1 -1
  54. package/dist/src/commands/swarm.js +6 -6
  55. package/dist/src/commands/swarm.js.map +1 -1
  56. package/dist/src/index.d.ts +2 -2
  57. package/dist/src/index.d.ts.map +1 -1
  58. package/dist/src/index.js +44 -3
  59. package/dist/src/index.js.map +1 -1
  60. package/dist/src/init/executor.d.ts.map +1 -1
  61. package/dist/src/init/executor.js +5 -0
  62. package/dist/src/init/executor.js.map +1 -1
  63. package/dist/src/init/helpers-generator.d.ts.map +1 -1
  64. package/dist/src/init/helpers-generator.js +1 -1
  65. package/dist/src/init/helpers-generator.js.map +1 -1
  66. package/dist/src/init/settings-generator.d.ts.map +1 -1
  67. package/dist/src/init/settings-generator.js +22 -12
  68. package/dist/src/init/settings-generator.js.map +1 -1
  69. package/dist/src/init/types.d.ts.map +1 -1
  70. package/dist/src/init/types.js +2 -2
  71. package/dist/src/init/types.js.map +1 -1
  72. package/dist/src/mcp-client.d.ts.map +1 -1
  73. package/dist/src/mcp-client.js +15 -1
  74. package/dist/src/mcp-client.js.map +1 -1
  75. package/dist/src/mcp-server.d.ts.map +1 -1
  76. package/dist/src/mcp-server.js +5 -0
  77. package/dist/src/mcp-server.js.map +1 -1
  78. package/dist/src/mcp-tools/agent-tools.d.ts +1 -1
  79. package/dist/src/mcp-tools/agent-tools.d.ts.map +1 -1
  80. package/dist/src/mcp-tools/agent-tools.js +350 -14
  81. package/dist/src/mcp-tools/agent-tools.js.map +1 -1
  82. package/dist/src/mcp-tools/config-tools.d.ts +1 -1
  83. package/dist/src/mcp-tools/config-tools.d.ts.map +1 -1
  84. package/dist/src/mcp-tools/config-tools.js +262 -15
  85. package/dist/src/mcp-tools/config-tools.js.map +1 -1
  86. package/dist/src/mcp-tools/hive-mind-tools.d.ts +8 -0
  87. package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -0
  88. package/dist/src/mcp-tools/hive-mind-tools.js +447 -0
  89. package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -0
  90. package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
  91. package/dist/src/mcp-tools/hooks-tools.js +80 -15
  92. package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
  93. package/dist/src/mcp-tools/index.d.ts +4 -0
  94. package/dist/src/mcp-tools/index.d.ts.map +1 -1
  95. package/dist/src/mcp-tools/index.js +4 -0
  96. package/dist/src/mcp-tools/index.js.map +1 -1
  97. package/dist/src/mcp-tools/memory-tools.d.ts +1 -1
  98. package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
  99. package/dist/src/mcp-tools/memory-tools.js +157 -9
  100. package/dist/src/mcp-tools/memory-tools.js.map +1 -1
  101. package/dist/src/mcp-tools/session-tools.d.ts +8 -0
  102. package/dist/src/mcp-tools/session-tools.d.ts.map +1 -0
  103. package/dist/src/mcp-tools/session-tools.js +315 -0
  104. package/dist/src/mcp-tools/session-tools.js.map +1 -0
  105. package/dist/src/mcp-tools/swarm-tools.d.ts.map +1 -1
  106. package/dist/src/mcp-tools/swarm-tools.js +37 -2
  107. package/dist/src/mcp-tools/swarm-tools.js.map +1 -1
  108. package/dist/src/mcp-tools/task-tools.d.ts +8 -0
  109. package/dist/src/mcp-tools/task-tools.d.ts.map +1 -0
  110. package/dist/src/mcp-tools/task-tools.js +302 -0
  111. package/dist/src/mcp-tools/task-tools.js.map +1 -0
  112. package/dist/src/mcp-tools/workflow-tools.d.ts +8 -0
  113. package/dist/src/mcp-tools/workflow-tools.d.ts.map +1 -0
  114. package/dist/src/mcp-tools/workflow-tools.js +481 -0
  115. package/dist/src/mcp-tools/workflow-tools.js.map +1 -0
  116. package/dist/src/output.d.ts +16 -0
  117. package/dist/src/output.d.ts.map +1 -1
  118. package/dist/src/output.js +42 -0
  119. package/dist/src/output.js.map +1 -1
  120. package/dist/src/services/index.d.ts +7 -0
  121. package/dist/src/services/index.d.ts.map +1 -0
  122. package/dist/src/services/index.js +6 -0
  123. package/dist/src/services/index.js.map +1 -0
  124. package/dist/src/services/worker-daemon.d.ts +153 -0
  125. package/dist/src/services/worker-daemon.d.ts.map +1 -0
  126. package/dist/src/services/worker-daemon.js +567 -0
  127. package/dist/src/services/worker-daemon.js.map +1 -0
  128. package/dist/tsconfig.tsbuildinfo +1 -1
  129. package/package.json +5 -3
  130. package/scripts/publish.sh +46 -0
  131. package/src/commands/agent.ts +43 -29
  132. package/src/commands/config.ts +2 -2
  133. package/src/commands/daemon.ts +621 -0
  134. package/src/commands/hive-mind.ts +229 -63
  135. package/src/commands/hooks.ts +1 -1
  136. package/src/commands/index.ts +4 -1
  137. package/src/commands/mcp.ts +1 -1
  138. package/src/commands/memory.ts +279 -181
  139. package/src/commands/migrate.ts +1 -1
  140. package/src/commands/process.ts +98 -20
  141. package/src/commands/start.ts +2 -2
  142. package/src/commands/status.ts +33 -2
  143. package/src/commands/swarm.ts +6 -6
  144. package/src/index.ts +48 -4
  145. package/src/init/executor.ts +6 -0
  146. package/src/init/helpers-generator.ts +1 -1
  147. package/src/init/settings-generator.ts +22 -12
  148. package/src/init/types.ts +3 -3
  149. package/src/mcp-client.ts +15 -1
  150. package/src/mcp-server.ts +6 -0
  151. package/src/mcp-tools/agent-tools.ts +388 -14
  152. package/src/mcp-tools/config-tools.ts +297 -15
  153. package/src/mcp-tools/hive-mind-tools.ts +521 -0
  154. package/src/mcp-tools/hooks-tools.ts +84 -15
  155. package/src/mcp-tools/index.ts +4 -0
  156. package/src/mcp-tools/memory-tools.ts +190 -9
  157. package/src/mcp-tools/session-tools.ts +359 -0
  158. package/src/mcp-tools/swarm-tools.ts +38 -2
  159. package/src/mcp-tools/task-tools.ts +347 -0
  160. package/src/mcp-tools/workflow-tools.ts +573 -0
  161. package/src/output.ts +47 -1
  162. package/src/services/index.ts +15 -0
  163. package/src/services/worker-daemon.ts +726 -0
  164. package/tmp.json +0 -0
@@ -0,0 +1,573 @@
1
+ /**
2
+ * Workflow MCP Tools for CLI
3
+ *
4
+ * Tool definitions for workflow automation and orchestration.
5
+ */
6
+
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import type { MCPTool } from './types.js';
10
+
11
+ // Storage paths
12
+ const STORAGE_DIR = '.claude-flow';
13
+ const WORKFLOW_DIR = 'workflows';
14
+ const WORKFLOW_FILE = 'store.json';
15
+
16
+ interface WorkflowStep {
17
+ stepId: string;
18
+ name: string;
19
+ type: 'task' | 'condition' | 'parallel' | 'loop' | 'wait';
20
+ config: Record<string, unknown>;
21
+ status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
22
+ result?: unknown;
23
+ startedAt?: string;
24
+ completedAt?: string;
25
+ }
26
+
27
+ interface WorkflowRecord {
28
+ workflowId: string;
29
+ name: string;
30
+ description?: string;
31
+ steps: WorkflowStep[];
32
+ status: 'draft' | 'ready' | 'running' | 'paused' | 'completed' | 'failed';
33
+ currentStep: number;
34
+ variables: Record<string, unknown>;
35
+ createdAt: string;
36
+ startedAt?: string;
37
+ completedAt?: string;
38
+ error?: string;
39
+ }
40
+
41
+ interface WorkflowStore {
42
+ workflows: Record<string, WorkflowRecord>;
43
+ templates: Record<string, WorkflowRecord>;
44
+ version: string;
45
+ }
46
+
47
+ function getWorkflowDir(): string {
48
+ return join(process.cwd(), STORAGE_DIR, WORKFLOW_DIR);
49
+ }
50
+
51
+ function getWorkflowPath(): string {
52
+ return join(getWorkflowDir(), WORKFLOW_FILE);
53
+ }
54
+
55
+ function ensureWorkflowDir(): void {
56
+ const dir = getWorkflowDir();
57
+ if (!existsSync(dir)) {
58
+ mkdirSync(dir, { recursive: true });
59
+ }
60
+ }
61
+
62
+ function loadWorkflowStore(): WorkflowStore {
63
+ try {
64
+ const path = getWorkflowPath();
65
+ if (existsSync(path)) {
66
+ const data = readFileSync(path, 'utf-8');
67
+ return JSON.parse(data);
68
+ }
69
+ } catch {
70
+ // Return default store on error
71
+ }
72
+ return { workflows: {}, templates: {}, version: '3.0.0' };
73
+ }
74
+
75
+ function saveWorkflowStore(store: WorkflowStore): void {
76
+ ensureWorkflowDir();
77
+ writeFileSync(getWorkflowPath(), JSON.stringify(store, null, 2), 'utf-8');
78
+ }
79
+
80
+ export const workflowTools: MCPTool[] = [
81
+ {
82
+ name: 'workflow/create',
83
+ description: 'Create a new workflow',
84
+ category: 'workflow',
85
+ inputSchema: {
86
+ type: 'object',
87
+ properties: {
88
+ name: { type: 'string', description: 'Workflow name' },
89
+ description: { type: 'string', description: 'Workflow description' },
90
+ steps: {
91
+ type: 'array',
92
+ description: 'Workflow steps',
93
+ items: {
94
+ type: 'object',
95
+ properties: {
96
+ name: { type: 'string' },
97
+ type: { type: 'string', enum: ['task', 'condition', 'parallel', 'loop', 'wait'] },
98
+ config: { type: 'object' },
99
+ },
100
+ },
101
+ },
102
+ variables: { type: 'object', description: 'Initial variables' },
103
+ },
104
+ required: ['name'],
105
+ },
106
+ handler: async (input) => {
107
+ const store = loadWorkflowStore();
108
+ const workflowId = `workflow-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
109
+
110
+ const steps: WorkflowStep[] = ((input.steps as Array<{name?: string; type?: string; config?: Record<string, unknown>}>) || []).map((s, i) => ({
111
+ stepId: `step-${i + 1}`,
112
+ name: s.name || `Step ${i + 1}`,
113
+ type: (s.type as WorkflowStep['type']) || 'task',
114
+ config: s.config || {} as Record<string, unknown>,
115
+ status: 'pending' as const,
116
+ }));
117
+
118
+ const workflow: WorkflowRecord = {
119
+ workflowId,
120
+ name: input.name as string,
121
+ description: input.description as string,
122
+ steps,
123
+ status: steps.length > 0 ? 'ready' : 'draft',
124
+ currentStep: 0,
125
+ variables: (input.variables as Record<string, unknown>) || {},
126
+ createdAt: new Date().toISOString(),
127
+ };
128
+
129
+ store.workflows[workflowId] = workflow;
130
+ saveWorkflowStore(store);
131
+
132
+ return {
133
+ workflowId,
134
+ name: workflow.name,
135
+ status: workflow.status,
136
+ stepCount: steps.length,
137
+ createdAt: workflow.createdAt,
138
+ };
139
+ },
140
+ },
141
+ {
142
+ name: 'workflow/execute',
143
+ description: 'Execute a workflow',
144
+ category: 'workflow',
145
+ inputSchema: {
146
+ type: 'object',
147
+ properties: {
148
+ workflowId: { type: 'string', description: 'Workflow ID to execute' },
149
+ variables: { type: 'object', description: 'Runtime variables to inject' },
150
+ startFromStep: { type: 'number', description: 'Step to start from (0-indexed)' },
151
+ },
152
+ required: ['workflowId'],
153
+ },
154
+ handler: async (input) => {
155
+ const store = loadWorkflowStore();
156
+ const workflowId = input.workflowId as string;
157
+ const workflow = store.workflows[workflowId];
158
+
159
+ if (!workflow) {
160
+ return { workflowId, error: 'Workflow not found' };
161
+ }
162
+
163
+ if (workflow.status === 'running') {
164
+ return { workflowId, error: 'Workflow already running' };
165
+ }
166
+
167
+ // Inject runtime variables
168
+ if (input.variables) {
169
+ workflow.variables = { ...workflow.variables, ...(input.variables as Record<string, unknown>) };
170
+ }
171
+
172
+ workflow.status = 'running';
173
+ workflow.startedAt = new Date().toISOString();
174
+ workflow.currentStep = (input.startFromStep as number) || 0;
175
+
176
+ // Execute steps (in real implementation, this would be async/event-driven)
177
+ const results: Array<{ stepId: string; status: string }> = [];
178
+ for (let i = workflow.currentStep; i < workflow.steps.length; i++) {
179
+ const step = workflow.steps[i];
180
+ step.status = 'running';
181
+ step.startedAt = new Date().toISOString();
182
+
183
+ // For now, mark as completed (real implementation would execute actual tasks)
184
+ step.status = 'completed';
185
+ step.completedAt = new Date().toISOString();
186
+ step.result = { executed: true, stepType: step.type };
187
+
188
+ results.push({ stepId: step.stepId, status: step.status });
189
+ workflow.currentStep = i + 1;
190
+ }
191
+
192
+ workflow.status = 'completed';
193
+ workflow.completedAt = new Date().toISOString();
194
+
195
+ saveWorkflowStore(store);
196
+
197
+ return {
198
+ workflowId,
199
+ status: workflow.status,
200
+ stepsExecuted: results.length,
201
+ results,
202
+ startedAt: workflow.startedAt,
203
+ completedAt: workflow.completedAt,
204
+ duration: new Date(workflow.completedAt).getTime() - new Date(workflow.startedAt!).getTime(),
205
+ };
206
+ },
207
+ },
208
+ {
209
+ name: 'workflow/status',
210
+ description: 'Get workflow status',
211
+ category: 'workflow',
212
+ inputSchema: {
213
+ type: 'object',
214
+ properties: {
215
+ workflowId: { type: 'string', description: 'Workflow ID' },
216
+ verbose: { type: 'boolean', description: 'Include step details' },
217
+ },
218
+ required: ['workflowId'],
219
+ },
220
+ handler: async (input) => {
221
+ const store = loadWorkflowStore();
222
+ const workflowId = input.workflowId as string;
223
+ const workflow = store.workflows[workflowId];
224
+
225
+ if (!workflow) {
226
+ return { workflowId, error: 'Workflow not found' };
227
+ }
228
+
229
+ const completedSteps = workflow.steps.filter(s => s.status === 'completed').length;
230
+ const progress = workflow.steps.length > 0 ? (completedSteps / workflow.steps.length) * 100 : 0;
231
+
232
+ const status = {
233
+ workflowId: workflow.workflowId,
234
+ name: workflow.name,
235
+ status: workflow.status,
236
+ progress,
237
+ currentStep: workflow.currentStep,
238
+ totalSteps: workflow.steps.length,
239
+ completedSteps,
240
+ createdAt: workflow.createdAt,
241
+ startedAt: workflow.startedAt,
242
+ completedAt: workflow.completedAt,
243
+ };
244
+
245
+ if (input.verbose) {
246
+ return {
247
+ ...status,
248
+ description: workflow.description,
249
+ variables: workflow.variables,
250
+ steps: workflow.steps.map(s => ({
251
+ stepId: s.stepId,
252
+ name: s.name,
253
+ type: s.type,
254
+ status: s.status,
255
+ startedAt: s.startedAt,
256
+ completedAt: s.completedAt,
257
+ })),
258
+ error: workflow.error,
259
+ };
260
+ }
261
+
262
+ return status;
263
+ },
264
+ },
265
+ {
266
+ name: 'workflow/list',
267
+ description: 'List all workflows',
268
+ category: 'workflow',
269
+ inputSchema: {
270
+ type: 'object',
271
+ properties: {
272
+ status: { type: 'string', description: 'Filter by status' },
273
+ limit: { type: 'number', description: 'Max workflows to return' },
274
+ },
275
+ },
276
+ handler: async (input) => {
277
+ const store = loadWorkflowStore();
278
+ let workflows = Object.values(store.workflows);
279
+
280
+ // Apply filters
281
+ if (input.status) {
282
+ workflows = workflows.filter(w => w.status === input.status);
283
+ }
284
+
285
+ // Sort by creation date (newest first)
286
+ workflows.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
287
+
288
+ // Apply limit
289
+ const limit = (input.limit as number) || 20;
290
+ workflows = workflows.slice(0, limit);
291
+
292
+ return {
293
+ workflows: workflows.map(w => ({
294
+ workflowId: w.workflowId,
295
+ name: w.name,
296
+ status: w.status,
297
+ stepCount: w.steps.length,
298
+ createdAt: w.createdAt,
299
+ completedAt: w.completedAt,
300
+ })),
301
+ total: workflows.length,
302
+ filters: { status: input.status },
303
+ };
304
+ },
305
+ },
306
+ {
307
+ name: 'workflow/pause',
308
+ description: 'Pause a running workflow',
309
+ category: 'workflow',
310
+ inputSchema: {
311
+ type: 'object',
312
+ properties: {
313
+ workflowId: { type: 'string', description: 'Workflow ID' },
314
+ },
315
+ required: ['workflowId'],
316
+ },
317
+ handler: async (input) => {
318
+ const store = loadWorkflowStore();
319
+ const workflowId = input.workflowId as string;
320
+ const workflow = store.workflows[workflowId];
321
+
322
+ if (!workflow) {
323
+ return { workflowId, error: 'Workflow not found' };
324
+ }
325
+
326
+ if (workflow.status !== 'running') {
327
+ return { workflowId, error: 'Workflow not running' };
328
+ }
329
+
330
+ workflow.status = 'paused';
331
+ saveWorkflowStore(store);
332
+
333
+ return {
334
+ workflowId,
335
+ status: workflow.status,
336
+ pausedAt: new Date().toISOString(),
337
+ currentStep: workflow.currentStep,
338
+ };
339
+ },
340
+ },
341
+ {
342
+ name: 'workflow/resume',
343
+ description: 'Resume a paused workflow',
344
+ category: 'workflow',
345
+ inputSchema: {
346
+ type: 'object',
347
+ properties: {
348
+ workflowId: { type: 'string', description: 'Workflow ID' },
349
+ },
350
+ required: ['workflowId'],
351
+ },
352
+ handler: async (input) => {
353
+ const store = loadWorkflowStore();
354
+ const workflowId = input.workflowId as string;
355
+ const workflow = store.workflows[workflowId];
356
+
357
+ if (!workflow) {
358
+ return { workflowId, error: 'Workflow not found' };
359
+ }
360
+
361
+ if (workflow.status !== 'paused') {
362
+ return { workflowId, error: 'Workflow not paused' };
363
+ }
364
+
365
+ workflow.status = 'running';
366
+ saveWorkflowStore(store);
367
+
368
+ // Continue execution from current step
369
+ const results: Array<{ stepId: string; status: string }> = [];
370
+ for (let i = workflow.currentStep; i < workflow.steps.length; i++) {
371
+ const step = workflow.steps[i];
372
+ step.status = 'running';
373
+ step.startedAt = new Date().toISOString();
374
+ step.status = 'completed';
375
+ step.completedAt = new Date().toISOString();
376
+ step.result = { executed: true };
377
+ results.push({ stepId: step.stepId, status: step.status });
378
+ workflow.currentStep = i + 1;
379
+ }
380
+
381
+ workflow.status = 'completed';
382
+ workflow.completedAt = new Date().toISOString();
383
+ saveWorkflowStore(store);
384
+
385
+ return {
386
+ workflowId,
387
+ status: workflow.status,
388
+ resumed: true,
389
+ stepsExecuted: results.length,
390
+ completedAt: workflow.completedAt,
391
+ };
392
+ },
393
+ },
394
+ {
395
+ name: 'workflow/cancel',
396
+ description: 'Cancel a workflow',
397
+ category: 'workflow',
398
+ inputSchema: {
399
+ type: 'object',
400
+ properties: {
401
+ workflowId: { type: 'string', description: 'Workflow ID' },
402
+ reason: { type: 'string', description: 'Cancellation reason' },
403
+ },
404
+ required: ['workflowId'],
405
+ },
406
+ handler: async (input) => {
407
+ const store = loadWorkflowStore();
408
+ const workflowId = input.workflowId as string;
409
+ const workflow = store.workflows[workflowId];
410
+
411
+ if (!workflow) {
412
+ return { workflowId, error: 'Workflow not found' };
413
+ }
414
+
415
+ if (workflow.status === 'completed' || workflow.status === 'failed') {
416
+ return { workflowId, error: 'Workflow already finished' };
417
+ }
418
+
419
+ workflow.status = 'failed';
420
+ workflow.error = (input.reason as string) || 'Cancelled by user';
421
+ workflow.completedAt = new Date().toISOString();
422
+
423
+ // Mark remaining steps as skipped
424
+ for (let i = workflow.currentStep; i < workflow.steps.length; i++) {
425
+ workflow.steps[i].status = 'skipped';
426
+ }
427
+
428
+ saveWorkflowStore(store);
429
+
430
+ return {
431
+ workflowId,
432
+ status: workflow.status,
433
+ cancelledAt: workflow.completedAt,
434
+ reason: workflow.error,
435
+ skippedSteps: workflow.steps.length - workflow.currentStep,
436
+ };
437
+ },
438
+ },
439
+ {
440
+ name: 'workflow/delete',
441
+ description: 'Delete a workflow',
442
+ category: 'workflow',
443
+ inputSchema: {
444
+ type: 'object',
445
+ properties: {
446
+ workflowId: { type: 'string', description: 'Workflow ID' },
447
+ },
448
+ required: ['workflowId'],
449
+ },
450
+ handler: async (input) => {
451
+ const store = loadWorkflowStore();
452
+ const workflowId = input.workflowId as string;
453
+
454
+ if (!store.workflows[workflowId]) {
455
+ return { workflowId, error: 'Workflow not found' };
456
+ }
457
+
458
+ const workflow = store.workflows[workflowId];
459
+ if (workflow.status === 'running') {
460
+ return { workflowId, error: 'Cannot delete running workflow' };
461
+ }
462
+
463
+ delete store.workflows[workflowId];
464
+ saveWorkflowStore(store);
465
+
466
+ return {
467
+ workflowId,
468
+ deleted: true,
469
+ deletedAt: new Date().toISOString(),
470
+ };
471
+ },
472
+ },
473
+ {
474
+ name: 'workflow/template',
475
+ description: 'Save workflow as template or create from template',
476
+ category: 'workflow',
477
+ inputSchema: {
478
+ type: 'object',
479
+ properties: {
480
+ action: { type: 'string', enum: ['save', 'create', 'list'], description: 'Template action' },
481
+ workflowId: { type: 'string', description: 'Workflow ID (for save)' },
482
+ templateId: { type: 'string', description: 'Template ID (for create)' },
483
+ templateName: { type: 'string', description: 'Template name (for save)' },
484
+ newName: { type: 'string', description: 'New workflow name (for create)' },
485
+ },
486
+ required: ['action'],
487
+ },
488
+ handler: async (input) => {
489
+ const store = loadWorkflowStore();
490
+ const action = input.action as string;
491
+
492
+ if (action === 'save') {
493
+ const workflow = store.workflows[input.workflowId as string];
494
+ if (!workflow) {
495
+ return { action, error: 'Workflow not found' };
496
+ }
497
+
498
+ const templateId = `template-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
499
+ const template: WorkflowRecord = {
500
+ ...workflow,
501
+ workflowId: templateId,
502
+ name: (input.templateName as string) || `${workflow.name} Template`,
503
+ status: 'draft',
504
+ currentStep: 0,
505
+ createdAt: new Date().toISOString(),
506
+ startedAt: undefined,
507
+ completedAt: undefined,
508
+ };
509
+
510
+ // Reset step statuses
511
+ template.steps = template.steps.map(s => ({
512
+ ...s,
513
+ status: 'pending',
514
+ result: undefined,
515
+ startedAt: undefined,
516
+ completedAt: undefined,
517
+ }));
518
+
519
+ store.templates[templateId] = template;
520
+ saveWorkflowStore(store);
521
+
522
+ return {
523
+ action,
524
+ templateId,
525
+ name: template.name,
526
+ savedAt: new Date().toISOString(),
527
+ };
528
+ }
529
+
530
+ if (action === 'create') {
531
+ const template = store.templates[input.templateId as string];
532
+ if (!template) {
533
+ return { action, error: 'Template not found' };
534
+ }
535
+
536
+ const workflowId = `workflow-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
537
+ const workflow: WorkflowRecord = {
538
+ ...template,
539
+ workflowId,
540
+ name: (input.newName as string) || template.name.replace(' Template', ''),
541
+ status: 'ready',
542
+ createdAt: new Date().toISOString(),
543
+ };
544
+
545
+ store.workflows[workflowId] = workflow;
546
+ saveWorkflowStore(store);
547
+
548
+ return {
549
+ action,
550
+ workflowId,
551
+ name: workflow.name,
552
+ fromTemplate: input.templateId,
553
+ createdAt: workflow.createdAt,
554
+ };
555
+ }
556
+
557
+ if (action === 'list') {
558
+ return {
559
+ action,
560
+ templates: Object.values(store.templates).map(t => ({
561
+ templateId: t.workflowId,
562
+ name: t.name,
563
+ stepCount: t.steps.length,
564
+ createdAt: t.createdAt,
565
+ })),
566
+ total: Object.keys(store.templates).length,
567
+ };
568
+ }
569
+
570
+ return { action, error: 'Unknown action' };
571
+ },
572
+ },
573
+ ];
package/src/output.ts CHANGED
@@ -50,15 +50,46 @@ const COLORS = {
50
50
 
51
51
  type ColorName = keyof typeof COLORS;
52
52
 
53
+ export type VerbosityLevel = 'quiet' | 'normal' | 'verbose' | 'debug';
54
+
53
55
  export class OutputFormatter {
54
56
  private colorEnabled: boolean;
55
57
  private outputStream: NodeJS.WriteStream;
56
58
  private errorStream: NodeJS.WriteStream;
59
+ private verbosity: VerbosityLevel;
57
60
 
58
- constructor(options: { color?: boolean } = {}) {
61
+ constructor(options: { color?: boolean; verbosity?: VerbosityLevel } = {}) {
59
62
  this.colorEnabled = options.color ?? this.supportsColor();
60
63
  this.outputStream = process.stdout;
61
64
  this.errorStream = process.stderr;
65
+ this.verbosity = options.verbosity ?? 'normal';
66
+ }
67
+
68
+ /**
69
+ * Set verbosity level
70
+ * - quiet: Only errors and direct results
71
+ * - normal: Errors, warnings, info, and results
72
+ * - verbose: All of normal + debug messages
73
+ * - debug: All output including trace
74
+ */
75
+ setVerbosity(level: VerbosityLevel): void {
76
+ this.verbosity = level;
77
+ }
78
+
79
+ getVerbosity(): VerbosityLevel {
80
+ return this.verbosity;
81
+ }
82
+
83
+ isQuiet(): boolean {
84
+ return this.verbosity === 'quiet';
85
+ }
86
+
87
+ isVerbose(): boolean {
88
+ return this.verbosity === 'verbose' || this.verbosity === 'debug';
89
+ }
90
+
91
+ isDebug(): boolean {
92
+ return this.verbosity === 'debug';
62
93
  }
63
94
 
64
95
  private supportsColor(): boolean {
@@ -136,11 +167,13 @@ export class OutputFormatter {
136
167
  // ============================================
137
168
 
138
169
  printSuccess(message: string): void {
170
+ // Success always shows (result output)
139
171
  const icon = this.color('[OK]', 'green', 'bold');
140
172
  this.writeln(`${icon} ${message}`);
141
173
  }
142
174
 
143
175
  printError(message: string, details?: string): void {
176
+ // Errors always show
144
177
  const icon = this.color('[ERROR]', 'red', 'bold');
145
178
  this.writeErrorln(`${icon} ${message}`);
146
179
  if (details) {
@@ -149,20 +182,33 @@ export class OutputFormatter {
149
182
  }
150
183
 
151
184
  printWarning(message: string): void {
185
+ // Warnings suppressed in quiet mode
186
+ if (this.verbosity === 'quiet') return;
152
187
  const icon = this.color('[WARN]', 'yellow', 'bold');
153
188
  this.writeln(`${icon} ${message}`);
154
189
  }
155
190
 
156
191
  printInfo(message: string): void {
192
+ // Info suppressed in quiet mode
193
+ if (this.verbosity === 'quiet') return;
157
194
  const icon = this.color('[INFO]', 'blue', 'bold');
158
195
  this.writeln(`${icon} ${message}`);
159
196
  }
160
197
 
161
198
  printDebug(message: string): void {
199
+ // Debug only shows in verbose/debug mode
200
+ if (this.verbosity !== 'verbose' && this.verbosity !== 'debug') return;
162
201
  const icon = this.color('[DEBUG]', 'gray');
163
202
  this.writeln(`${icon} ${this.dim(message)}`);
164
203
  }
165
204
 
205
+ printTrace(message: string): void {
206
+ // Trace only shows in debug mode
207
+ if (this.verbosity !== 'debug') return;
208
+ const icon = this.color('[TRACE]', 'gray', 'dim');
209
+ this.writeln(`${icon} ${this.dim(message)}`);
210
+ }
211
+
166
212
  // ============================================
167
213
  // Table Formatting
168
214
  // ============================================
@@ -0,0 +1,15 @@
1
+ /**
2
+ * V3 CLI Services Index
3
+ * Central registry for all background services
4
+ */
5
+
6
+ export {
7
+ WorkerDaemon,
8
+ getDaemon,
9
+ startDaemon,
10
+ stopDaemon,
11
+ type WorkerType,
12
+ } from './worker-daemon.js';
13
+
14
+ // Re-export types
15
+ export type { default as WorkerDaemonType } from './worker-daemon.js';