agileflow 2.89.3 → 2.90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +3 -3
  3. package/lib/placeholder-registry.js +617 -0
  4. package/lib/smart-json-file.js +205 -1
  5. package/lib/table-formatter.js +504 -0
  6. package/lib/transient-status.js +374 -0
  7. package/lib/ui-manager.js +612 -0
  8. package/lib/validate-args.js +213 -0
  9. package/lib/validate-names.js +143 -0
  10. package/lib/validate-paths.js +434 -0
  11. package/lib/validate.js +37 -737
  12. package/package.json +4 -1
  13. package/scripts/check-update.js +16 -3
  14. package/scripts/lib/sessionRegistry.js +682 -0
  15. package/scripts/session-manager.js +77 -10
  16. package/scripts/tui/App.js +176 -0
  17. package/scripts/tui/index.js +75 -0
  18. package/scripts/tui/lib/crashRecovery.js +302 -0
  19. package/scripts/tui/lib/eventStream.js +316 -0
  20. package/scripts/tui/lib/keyboard.js +252 -0
  21. package/scripts/tui/lib/loopControl.js +371 -0
  22. package/scripts/tui/panels/OutputPanel.js +278 -0
  23. package/scripts/tui/panels/SessionPanel.js +178 -0
  24. package/scripts/tui/panels/TracePanel.js +333 -0
  25. package/src/core/commands/tui.md +91 -0
  26. package/tools/cli/commands/config.js +7 -30
  27. package/tools/cli/commands/doctor.js +18 -38
  28. package/tools/cli/commands/list.js +47 -35
  29. package/tools/cli/commands/status.js +13 -37
  30. package/tools/cli/commands/uninstall.js +9 -38
  31. package/tools/cli/installers/core/installer.js +13 -0
  32. package/tools/cli/lib/command-context.js +374 -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 +16 -3
  36. package/tools/cli/lib/self-update.js +148 -0
  37. package/tools/cli/lib/validation-middleware.js +491 -0
@@ -0,0 +1,374 @@
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 { name, description, options = [], arguments: args = [], middleware: mw = [], validate, action } = definition;
197
+
198
+ // Build middleware pipeline
199
+ const pipeline = [...mw];
200
+
201
+ // Add validation middleware if schemas provided
202
+ if (validate) {
203
+ pipeline.unshift(middleware.validate(validate));
204
+ }
205
+
206
+ // Wrapped action with middleware pipeline
207
+ const wrappedAction = async (...actionArgs) => {
208
+ // Extract options from last argument (Commander.js pattern)
209
+ const opts = actionArgs[actionArgs.length - 1];
210
+
211
+ // Create context
212
+ const ctx = {
213
+ options: { ...opts },
214
+ directory: opts.directory ? path.resolve(opts.directory) : process.cwd(),
215
+ installer: getInstaller(),
216
+ errorHandler: new ErrorHandler(name),
217
+ meta: {
218
+ name,
219
+ description,
220
+ args: actionArgs.slice(0, -1),
221
+ },
222
+ };
223
+
224
+ try {
225
+ // Execute middleware pipeline
226
+ const completed = await executeMiddleware(pipeline, ctx);
227
+
228
+ // Only run action if middleware completed
229
+ if (completed) {
230
+ await action(ctx);
231
+ }
232
+ } catch (err) {
233
+ // Handle errors
234
+ if (ctx.options.json) {
235
+ console.log(JSON.stringify({ error: err.message }));
236
+ process.exit(1);
237
+ }
238
+
239
+ ctx.errorHandler.critical(
240
+ `${name} failed`,
241
+ 'Check error message for details',
242
+ `npx agileflow ${name} --help`,
243
+ err
244
+ );
245
+ }
246
+ };
247
+
248
+ return {
249
+ name,
250
+ description,
251
+ options,
252
+ arguments: args,
253
+ action: wrappedAction,
254
+ };
255
+ }
256
+
257
+ /**
258
+ * Hook types for command lifecycle
259
+ * @typedef {Object} Hooks
260
+ * @property {Function[]} beforeAction - Run before action
261
+ * @property {Function[]} afterAction - Run after action
262
+ * @property {Function[]} onError - Run on error
263
+ */
264
+
265
+ /**
266
+ * Command registry for managing hooks
267
+ */
268
+ class CommandRegistry {
269
+ constructor() {
270
+ this.hooks = {
271
+ beforeAction: [],
272
+ afterAction: [],
273
+ onError: [],
274
+ };
275
+ }
276
+
277
+ /**
278
+ * Register a beforeAction hook
279
+ * @param {Function} fn - Hook function
280
+ */
281
+ beforeAction(fn) {
282
+ this.hooks.beforeAction.push(fn);
283
+ return this;
284
+ }
285
+
286
+ /**
287
+ * Register an afterAction hook
288
+ * @param {Function} fn - Hook function
289
+ */
290
+ afterAction(fn) {
291
+ this.hooks.afterAction.push(fn);
292
+ return this;
293
+ }
294
+
295
+ /**
296
+ * Register an error handler hook
297
+ * @param {Function} fn - Hook function
298
+ */
299
+ onError(fn) {
300
+ this.hooks.onError.push(fn);
301
+ return this;
302
+ }
303
+
304
+ /**
305
+ * Execute beforeAction hooks
306
+ * @param {CommandContext} ctx
307
+ */
308
+ async runBeforeAction(ctx) {
309
+ for (const hook of this.hooks.beforeAction) {
310
+ await hook(ctx);
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Execute afterAction hooks
316
+ * @param {CommandContext} ctx
317
+ * @param {*} result - Action result
318
+ */
319
+ async runAfterAction(ctx, result) {
320
+ for (const hook of this.hooks.afterAction) {
321
+ await hook(ctx, result);
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Execute error hooks
327
+ * @param {CommandContext} ctx
328
+ * @param {Error} error
329
+ */
330
+ async runOnError(ctx, error) {
331
+ for (const hook of this.hooks.onError) {
332
+ await hook(ctx, error);
333
+ }
334
+ }
335
+ }
336
+
337
+ // Global registry instance
338
+ const registry = new CommandRegistry();
339
+
340
+ /**
341
+ * Common middleware combinations for different command types
342
+ */
343
+ const presets = {
344
+ /**
345
+ * Standard command that requires installation
346
+ */
347
+ standard: [middleware.resolveDirectory, middleware.displayLogo, middleware.requireInstalled],
348
+
349
+ /**
350
+ * Command that just needs directory resolution
351
+ */
352
+ simple: [middleware.resolveDirectory, middleware.displayLogo],
353
+
354
+ /**
355
+ * Command with JSON output support (no logo)
356
+ */
357
+ json: [middleware.resolveDirectory, middleware.checkInstallation],
358
+
359
+ /**
360
+ * Command that doesn't need installation
361
+ */
362
+ setup: [middleware.resolveDirectory, middleware.displayLogo],
363
+ };
364
+
365
+ module.exports = {
366
+ createCommand,
367
+ middleware,
368
+ executeMiddleware,
369
+ CommandRegistry,
370
+ registry,
371
+ presets,
372
+ getInstaller,
373
+ schemas, // Re-export for convenience
374
+ };