agileflow 2.89.3 → 2.90.1

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 (37) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/lib/placeholder-registry.js +617 -0
  3. package/lib/smart-json-file.js +228 -1
  4. package/lib/table-formatter.js +519 -0
  5. package/lib/transient-status.js +374 -0
  6. package/lib/ui-manager.js +612 -0
  7. package/lib/validate-args.js +213 -0
  8. package/lib/validate-names.js +143 -0
  9. package/lib/validate-paths.js +434 -0
  10. package/lib/validate.js +37 -737
  11. package/package.json +3 -1
  12. package/scripts/check-update.js +17 -3
  13. package/scripts/lib/sessionRegistry.js +678 -0
  14. package/scripts/session-manager.js +77 -10
  15. package/scripts/tui/App.js +151 -0
  16. package/scripts/tui/index.js +31 -0
  17. package/scripts/tui/lib/crashRecovery.js +304 -0
  18. package/scripts/tui/lib/eventStream.js +309 -0
  19. package/scripts/tui/lib/keyboard.js +261 -0
  20. package/scripts/tui/lib/loopControl.js +371 -0
  21. package/scripts/tui/panels/OutputPanel.js +242 -0
  22. package/scripts/tui/panels/SessionPanel.js +170 -0
  23. package/scripts/tui/panels/TracePanel.js +298 -0
  24. package/scripts/tui/simple-tui.js +390 -0
  25. package/tools/cli/commands/config.js +7 -31
  26. package/tools/cli/commands/doctor.js +28 -39
  27. package/tools/cli/commands/list.js +47 -35
  28. package/tools/cli/commands/status.js +20 -38
  29. package/tools/cli/commands/tui.js +59 -0
  30. package/tools/cli/commands/uninstall.js +12 -39
  31. package/tools/cli/installers/core/installer.js +13 -0
  32. package/tools/cli/lib/command-context.js +382 -0
  33. package/tools/cli/lib/config-manager.js +394 -0
  34. package/tools/cli/lib/ide-registry.js +186 -0
  35. package/tools/cli/lib/npm-utils.js +17 -3
  36. package/tools/cli/lib/self-update.js +148 -0
  37. package/tools/cli/lib/validation-middleware.js +491 -0
@@ -0,0 +1,382 @@
1
+ /**
2
+ * AgileFlow CLI - Command Context & Middleware Pipeline
3
+ *
4
+ * Provides a middleware system for CLI commands to reduce boilerplate
5
+ * and ensure consistent behavior across all commands.
6
+ *
7
+ * Usage:
8
+ * const { createCommand, middleware } = require('./lib/command-context');
9
+ * module.exports = createCommand({
10
+ * name: 'mycommand',
11
+ * middleware: [middleware.resolveDirectory, middleware.requireInstalled],
12
+ * action: async (ctx) => { ... }
13
+ * });
14
+ */
15
+
16
+ const path = require('path');
17
+ const { Installer } = require('../installers/core/installer');
18
+ const { displayLogo, displaySection, warning, error } = require('./ui');
19
+ const { ErrorHandler } = require('./error-handler');
20
+ const { validateOptions, schemas } = require('./validation-middleware');
21
+
22
+ // Singleton installer instance
23
+ let _installer = null;
24
+ function getInstaller() {
25
+ if (!_installer) {
26
+ _installer = new Installer();
27
+ }
28
+ return _installer;
29
+ }
30
+
31
+ /**
32
+ * Command context passed to action handlers
33
+ * @typedef {Object} CommandContext
34
+ * @property {Object} options - Parsed command options
35
+ * @property {string} directory - Resolved project directory
36
+ * @property {Object} [status] - Installation status (if requireInstalled middleware used)
37
+ * @property {Installer} installer - Installer instance
38
+ * @property {ErrorHandler} errorHandler - Error handler instance
39
+ * @property {Object} meta - Command metadata
40
+ */
41
+
42
+ /**
43
+ * Middleware function signature
44
+ * @typedef {Function} MiddlewareFunction
45
+ * @param {CommandContext} ctx - Command context
46
+ * @param {Function} next - Call to continue to next middleware
47
+ * @returns {Promise<void>}
48
+ */
49
+
50
+ /**
51
+ * Command definition
52
+ * @typedef {Object} CommandDefinition
53
+ * @property {string} name - Command name
54
+ * @property {string} description - Command description
55
+ * @property {Array} [options] - Commander.js options
56
+ * @property {Array} [arguments] - Commander.js arguments
57
+ * @property {MiddlewareFunction[]} [middleware] - Middleware functions
58
+ * @property {Object} [validate] - Validation schemas
59
+ * @property {Function} action - Action handler
60
+ */
61
+
62
+ /**
63
+ * Built-in middleware functions
64
+ */
65
+ const middleware = {
66
+ /**
67
+ * Resolves the directory option to an absolute path
68
+ * @param {CommandContext} ctx
69
+ * @param {Function} next
70
+ */
71
+ async resolveDirectory(ctx, next) {
72
+ ctx.directory = path.resolve(ctx.options.directory || '.');
73
+ await next();
74
+ },
75
+
76
+ /**
77
+ * Displays the AgileFlow logo
78
+ * @param {CommandContext} ctx
79
+ * @param {Function} next
80
+ */
81
+ async displayLogo(ctx, next) {
82
+ if (!ctx.options.json && !ctx.options.quiet) {
83
+ displayLogo();
84
+ }
85
+ await next();
86
+ },
87
+
88
+ /**
89
+ * Requires AgileFlow to be installed in the directory
90
+ * @param {CommandContext} ctx
91
+ * @param {Function} next
92
+ */
93
+ async requireInstalled(ctx, next) {
94
+ const installer = getInstaller();
95
+ ctx.status = await installer.getStatus(ctx.directory);
96
+
97
+ if (!ctx.status.installed) {
98
+ if (ctx.options.json) {
99
+ console.log(JSON.stringify({ error: 'Not installed' }));
100
+ process.exit(1);
101
+ }
102
+
103
+ ctx.errorHandler.warning(
104
+ 'No AgileFlow installation found',
105
+ 'Initialize AgileFlow first',
106
+ 'npx agileflow setup'
107
+ );
108
+ return; // Don't continue to next middleware
109
+ }
110
+
111
+ await next();
112
+ },
113
+
114
+ /**
115
+ * Checks installation status but doesn't require it
116
+ * @param {CommandContext} ctx
117
+ * @param {Function} next
118
+ */
119
+ async checkInstallation(ctx, next) {
120
+ const installer = getInstaller();
121
+ ctx.status = await installer.getStatus(ctx.directory);
122
+ await next();
123
+ },
124
+
125
+ /**
126
+ * Adds validation middleware
127
+ * @param {Object} validationSchemas - Schemas to validate against
128
+ * @returns {MiddlewareFunction}
129
+ */
130
+ validate(validationSchemas) {
131
+ return async (ctx, next) => {
132
+ const result = validateOptions(ctx.options, validationSchemas, ctx.directory);
133
+
134
+ if (!result.ok) {
135
+ const errorList = result.errors.map(e => ` • ${e}`).join('\n');
136
+ ctx.errorHandler.warning(
137
+ 'Input validation failed',
138
+ `Please fix the following:\n${errorList}`,
139
+ 'Check command help with --help'
140
+ );
141
+ return;
142
+ }
143
+
144
+ // Merge validated data into options
145
+ ctx.options = { ...ctx.options, ...result.data };
146
+ await next();
147
+ };
148
+ },
149
+
150
+ /**
151
+ * Displays a section header
152
+ * @param {string} title - Section title
153
+ * @param {Function} [subtitleFn] - Function to generate subtitle from ctx
154
+ * @returns {MiddlewareFunction}
155
+ */
156
+ section(title, subtitleFn) {
157
+ return async (ctx, next) => {
158
+ if (!ctx.options.json && !ctx.options.quiet) {
159
+ const subtitle = subtitleFn ? subtitleFn(ctx) : undefined;
160
+ displaySection(title, subtitle);
161
+ }
162
+ await next();
163
+ };
164
+ },
165
+ };
166
+
167
+ /**
168
+ * Execute middleware pipeline
169
+ * @param {MiddlewareFunction[]} middlewares - Array of middleware functions
170
+ * @param {CommandContext} ctx - Command context
171
+ * @returns {Promise<boolean>} - True if pipeline completed
172
+ */
173
+ async function executeMiddleware(middlewares, ctx) {
174
+ let index = 0;
175
+ let completed = false;
176
+
177
+ const next = async () => {
178
+ if (index < middlewares.length) {
179
+ const middleware = middlewares[index++];
180
+ await middleware(ctx, next);
181
+ } else {
182
+ completed = true;
183
+ }
184
+ };
185
+
186
+ await next();
187
+ return completed;
188
+ }
189
+
190
+ /**
191
+ * Create a command with middleware support
192
+ * @param {CommandDefinition} definition - Command definition
193
+ * @returns {Object} Commander.js compatible command object
194
+ */
195
+ function createCommand(definition) {
196
+ const {
197
+ name,
198
+ description,
199
+ options = [],
200
+ arguments: args = [],
201
+ middleware: mw = [],
202
+ validate,
203
+ action,
204
+ } = definition;
205
+
206
+ // Build middleware pipeline
207
+ const pipeline = [...mw];
208
+
209
+ // Add validation middleware if schemas provided
210
+ if (validate) {
211
+ pipeline.unshift(middleware.validate(validate));
212
+ }
213
+
214
+ // Wrapped action with middleware pipeline
215
+ const wrappedAction = async (...actionArgs) => {
216
+ // Extract options from last argument (Commander.js pattern)
217
+ const opts = actionArgs[actionArgs.length - 1];
218
+
219
+ // Create context
220
+ const ctx = {
221
+ options: { ...opts },
222
+ directory: opts.directory ? path.resolve(opts.directory) : process.cwd(),
223
+ installer: getInstaller(),
224
+ errorHandler: new ErrorHandler(name),
225
+ meta: {
226
+ name,
227
+ description,
228
+ args: actionArgs.slice(0, -1),
229
+ },
230
+ };
231
+
232
+ try {
233
+ // Execute middleware pipeline
234
+ const completed = await executeMiddleware(pipeline, ctx);
235
+
236
+ // Only run action if middleware completed
237
+ if (completed) {
238
+ await action(ctx);
239
+ }
240
+ } catch (err) {
241
+ // Handle errors
242
+ if (ctx.options.json) {
243
+ console.log(JSON.stringify({ error: err.message }));
244
+ process.exit(1);
245
+ }
246
+
247
+ ctx.errorHandler.critical(
248
+ `${name} failed`,
249
+ 'Check error message for details',
250
+ `npx agileflow ${name} --help`,
251
+ err
252
+ );
253
+ }
254
+ };
255
+
256
+ return {
257
+ name,
258
+ description,
259
+ options,
260
+ arguments: args,
261
+ action: wrappedAction,
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Hook types for command lifecycle
267
+ * @typedef {Object} Hooks
268
+ * @property {Function[]} beforeAction - Run before action
269
+ * @property {Function[]} afterAction - Run after action
270
+ * @property {Function[]} onError - Run on error
271
+ */
272
+
273
+ /**
274
+ * Command registry for managing hooks
275
+ */
276
+ class CommandRegistry {
277
+ constructor() {
278
+ this.hooks = {
279
+ beforeAction: [],
280
+ afterAction: [],
281
+ onError: [],
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Register a beforeAction hook
287
+ * @param {Function} fn - Hook function
288
+ */
289
+ beforeAction(fn) {
290
+ this.hooks.beforeAction.push(fn);
291
+ return this;
292
+ }
293
+
294
+ /**
295
+ * Register an afterAction hook
296
+ * @param {Function} fn - Hook function
297
+ */
298
+ afterAction(fn) {
299
+ this.hooks.afterAction.push(fn);
300
+ return this;
301
+ }
302
+
303
+ /**
304
+ * Register an error handler hook
305
+ * @param {Function} fn - Hook function
306
+ */
307
+ onError(fn) {
308
+ this.hooks.onError.push(fn);
309
+ return this;
310
+ }
311
+
312
+ /**
313
+ * Execute beforeAction hooks
314
+ * @param {CommandContext} ctx
315
+ */
316
+ async runBeforeAction(ctx) {
317
+ for (const hook of this.hooks.beforeAction) {
318
+ await hook(ctx);
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Execute afterAction hooks
324
+ * @param {CommandContext} ctx
325
+ * @param {*} result - Action result
326
+ */
327
+ async runAfterAction(ctx, result) {
328
+ for (const hook of this.hooks.afterAction) {
329
+ await hook(ctx, result);
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Execute error hooks
335
+ * @param {CommandContext} ctx
336
+ * @param {Error} error
337
+ */
338
+ async runOnError(ctx, error) {
339
+ for (const hook of this.hooks.onError) {
340
+ await hook(ctx, error);
341
+ }
342
+ }
343
+ }
344
+
345
+ // Global registry instance
346
+ const registry = new CommandRegistry();
347
+
348
+ /**
349
+ * Common middleware combinations for different command types
350
+ */
351
+ const presets = {
352
+ /**
353
+ * Standard command that requires installation
354
+ */
355
+ standard: [middleware.resolveDirectory, middleware.displayLogo, middleware.requireInstalled],
356
+
357
+ /**
358
+ * Command that just needs directory resolution
359
+ */
360
+ simple: [middleware.resolveDirectory, middleware.displayLogo],
361
+
362
+ /**
363
+ * Command with JSON output support (no logo)
364
+ */
365
+ json: [middleware.resolveDirectory, middleware.checkInstallation],
366
+
367
+ /**
368
+ * Command that doesn't need installation
369
+ */
370
+ setup: [middleware.resolveDirectory, middleware.displayLogo],
371
+ };
372
+
373
+ module.exports = {
374
+ createCommand,
375
+ middleware,
376
+ executeMiddleware,
377
+ CommandRegistry,
378
+ registry,
379
+ presets,
380
+ getInstaller,
381
+ schemas, // Re-export for convenience
382
+ };