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.
- package/CHANGELOG.md +5 -0
- package/README.md +3 -3
- package/lib/placeholder-registry.js +617 -0
- package/lib/smart-json-file.js +205 -1
- package/lib/table-formatter.js +504 -0
- package/lib/transient-status.js +374 -0
- package/lib/ui-manager.js +612 -0
- package/lib/validate-args.js +213 -0
- package/lib/validate-names.js +143 -0
- package/lib/validate-paths.js +434 -0
- package/lib/validate.js +37 -737
- package/package.json +4 -1
- package/scripts/check-update.js +16 -3
- package/scripts/lib/sessionRegistry.js +682 -0
- package/scripts/session-manager.js +77 -10
- package/scripts/tui/App.js +176 -0
- package/scripts/tui/index.js +75 -0
- package/scripts/tui/lib/crashRecovery.js +302 -0
- package/scripts/tui/lib/eventStream.js +316 -0
- package/scripts/tui/lib/keyboard.js +252 -0
- package/scripts/tui/lib/loopControl.js +371 -0
- package/scripts/tui/panels/OutputPanel.js +278 -0
- package/scripts/tui/panels/SessionPanel.js +178 -0
- package/scripts/tui/panels/TracePanel.js +333 -0
- package/src/core/commands/tui.md +91 -0
- package/tools/cli/commands/config.js +7 -30
- package/tools/cli/commands/doctor.js +18 -38
- package/tools/cli/commands/list.js +47 -35
- package/tools/cli/commands/status.js +13 -37
- package/tools/cli/commands/uninstall.js +9 -38
- package/tools/cli/installers/core/installer.js +13 -0
- package/tools/cli/lib/command-context.js +374 -0
- package/tools/cli/lib/config-manager.js +394 -0
- package/tools/cli/lib/ide-registry.js +186 -0
- package/tools/cli/lib/npm-utils.js +16 -3
- package/tools/cli/lib/self-update.js +148 -0
- 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
|
+
};
|