@claude-flow/cli 3.5.43 → 3.5.45

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 (56) hide show
  1. package/README.md +199 -196
  2. package/dist/src/autopilot-state.d.ts +77 -0
  3. package/dist/src/autopilot-state.d.ts.map +1 -0
  4. package/dist/src/autopilot-state.js +279 -0
  5. package/dist/src/autopilot-state.js.map +1 -0
  6. package/dist/src/commands/autopilot.d.ts +15 -0
  7. package/dist/src/commands/autopilot.d.ts.map +1 -0
  8. package/dist/src/commands/autopilot.js +362 -0
  9. package/dist/src/commands/autopilot.js.map +1 -0
  10. package/dist/src/commands/claims.d.ts.map +1 -1
  11. package/dist/src/commands/claims.js +352 -34
  12. package/dist/src/commands/claims.js.map +1 -1
  13. package/dist/src/commands/config.d.ts.map +1 -1
  14. package/dist/src/commands/config.js +66 -34
  15. package/dist/src/commands/config.js.map +1 -1
  16. package/dist/src/commands/deployment.d.ts.map +1 -1
  17. package/dist/src/commands/deployment.js +538 -46
  18. package/dist/src/commands/deployment.js.map +1 -1
  19. package/dist/src/commands/index.d.ts +2 -0
  20. package/dist/src/commands/index.d.ts.map +1 -1
  21. package/dist/src/commands/index.js +7 -0
  22. package/dist/src/commands/index.js.map +1 -1
  23. package/dist/src/commands/migrate.d.ts.map +1 -1
  24. package/dist/src/commands/migrate.js +531 -39
  25. package/dist/src/commands/migrate.js.map +1 -1
  26. package/dist/src/commands/providers.d.ts.map +1 -1
  27. package/dist/src/commands/providers.js +163 -29
  28. package/dist/src/commands/providers.js.map +1 -1
  29. package/dist/src/init/executor.d.ts.map +1 -1
  30. package/dist/src/init/executor.js +2 -3
  31. package/dist/src/init/executor.js.map +1 -1
  32. package/dist/src/mcp-client.d.ts.map +1 -1
  33. package/dist/src/mcp-client.js +6 -0
  34. package/dist/src/mcp-client.js.map +1 -1
  35. package/dist/src/mcp-server.d.ts +3 -1
  36. package/dist/src/mcp-server.d.ts.map +1 -1
  37. package/dist/src/mcp-server.js +31 -4
  38. package/dist/src/mcp-server.js.map +1 -1
  39. package/dist/src/mcp-tools/autopilot-tools.d.ts +12 -0
  40. package/dist/src/mcp-tools/autopilot-tools.d.ts.map +1 -0
  41. package/dist/src/mcp-tools/autopilot-tools.js +227 -0
  42. package/dist/src/mcp-tools/autopilot-tools.js.map +1 -0
  43. package/dist/src/mcp-tools/guidance-tools.d.ts +15 -0
  44. package/dist/src/mcp-tools/guidance-tools.d.ts.map +1 -0
  45. package/dist/src/mcp-tools/guidance-tools.js +617 -0
  46. package/dist/src/mcp-tools/guidance-tools.js.map +1 -0
  47. package/dist/src/mcp-tools/index.d.ts +2 -0
  48. package/dist/src/mcp-tools/index.d.ts.map +1 -1
  49. package/dist/src/mcp-tools/index.js +2 -0
  50. package/dist/src/mcp-tools/index.js.map +1 -1
  51. package/dist/src/services/config-file-manager.d.ts +37 -0
  52. package/dist/src/services/config-file-manager.d.ts.map +1 -0
  53. package/dist/src/services/config-file-manager.js +224 -0
  54. package/dist/src/services/config-file-manager.js.map +1 -0
  55. package/dist/tsconfig.tsbuildinfo +1 -1
  56. package/package.json +3 -1
@@ -5,52 +5,249 @@
5
5
  * Created with ❤️ by ruv.io
6
6
  */
7
7
  import { output } from '../output.js';
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ // ============================================
11
+ // State Helpers
12
+ // ============================================
13
+ function getStateDir(cwd) {
14
+ return path.join(cwd, '.claude-flow');
15
+ }
16
+ function getStatePath(cwd) {
17
+ return path.join(getStateDir(cwd), 'deployments.json');
18
+ }
19
+ function emptyState() {
20
+ return { environments: {}, history: [], activeDeployment: undefined };
21
+ }
22
+ function loadDeploymentState(cwd) {
23
+ const filePath = getStatePath(cwd);
24
+ if (!fs.existsSync(filePath)) {
25
+ return emptyState();
26
+ }
27
+ try {
28
+ const raw = fs.readFileSync(filePath, 'utf-8');
29
+ return JSON.parse(raw);
30
+ }
31
+ catch {
32
+ return emptyState();
33
+ }
34
+ }
35
+ function saveDeploymentState(cwd, state) {
36
+ const dir = getStateDir(cwd);
37
+ if (!fs.existsSync(dir)) {
38
+ fs.mkdirSync(dir, { recursive: true });
39
+ }
40
+ const filePath = getStatePath(cwd);
41
+ const tmpPath = filePath + '.tmp';
42
+ fs.writeFileSync(tmpPath, JSON.stringify(state, null, 2), 'utf-8');
43
+ fs.renameSync(tmpPath, filePath);
44
+ }
45
+ function generateId() {
46
+ const ts = Date.now().toString(36);
47
+ const rand = Math.random().toString(36).slice(2, 8);
48
+ return `dep-${ts}-${rand}`;
49
+ }
50
+ function readProjectVersion(cwd) {
51
+ const pkgPath = path.join(cwd, 'package.json');
52
+ if (!fs.existsSync(pkgPath)) {
53
+ return null;
54
+ }
55
+ try {
56
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
57
+ return pkg.version ?? null;
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ // ============================================
8
64
  // Deploy subcommand
65
+ // ============================================
9
66
  const deployCommand = {
10
67
  name: 'deploy',
11
68
  description: 'Deploy to target environment',
12
69
  options: [
13
70
  { name: 'env', short: 'e', type: 'string', description: 'Environment: dev, staging, prod', default: 'staging' },
14
- { name: 'version', short: 'v', type: 'string', description: 'Version to deploy', default: 'latest' },
71
+ { name: 'version', short: 'v', type: 'string', description: 'Version to deploy' },
15
72
  { name: 'dry-run', short: 'd', type: 'boolean', description: 'Simulate deployment without changes' },
16
- { name: 'force', short: 'f', type: 'boolean', description: 'Force deployment without checks' },
17
- { name: 'rollback-on-fail', type: 'boolean', description: 'Auto rollback on failure', default: 'true' },
73
+ { name: 'description', type: 'string', description: 'Deployment description' },
18
74
  ],
19
75
  examples: [
20
76
  { command: 'claude-flow deployment deploy -e prod', description: 'Deploy to production' },
21
77
  { command: 'claude-flow deployment deploy --dry-run', description: 'Simulate deployment' },
22
78
  ],
23
79
  action: async (ctx) => {
24
- // #1425: This command is not yet implemented — was returning hardcoded fake responses
25
- output.writeln();
26
- output.printError('deployment deploy is not yet implemented');
27
- output.writeln(output.dim('This command will be implemented in a future release.'));
28
- output.writeln(output.dim('Track progress: https://github.com/ruvnet/claude-flow/issues/1425'));
29
- return { success: false, exitCode: 1 };
80
+ try {
81
+ const envName = String(ctx.flags['env'] || 'staging');
82
+ const dryRun = Boolean(ctx.flags['dry-run']);
83
+ const description = ctx.flags['description'] ? String(ctx.flags['description']) : undefined;
84
+ let version = ctx.flags['version'] ? String(ctx.flags['version']) : null;
85
+ if (!version) {
86
+ version = readProjectVersion(ctx.cwd) || '0.0.0';
87
+ }
88
+ const state = loadDeploymentState(ctx.cwd);
89
+ // Ensure environment exists; auto-create if it doesn't
90
+ if (!state.environments[envName]) {
91
+ state.environments[envName] = {
92
+ name: envName,
93
+ type: envName === 'prod' || envName === 'production' ? 'production' : envName === 'staging' ? 'staging' : 'local',
94
+ createdAt: new Date().toISOString(),
95
+ };
96
+ }
97
+ const record = {
98
+ id: generateId(),
99
+ environment: envName,
100
+ version,
101
+ status: 'deployed',
102
+ timestamp: new Date().toISOString(),
103
+ description,
104
+ };
105
+ if (dryRun) {
106
+ output.writeln();
107
+ output.printInfo('Dry run - no changes will be made');
108
+ output.writeln();
109
+ output.writeln(output.bold('Deployment Preview'));
110
+ output.printTable({
111
+ columns: [
112
+ { key: 'field', header: 'Field' },
113
+ { key: 'value', header: 'Value' },
114
+ ],
115
+ data: [
116
+ { field: 'ID', value: record.id },
117
+ { field: 'Environment', value: envName },
118
+ { field: 'Version', value: version },
119
+ { field: 'Status', value: 'deployed (dry-run)' },
120
+ { field: 'Description', value: description || '-' },
121
+ ],
122
+ });
123
+ return { success: true };
124
+ }
125
+ state.history.push(record);
126
+ state.activeDeployment = record.id;
127
+ saveDeploymentState(ctx.cwd, state);
128
+ output.writeln();
129
+ output.printSuccess(`Deployed version ${version} to ${envName}`);
130
+ output.writeln();
131
+ output.printTable({
132
+ columns: [
133
+ { key: 'field', header: 'Field' },
134
+ { key: 'value', header: 'Value' },
135
+ ],
136
+ data: [
137
+ { field: 'ID', value: record.id },
138
+ { field: 'Environment', value: envName },
139
+ { field: 'Version', value: version },
140
+ { field: 'Status', value: record.status },
141
+ { field: 'Timestamp', value: record.timestamp },
142
+ { field: 'Description', value: description || '-' },
143
+ ],
144
+ });
145
+ return { success: true, data: record };
146
+ }
147
+ catch (err) {
148
+ const msg = err instanceof Error ? err.message : String(err);
149
+ output.printError('Deploy failed', msg);
150
+ return { success: false, exitCode: 1 };
151
+ }
30
152
  },
31
153
  };
154
+ // ============================================
32
155
  // Status subcommand
156
+ // ============================================
33
157
  const statusCommand = {
34
158
  name: 'status',
35
159
  description: 'Check deployment status across environments',
36
160
  options: [
37
161
  { name: 'env', short: 'e', type: 'string', description: 'Specific environment to check' },
38
- { name: 'watch', short: 'w', type: 'boolean', description: 'Watch for changes' },
39
162
  ],
40
163
  examples: [
41
164
  { command: 'claude-flow deployment status', description: 'Show all environments' },
42
165
  { command: 'claude-flow deployment status -e prod', description: 'Check production' },
43
166
  ],
44
167
  action: async (ctx) => {
45
- // #1425: This command is not yet implemented — was returning hardcoded fake data
46
- output.writeln();
47
- output.printError('deployment status is not yet implemented');
48
- output.writeln(output.dim('This command will be implemented in a future release.'));
49
- output.writeln(output.dim('Track progress: https://github.com/ruvnet/claude-flow/issues/1425'));
50
- return { success: false, exitCode: 1 };
168
+ try {
169
+ const state = loadDeploymentState(ctx.cwd);
170
+ const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']) : null;
171
+ output.writeln();
172
+ output.writeln(output.bold('Deployment Status'));
173
+ output.writeln();
174
+ // Active deployment
175
+ if (state.activeDeployment) {
176
+ const active = state.history.find(r => r.id === state.activeDeployment);
177
+ if (active) {
178
+ output.printInfo(`Active deployment: ${active.id} (v${active.version} on ${active.environment})`);
179
+ }
180
+ }
181
+ else {
182
+ output.writeln(output.dim('No active deployment'));
183
+ }
184
+ // Environments table
185
+ const envEntries = Object.values(state.environments);
186
+ if (filterEnv) {
187
+ const env = state.environments[filterEnv];
188
+ if (!env) {
189
+ output.printWarning(`Environment '${filterEnv}' not found`);
190
+ return { success: true };
191
+ }
192
+ output.writeln();
193
+ output.writeln(output.bold('Environment'));
194
+ output.printTable({
195
+ columns: [
196
+ { key: 'name', header: 'Name' },
197
+ { key: 'type', header: 'Type' },
198
+ { key: 'url', header: 'URL' },
199
+ { key: 'createdAt', header: 'Created' },
200
+ ],
201
+ data: [{ name: env.name, type: env.type, url: env.url || '-', createdAt: env.createdAt }],
202
+ });
203
+ }
204
+ else if (envEntries.length > 0) {
205
+ output.writeln();
206
+ output.writeln(output.bold('Environments'));
207
+ output.printTable({
208
+ columns: [
209
+ { key: 'name', header: 'Name' },
210
+ { key: 'type', header: 'Type' },
211
+ { key: 'url', header: 'URL' },
212
+ { key: 'createdAt', header: 'Created' },
213
+ ],
214
+ data: envEntries.map(e => ({ name: e.name, type: e.type, url: e.url || '-', createdAt: e.createdAt })),
215
+ });
216
+ }
217
+ else {
218
+ output.writeln(output.dim('No environments configured'));
219
+ }
220
+ // Recent history (last 5)
221
+ let recent = [...state.history].reverse().slice(0, 5);
222
+ if (filterEnv) {
223
+ recent = recent.filter(r => r.environment === filterEnv);
224
+ }
225
+ if (recent.length > 0) {
226
+ output.writeln();
227
+ output.writeln(output.bold('Recent Deployments'));
228
+ output.printTable({
229
+ columns: [
230
+ { key: 'id', header: 'ID' },
231
+ { key: 'environment', header: 'Env' },
232
+ { key: 'version', header: 'Version' },
233
+ { key: 'status', header: 'Status' },
234
+ { key: 'timestamp', header: 'Time' },
235
+ ],
236
+ data: recent.map(r => ({ ...r })),
237
+ });
238
+ }
239
+ return { success: true };
240
+ }
241
+ catch (err) {
242
+ const msg = err instanceof Error ? err.message : String(err);
243
+ output.printError('Status check failed', msg);
244
+ return { success: false, exitCode: 1 };
245
+ }
51
246
  },
52
247
  };
248
+ // ============================================
53
249
  // Rollback subcommand
250
+ // ============================================
54
251
  const rollbackCommand = {
55
252
  name: 'rollback',
56
253
  description: 'Rollback to previous deployment',
@@ -64,15 +261,82 @@ const rollbackCommand = {
64
261
  { command: 'claude-flow deployment rollback -e prod -v v3.0.0', description: 'Rollback to specific version' },
65
262
  ],
66
263
  action: async (ctx) => {
67
- // #1425: This command is not yet implemented — was returning hardcoded fake responses
68
- output.writeln();
69
- output.printError('deployment rollback is not yet implemented');
70
- output.writeln(output.dim('This command will be implemented in a future release.'));
71
- output.writeln(output.dim('Track progress: https://github.com/ruvnet/claude-flow/issues/1425'));
72
- return { success: false, exitCode: 1 };
264
+ try {
265
+ const envName = String(ctx.flags['env'] || '');
266
+ if (!envName) {
267
+ output.printError('Environment is required', 'Use --env or -e to specify');
268
+ return { success: false, exitCode: 1 };
269
+ }
270
+ const targetVersion = ctx.flags['version'] ? String(ctx.flags['version']) : null;
271
+ const state = loadDeploymentState(ctx.cwd);
272
+ // Find deployments for this environment in reverse chronological order
273
+ const envHistory = state.history
274
+ .filter(r => r.environment === envName && r.status === 'deployed')
275
+ .reverse();
276
+ if (envHistory.length < 2 && !targetVersion) {
277
+ output.printWarning('No previous deployment to rollback to');
278
+ return { success: false, exitCode: 1 };
279
+ }
280
+ let rollbackTo;
281
+ if (targetVersion) {
282
+ rollbackTo = envHistory.find(r => r.version === targetVersion);
283
+ if (!rollbackTo) {
284
+ output.printError(`Version '${targetVersion}' not found in deployment history for '${envName}'`);
285
+ return { success: false, exitCode: 1 };
286
+ }
287
+ }
288
+ else {
289
+ // Rollback to the deployment before the most recent one
290
+ rollbackTo = envHistory[1];
291
+ }
292
+ // Mark current active deployment for this env as rolled-back
293
+ const current = envHistory[0];
294
+ if (current) {
295
+ const idx = state.history.findIndex(r => r.id === current.id);
296
+ if (idx >= 0) {
297
+ state.history[idx].status = 'rolled-back';
298
+ }
299
+ }
300
+ // Create a new record for the rollback
301
+ const record = {
302
+ id: generateId(),
303
+ environment: envName,
304
+ version: rollbackTo.version,
305
+ status: 'deployed',
306
+ timestamp: new Date().toISOString(),
307
+ description: `Rollback from ${current?.version || 'unknown'} to ${rollbackTo.version}`,
308
+ };
309
+ state.history.push(record);
310
+ state.activeDeployment = record.id;
311
+ saveDeploymentState(ctx.cwd, state);
312
+ output.writeln();
313
+ output.printSuccess(`Rolled back ${envName} to version ${rollbackTo.version}`);
314
+ output.writeln();
315
+ output.printTable({
316
+ columns: [
317
+ { key: 'field', header: 'Field' },
318
+ { key: 'value', header: 'Value' },
319
+ ],
320
+ data: [
321
+ { field: 'Rollback ID', value: record.id },
322
+ { field: 'Environment', value: envName },
323
+ { field: 'From Version', value: current?.version || 'unknown' },
324
+ { field: 'To Version', value: rollbackTo.version },
325
+ { field: 'Timestamp', value: record.timestamp },
326
+ ],
327
+ });
328
+ return { success: true, data: record };
329
+ }
330
+ catch (err) {
331
+ const msg = err instanceof Error ? err.message : String(err);
332
+ output.printError('Rollback failed', msg);
333
+ return { success: false, exitCode: 1 };
334
+ }
73
335
  },
74
336
  };
75
- // History subcommand
337
+ // ============================================
338
+ // History subcommand (logs)
339
+ // ============================================
76
340
  const historyCommand = {
77
341
  name: 'history',
78
342
  description: 'View deployment history',
@@ -85,44 +349,153 @@ const historyCommand = {
85
349
  { command: 'claude-flow deployment history -e prod', description: 'Production history' },
86
350
  ],
87
351
  action: async (ctx) => {
88
- // #1425: This command is not yet implemented — was returning hardcoded fake data
89
- output.writeln();
90
- output.printError('deployment history is not yet implemented');
91
- output.writeln(output.dim('This command will be implemented in a future release.'));
92
- output.writeln(output.dim('Track progress: https://github.com/ruvnet/claude-flow/issues/1425'));
93
- return { success: false, exitCode: 1 };
352
+ try {
353
+ const state = loadDeploymentState(ctx.cwd);
354
+ const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']) : null;
355
+ const limit = Number(ctx.flags['limit']) || 10;
356
+ let records = [...state.history].reverse();
357
+ if (filterEnv) {
358
+ records = records.filter(r => r.environment === filterEnv);
359
+ }
360
+ records = records.slice(0, limit);
361
+ output.writeln();
362
+ output.writeln(output.bold('Deployment History'));
363
+ if (filterEnv) {
364
+ output.writeln(output.dim(`Filtered by environment: ${filterEnv}`));
365
+ }
366
+ output.writeln();
367
+ if (records.length === 0) {
368
+ output.writeln(output.dim('No deployment history found'));
369
+ return { success: true };
370
+ }
371
+ output.printTable({
372
+ columns: [
373
+ { key: 'id', header: 'ID' },
374
+ { key: 'environment', header: 'Env' },
375
+ { key: 'version', header: 'Version' },
376
+ { key: 'status', header: 'Status' },
377
+ { key: 'timestamp', header: 'Time' },
378
+ { key: 'description', header: 'Description' },
379
+ ],
380
+ data: records.map(r => ({
381
+ ...r,
382
+ description: r.description || '-',
383
+ })),
384
+ });
385
+ output.writeln();
386
+ output.writeln(output.dim(`Showing ${records.length} of ${state.history.length} total records`));
387
+ return { success: true };
388
+ }
389
+ catch (err) {
390
+ const msg = err instanceof Error ? err.message : String(err);
391
+ output.printError('Failed to load history', msg);
392
+ return { success: false, exitCode: 1 };
393
+ }
94
394
  },
95
395
  };
396
+ // ============================================
96
397
  // Environments subcommand
398
+ // ============================================
97
399
  const environmentsCommand = {
98
400
  name: 'environments',
99
401
  description: 'Manage deployment environments',
100
402
  aliases: ['envs'],
101
403
  options: [
102
- { name: 'action', short: 'a', type: 'string', description: 'Action: list, create, delete', default: 'list' },
404
+ { name: 'action', short: 'a', type: 'string', description: 'Action: list, add, remove', default: 'list' },
103
405
  { name: 'name', short: 'n', type: 'string', description: 'Environment name' },
406
+ { name: 'type', short: 't', type: 'string', description: 'Environment type: local, staging, production', default: 'local' },
407
+ { name: 'url', short: 'u', type: 'string', description: 'Environment URL' },
104
408
  ],
105
409
  examples: [
106
410
  { command: 'claude-flow deployment environments', description: 'List environments' },
107
- { command: 'claude-flow deployment envs -a create -n preview', description: 'Create environment' },
411
+ { command: 'claude-flow deployment envs -a add -n preview -t staging', description: 'Add environment' },
412
+ { command: 'claude-flow deployment envs -a remove -n preview', description: 'Remove environment' },
108
413
  ],
109
414
  action: async (ctx) => {
110
- // #1425: This command is not yet implemented — was returning hardcoded fake data
111
- output.writeln();
112
- output.printError('deployment environments is not yet implemented');
113
- output.writeln(output.dim('This command will be implemented in a future release.'));
114
- output.writeln(output.dim('Track progress: https://github.com/ruvnet/claude-flow/issues/1425'));
115
- return { success: false, exitCode: 1 };
415
+ try {
416
+ const action = String(ctx.flags['action'] || 'list');
417
+ const state = loadDeploymentState(ctx.cwd);
418
+ if (action === 'list') {
419
+ const envs = Object.values(state.environments);
420
+ output.writeln();
421
+ output.writeln(output.bold('Deployment Environments'));
422
+ output.writeln();
423
+ if (envs.length === 0) {
424
+ output.writeln(output.dim('No environments configured. Use --action add to create one.'));
425
+ return { success: true };
426
+ }
427
+ output.printTable({
428
+ columns: [
429
+ { key: 'name', header: 'Name' },
430
+ { key: 'type', header: 'Type' },
431
+ { key: 'url', header: 'URL' },
432
+ { key: 'createdAt', header: 'Created' },
433
+ ],
434
+ data: envs.map(e => ({ name: e.name, type: e.type, url: e.url || '-', createdAt: e.createdAt })),
435
+ });
436
+ return { success: true };
437
+ }
438
+ if (action === 'add') {
439
+ const name = ctx.flags['name'] ? String(ctx.flags['name']) : null;
440
+ if (!name) {
441
+ output.printError('Environment name is required', 'Use --name or -n to specify');
442
+ return { success: false, exitCode: 1 };
443
+ }
444
+ if (state.environments[name]) {
445
+ output.printWarning(`Environment '${name}' already exists`);
446
+ return { success: false, exitCode: 1 };
447
+ }
448
+ const envType = String(ctx.flags['type'] || 'local');
449
+ const url = ctx.flags['url'] ? String(ctx.flags['url']) : undefined;
450
+ state.environments[name] = {
451
+ name,
452
+ type: envType,
453
+ url,
454
+ createdAt: new Date().toISOString(),
455
+ };
456
+ saveDeploymentState(ctx.cwd, state);
457
+ output.writeln();
458
+ output.printSuccess(`Added environment '${name}' (${envType})`);
459
+ if (url) {
460
+ output.writeln(output.dim(` URL: ${url}`));
461
+ }
462
+ return { success: true };
463
+ }
464
+ if (action === 'remove') {
465
+ const name = ctx.flags['name'] ? String(ctx.flags['name']) : null;
466
+ if (!name) {
467
+ output.printError('Environment name is required', 'Use --name or -n to specify');
468
+ return { success: false, exitCode: 1 };
469
+ }
470
+ if (!state.environments[name]) {
471
+ output.printWarning(`Environment '${name}' not found`);
472
+ return { success: false, exitCode: 1 };
473
+ }
474
+ delete state.environments[name];
475
+ saveDeploymentState(ctx.cwd, state);
476
+ output.writeln();
477
+ output.printSuccess(`Removed environment '${name}'`);
478
+ return { success: true };
479
+ }
480
+ output.printError(`Unknown action '${action}'`, 'Valid actions: list, add, remove');
481
+ return { success: false, exitCode: 1 };
482
+ }
483
+ catch (err) {
484
+ const msg = err instanceof Error ? err.message : String(err);
485
+ output.printError('Environments command failed', msg);
486
+ return { success: false, exitCode: 1 };
487
+ }
116
488
  },
117
489
  };
490
+ // ============================================
118
491
  // Logs subcommand
492
+ // ============================================
119
493
  const logsCommand = {
120
494
  name: 'logs',
121
495
  description: 'View deployment logs',
122
496
  options: [
123
497
  { name: 'deployment', short: 'd', type: 'string', description: 'Deployment ID' },
124
498
  { name: 'env', short: 'e', type: 'string', description: 'Environment' },
125
- { name: 'follow', short: 'f', type: 'boolean', description: 'Follow log output' },
126
499
  { name: 'lines', short: 'n', type: 'number', description: 'Number of lines', default: '50' },
127
500
  ],
128
501
  examples: [
@@ -130,24 +503,142 @@ const logsCommand = {
130
503
  { command: 'claude-flow deployment logs -d dep-123', description: 'View specific deployment' },
131
504
  ],
132
505
  action: async (ctx) => {
133
- // #1425: This command is not yet implemented — was returning hardcoded fake logs
134
- output.writeln();
135
- output.printError('deployment logs is not yet implemented');
136
- output.writeln(output.dim('This command will be implemented in a future release.'));
137
- output.writeln(output.dim('Track progress: https://github.com/ruvnet/claude-flow/issues/1425'));
138
- return { success: false, exitCode: 1 };
506
+ try {
507
+ const state = loadDeploymentState(ctx.cwd);
508
+ const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']) : null;
509
+ const deploymentId = ctx.flags['deployment'] ? String(ctx.flags['deployment']) : null;
510
+ const limit = Number(ctx.flags['lines']) || 50;
511
+ output.writeln();
512
+ output.writeln(output.bold('Deployment Logs'));
513
+ output.writeln();
514
+ let records = [...state.history].reverse();
515
+ if (deploymentId) {
516
+ records = records.filter(r => r.id === deploymentId);
517
+ if (records.length === 0) {
518
+ output.printWarning(`Deployment '${deploymentId}' not found`);
519
+ return { success: false, exitCode: 1 };
520
+ }
521
+ }
522
+ if (filterEnv) {
523
+ records = records.filter(r => r.environment === filterEnv);
524
+ }
525
+ records = records.slice(0, limit);
526
+ if (records.length === 0) {
527
+ output.writeln(output.dim('No deployment logs found'));
528
+ return { success: true };
529
+ }
530
+ output.printTable({
531
+ columns: [
532
+ { key: 'id', header: 'ID' },
533
+ { key: 'environment', header: 'Env' },
534
+ { key: 'version', header: 'Version' },
535
+ { key: 'status', header: 'Status' },
536
+ { key: 'timestamp', header: 'Time' },
537
+ { key: 'description', header: 'Description' },
538
+ ],
539
+ data: records.map(r => ({
540
+ ...r,
541
+ description: r.description || '-',
542
+ })),
543
+ });
544
+ output.writeln();
545
+ output.writeln(output.dim(`${records.length} entries shown`));
546
+ return { success: true };
547
+ }
548
+ catch (err) {
549
+ const msg = err instanceof Error ? err.message : String(err);
550
+ output.printError('Failed to load logs', msg);
551
+ return { success: false, exitCode: 1 };
552
+ }
553
+ },
554
+ };
555
+ // ============================================
556
+ // Release subcommand
557
+ // ============================================
558
+ const releaseCommand = {
559
+ name: 'release',
560
+ description: 'Create a new release deployment',
561
+ options: [
562
+ { name: 'version', short: 'v', type: 'string', description: 'Release version' },
563
+ { name: 'env', short: 'e', type: 'string', description: 'Target environment', default: 'production' },
564
+ { name: 'description', short: 'd', type: 'string', description: 'Release description' },
565
+ ],
566
+ examples: [
567
+ { command: 'claude-flow deployment release -v 3.5.0', description: 'Release version 3.5.0' },
568
+ { command: 'claude-flow deployment release -v 3.5.0 -d "Major update"', description: 'Release with description' },
569
+ ],
570
+ action: async (ctx) => {
571
+ try {
572
+ const envName = String(ctx.flags['env'] || 'production');
573
+ const description = ctx.flags['description'] ? String(ctx.flags['description']) : undefined;
574
+ let version = ctx.flags['version'] ? String(ctx.flags['version']) : null;
575
+ if (!version) {
576
+ const pkgVersion = readProjectVersion(ctx.cwd);
577
+ if (!pkgVersion) {
578
+ output.printError('Version is required', 'Use --version or -v, or ensure package.json has a version field');
579
+ return { success: false, exitCode: 1 };
580
+ }
581
+ version = pkgVersion;
582
+ }
583
+ const state = loadDeploymentState(ctx.cwd);
584
+ // Ensure environment exists
585
+ if (!state.environments[envName]) {
586
+ state.environments[envName] = {
587
+ name: envName,
588
+ type: envName === 'prod' || envName === 'production' ? 'production' : 'staging',
589
+ createdAt: new Date().toISOString(),
590
+ };
591
+ }
592
+ const record = {
593
+ id: generateId(),
594
+ environment: envName,
595
+ version,
596
+ status: 'deployed',
597
+ timestamp: new Date().toISOString(),
598
+ description: description || `Release ${version}`,
599
+ };
600
+ state.history.push(record);
601
+ state.activeDeployment = record.id;
602
+ saveDeploymentState(ctx.cwd, state);
603
+ output.writeln();
604
+ output.printSuccess(`Released version ${version} to ${envName}`);
605
+ output.writeln();
606
+ output.printTable({
607
+ columns: [
608
+ { key: 'field', header: 'Field' },
609
+ { key: 'value', header: 'Value' },
610
+ ],
611
+ data: [
612
+ { field: 'Release ID', value: record.id },
613
+ { field: 'Environment', value: envName },
614
+ { field: 'Version', value: version },
615
+ { field: 'Status', value: record.status },
616
+ { field: 'Timestamp', value: record.timestamp },
617
+ { field: 'Description', value: record.description || '-' },
618
+ ],
619
+ });
620
+ return { success: true, data: record };
621
+ }
622
+ catch (err) {
623
+ const msg = err instanceof Error ? err.message : String(err);
624
+ output.printError('Release failed', msg);
625
+ return { success: false, exitCode: 1 };
626
+ }
139
627
  },
140
628
  };
629
+ // ============================================
141
630
  // Main deployment command
631
+ // ============================================
142
632
  export const deploymentCommand = {
143
633
  name: 'deployment',
144
634
  description: 'Deployment management, environments, rollbacks',
145
635
  aliases: ['deploy'],
146
- subcommands: [deployCommand, statusCommand, rollbackCommand, historyCommand, environmentsCommand, logsCommand],
636
+ subcommands: [deployCommand, statusCommand, rollbackCommand, historyCommand, environmentsCommand, logsCommand, releaseCommand],
147
637
  examples: [
148
638
  { command: 'claude-flow deployment deploy -e prod', description: 'Deploy to production' },
149
639
  { command: 'claude-flow deployment status', description: 'Check all environments' },
150
640
  { command: 'claude-flow deployment rollback -e prod', description: 'Rollback production' },
641
+ { command: 'claude-flow deployment release -v 3.5.0', description: 'Create a release' },
151
642
  ],
152
643
  action: async () => {
153
644
  output.writeln();
@@ -162,6 +653,7 @@ export const deploymentCommand = {
162
653
  'history - View deployment history',
163
654
  'environments - Manage deployment environments',
164
655
  'logs - View deployment logs',
656
+ 'release - Create a new release',
165
657
  ]);
166
658
  output.writeln();
167
659
  output.writeln('Features:');
@@ -172,7 +664,7 @@ export const deploymentCommand = {
172
664
  'Deployment previews for PRs',
173
665
  ]);
174
666
  output.writeln();
175
- output.writeln(output.dim('Created with ❤️ by ruv.io'));
667
+ output.writeln(output.dim('Created with love by ruv.io'));
176
668
  return { success: true };
177
669
  },
178
670
  };