@auto-engineer/cli 0.13.3 → 0.14.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 (158) hide show
  1. package/CHANGELOG.md +5 -872
  2. package/dist/bin/auto.js +4 -4
  3. package/dist/bin/auto.js.map +1 -1
  4. package/dist/src/config-loader.d.ts +12 -0
  5. package/dist/src/config-loader.d.ts.map +1 -0
  6. package/dist/src/config-loader.js +24 -0
  7. package/dist/src/config-loader.js.map +1 -0
  8. package/dist/src/file-syncer/crypto/jwe-encryptor.d.ts.map +1 -0
  9. package/dist/src/{server/file-syncer → file-syncer}/crypto/jwe-encryptor.js +1 -1
  10. package/dist/src/file-syncer/crypto/jwe-encryptor.js.map +1 -0
  11. package/dist/src/file-syncer/crypto/provider-resolver.d.ts.map +1 -0
  12. package/dist/src/{server/file-syncer → file-syncer}/crypto/provider-resolver.js +2 -2
  13. package/dist/src/file-syncer/crypto/provider-resolver.js.map +1 -0
  14. package/dist/src/file-syncer/discovery/bareImports.d.ts +3 -0
  15. package/dist/src/file-syncer/discovery/bareImports.d.ts.map +1 -0
  16. package/dist/src/file-syncer/discovery/bareImports.js.map +1 -0
  17. package/dist/src/{server/file-syncer → file-syncer}/discovery/dts.d.ts +1 -1
  18. package/dist/src/file-syncer/discovery/dts.d.ts.map +1 -0
  19. package/dist/src/file-syncer/discovery/dts.js.map +1 -0
  20. package/dist/src/file-syncer/index.d.ts.map +1 -0
  21. package/dist/src/{server/file-syncer → file-syncer}/index.js +6 -6
  22. package/dist/src/file-syncer/index.js.map +1 -0
  23. package/dist/src/{server/file-syncer → file-syncer}/sync/resolveSyncFileSet.d.ts +1 -1
  24. package/dist/src/file-syncer/sync/resolveSyncFileSet.d.ts.map +1 -0
  25. package/dist/src/{server/file-syncer → file-syncer}/sync/resolveSyncFileSet.js +2 -2
  26. package/dist/src/file-syncer/sync/resolveSyncFileSet.js.map +1 -0
  27. package/dist/src/file-syncer/types/wire.d.ts.map +1 -0
  28. package/dist/src/file-syncer/types/wire.js.map +1 -0
  29. package/dist/src/{server/file-syncer → file-syncer}/utils/hash.d.ts +1 -1
  30. package/dist/src/file-syncer/utils/hash.d.ts.map +1 -0
  31. package/dist/src/file-syncer/utils/hash.js.map +1 -0
  32. package/dist/src/file-syncer/utils/path.d.ts.map +1 -0
  33. package/dist/src/{server/file-syncer → file-syncer}/utils/path.js +2 -2
  34. package/dist/src/file-syncer/utils/path.js.map +1 -0
  35. package/dist/src/index.d.ts +1 -1
  36. package/dist/src/index.d.ts.map +1 -1
  37. package/dist/src/index.js +294 -416
  38. package/dist/src/index.js.map +1 -1
  39. package/dist/tsconfig.tsbuildinfo +1 -1
  40. package/package.json +8 -50
  41. package/README.md +0 -283
  42. package/dist/src/dsl/index.d.ts +0 -71
  43. package/dist/src/dsl/index.d.ts.map +0 -1
  44. package/dist/src/dsl/index.js +0 -504
  45. package/dist/src/dsl/index.js.map +0 -1
  46. package/dist/src/dsl/types.d.ts +0 -39
  47. package/dist/src/dsl/types.d.ts.map +0 -1
  48. package/dist/src/dsl/types.js +0 -2
  49. package/dist/src/dsl/types.js.map +0 -1
  50. package/dist/src/dsl-exports.d.ts +0 -5
  51. package/dist/src/dsl-exports.d.ts.map +0 -1
  52. package/dist/src/dsl-exports.js +0 -4
  53. package/dist/src/dsl-exports.js.map +0 -1
  54. package/dist/src/plugin-loader.d.ts +0 -84
  55. package/dist/src/plugin-loader.d.ts.map +0 -1
  56. package/dist/src/plugin-loader.js +0 -801
  57. package/dist/src/plugin-loader.js.map +0 -1
  58. package/dist/src/server/command-metadata-service.d.ts +0 -27
  59. package/dist/src/server/command-metadata-service.d.ts.map +0 -1
  60. package/dist/src/server/command-metadata-service.js +0 -69
  61. package/dist/src/server/command-metadata-service.js.map +0 -1
  62. package/dist/src/server/command-registry.d.ts +0 -21
  63. package/dist/src/server/command-registry.d.ts.map +0 -1
  64. package/dist/src/server/command-registry.js +0 -58
  65. package/dist/src/server/command-registry.js.map +0 -1
  66. package/dist/src/server/config-loader.d.ts +0 -16
  67. package/dist/src/server/config-loader.d.ts.map +0 -1
  68. package/dist/src/server/config-loader.js +0 -106
  69. package/dist/src/server/config-loader.js.map +0 -1
  70. package/dist/src/server/event-processor.d.ts +0 -45
  71. package/dist/src/server/event-processor.d.ts.map +0 -1
  72. package/dist/src/server/event-processor.js +0 -287
  73. package/dist/src/server/event-processor.js.map +0 -1
  74. package/dist/src/server/file-syncer/crypto/jwe-encryptor.d.ts.map +0 -1
  75. package/dist/src/server/file-syncer/crypto/jwe-encryptor.js.map +0 -1
  76. package/dist/src/server/file-syncer/crypto/provider-resolver.d.ts.map +0 -1
  77. package/dist/src/server/file-syncer/crypto/provider-resolver.js.map +0 -1
  78. package/dist/src/server/file-syncer/discovery/bareImports.d.ts +0 -3
  79. package/dist/src/server/file-syncer/discovery/bareImports.d.ts.map +0 -1
  80. package/dist/src/server/file-syncer/discovery/bareImports.js.map +0 -1
  81. package/dist/src/server/file-syncer/discovery/dts.d.ts.map +0 -1
  82. package/dist/src/server/file-syncer/discovery/dts.js.map +0 -1
  83. package/dist/src/server/file-syncer/index.d.ts.map +0 -1
  84. package/dist/src/server/file-syncer/index.js.map +0 -1
  85. package/dist/src/server/file-syncer/sync/resolveSyncFileSet.d.ts.map +0 -1
  86. package/dist/src/server/file-syncer/sync/resolveSyncFileSet.js.map +0 -1
  87. package/dist/src/server/file-syncer/types/wire.d.ts.map +0 -1
  88. package/dist/src/server/file-syncer/types/wire.js.map +0 -1
  89. package/dist/src/server/file-syncer/utils/hash.d.ts.map +0 -1
  90. package/dist/src/server/file-syncer/utils/hash.js.map +0 -1
  91. package/dist/src/server/file-syncer/utils/path.d.ts.map +0 -1
  92. package/dist/src/server/file-syncer/utils/path.js.map +0 -1
  93. package/dist/src/server/http-routes.d.ts +0 -30
  94. package/dist/src/server/http-routes.d.ts.map +0 -1
  95. package/dist/src/server/http-routes.js +0 -394
  96. package/dist/src/server/http-routes.js.map +0 -1
  97. package/dist/src/server/sandbox-landing-page.html +0 -367
  98. package/dist/src/server/server.d.ts +0 -106
  99. package/dist/src/server/server.d.ts.map +0 -1
  100. package/dist/src/server/server.js +0 -255
  101. package/dist/src/server/server.js.map +0 -1
  102. package/dist/src/server/services/child-process-manager.d.ts +0 -27
  103. package/dist/src/server/services/child-process-manager.d.ts.map +0 -1
  104. package/dist/src/server/services/child-process-manager.js +0 -126
  105. package/dist/src/server/services/child-process-manager.js.map +0 -1
  106. package/dist/src/server/services/index.d.ts +0 -3
  107. package/dist/src/server/services/index.d.ts.map +0 -1
  108. package/dist/src/server/services/index.js +0 -2
  109. package/dist/src/server/services/index.js.map +0 -1
  110. package/dist/src/server/services/interface.d.ts +0 -6
  111. package/dist/src/server/services/interface.d.ts.map +0 -1
  112. package/dist/src/server/services/interface.js +0 -2
  113. package/dist/src/server/services/interface.js.map +0 -1
  114. package/dist/src/server/settled-tracker.d.ts +0 -29
  115. package/dist/src/server/settled-tracker.d.ts.map +0 -1
  116. package/dist/src/server/settled-tracker.js +0 -203
  117. package/dist/src/server/settled-tracker.js.map +0 -1
  118. package/dist/src/server/state-manager.d.ts +0 -24
  119. package/dist/src/server/state-manager.d.ts.map +0 -1
  120. package/dist/src/server/state-manager.js +0 -56
  121. package/dist/src/server/state-manager.js.map +0 -1
  122. package/dist/src/server/websocket-handler.d.ts +0 -5
  123. package/dist/src/server/websocket-handler.d.ts.map +0 -1
  124. package/dist/src/server/websocket-handler.js +0 -40
  125. package/dist/src/server/websocket-handler.js.map +0 -1
  126. package/dist/src/utils/analytics.d.ts +0 -21
  127. package/dist/src/utils/analytics.d.ts.map +0 -1
  128. package/dist/src/utils/analytics.js +0 -41
  129. package/dist/src/utils/analytics.js.map +0 -1
  130. package/dist/src/utils/config.d.ts +0 -11
  131. package/dist/src/utils/config.d.ts.map +0 -1
  132. package/dist/src/utils/config.js +0 -50
  133. package/dist/src/utils/config.js.map +0 -1
  134. package/dist/src/utils/correlation-id.d.ts +0 -3
  135. package/dist/src/utils/correlation-id.d.ts.map +0 -1
  136. package/dist/src/utils/correlation-id.js +0 -7
  137. package/dist/src/utils/correlation-id.js.map +0 -1
  138. package/dist/src/utils/errors.d.ts +0 -22
  139. package/dist/src/utils/errors.d.ts.map +0 -1
  140. package/dist/src/utils/errors.js +0 -50
  141. package/dist/src/utils/errors.js.map +0 -1
  142. package/dist/src/utils/get-package-version.d.ts +0 -7
  143. package/dist/src/utils/get-package-version.d.ts.map +0 -1
  144. package/dist/src/utils/get-package-version.js +0 -31
  145. package/dist/src/utils/get-package-version.js.map +0 -1
  146. package/dist/src/utils/terminal.d.ts +0 -13
  147. package/dist/src/utils/terminal.d.ts.map +0 -1
  148. package/dist/src/utils/terminal.js +0 -85
  149. package/dist/src/utils/terminal.js.map +0 -1
  150. /package/dist/src/{server/file-syncer → file-syncer}/crypto/jwe-encryptor.d.ts +0 -0
  151. /package/dist/src/{server/file-syncer → file-syncer}/crypto/provider-resolver.d.ts +0 -0
  152. /package/dist/src/{server/file-syncer → file-syncer}/discovery/bareImports.js +0 -0
  153. /package/dist/src/{server/file-syncer → file-syncer}/discovery/dts.js +0 -0
  154. /package/dist/src/{server/file-syncer → file-syncer}/index.d.ts +0 -0
  155. /package/dist/src/{server/file-syncer → file-syncer}/types/wire.d.ts +0 -0
  156. /package/dist/src/{server/file-syncer → file-syncer}/types/wire.js +0 -0
  157. /package/dist/src/{server/file-syncer → file-syncer}/utils/hash.js +0 -0
  158. /package/dist/src/{server/file-syncer → file-syncer}/utils/path.d.ts +0 -0
package/dist/src/index.js CHANGED
@@ -1,32 +1,87 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
2
+ import * as fs from 'node:fs';
3
+ import { createServer } from 'node:http';
4
+ import * as path from 'node:path';
5
+ import { fileURLToPath, pathToFileURL } from 'node:url';
6
+ import { PipelineServer } from '@auto-engineer/pipeline';
3
7
  import chalk from 'chalk';
8
+ import { Command } from 'commander';
4
9
  import * as dotenv from 'dotenv';
5
- import * as path from 'path';
6
- import * as fs from 'fs';
7
- import { fileURLToPath } from 'url';
8
- import Debug from 'debug';
9
- import { loadConfig, validateConfig } from './utils/config.js';
10
- import { handleError } from './utils/errors.js';
11
- import { createOutput } from './utils/terminal.js';
12
- import { Analytics } from './utils/analytics.js';
13
- import { PluginLoader } from './plugin-loader.js';
14
10
  import getPort, { portNumbers } from 'get-port';
15
- // Export DSL functions for use in auto.config.ts
16
- export { on, dispatch, fold, autoConfig } from './dsl/index.js';
17
- const debug = Debug('auto-engineer:cli');
18
- // Get version from package.json - works in both dev and production
19
- const getVersion = () => {
11
+ import createJiti from 'jiti';
12
+ import ora from 'ora';
13
+ import { Server as SocketIOServer } from 'socket.io';
14
+ import { FileSyncer } from './file-syncer/index.js';
15
+ dotenv.config();
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+ function isValidHandler(item) {
19
+ return item !== null && item !== undefined && typeof item === 'object' && 'handle' in item && 'name' in item;
20
+ }
21
+ function extractCommandHandler(module, filename, packageName) {
22
+ if (isValidHandler(module.default)) {
23
+ return module.default;
24
+ }
25
+ if (isValidHandler(module.commandHandler)) {
26
+ return module.commandHandler;
27
+ }
28
+ if (isValidHandler(module.handler)) {
29
+ return module.handler;
30
+ }
31
+ for (const [key, value] of Object.entries(module)) {
32
+ if (isValidHandler(value)) {
33
+ console.log(chalk.gray(` Found handler via named export "${key}" in ${filename}`));
34
+ return value;
35
+ }
36
+ }
37
+ console.log(chalk.gray(` No valid handler found in ${filename} from ${packageName}`));
38
+ return null;
39
+ }
40
+ async function detectCommandsByConvention(packageName) {
41
+ const handlers = [];
42
+ const packageShortName = packageName.replace('@auto-engineer/', '');
43
+ const workspaceCommandsPath = path.join(process.cwd(), 'packages', packageShortName, 'dist', 'commands');
44
+ const workspaceSrcCommandsPath = path.join(process.cwd(), 'packages', packageShortName, 'dist', 'src', 'commands');
45
+ const nodeModulesCommandsPath = path.join(process.cwd(), 'node_modules', packageName, 'dist', 'commands');
46
+ const nodeModulesSrcCommandsPath = path.join(process.cwd(), 'node_modules', packageName, 'dist', 'src', 'commands');
47
+ let commandsDir = null;
48
+ if (fs.existsSync(workspaceCommandsPath)) {
49
+ commandsDir = workspaceCommandsPath;
50
+ }
51
+ else if (fs.existsSync(workspaceSrcCommandsPath)) {
52
+ commandsDir = workspaceSrcCommandsPath;
53
+ }
54
+ else if (fs.existsSync(nodeModulesCommandsPath)) {
55
+ commandsDir = nodeModulesCommandsPath;
56
+ }
57
+ else if (fs.existsSync(nodeModulesSrcCommandsPath)) {
58
+ commandsDir = nodeModulesSrcCommandsPath;
59
+ }
60
+ if (!commandsDir) {
61
+ return handlers;
62
+ }
63
+ const commandFiles = fs.readdirSync(commandsDir).filter((file) => file.endsWith('.js'));
64
+ for (const filename of commandFiles) {
65
+ const filePath = path.join(commandsDir, filename);
66
+ const fileUrl = pathToFileURL(filePath).href;
67
+ try {
68
+ const module = (await import(fileUrl));
69
+ const handler = extractCommandHandler(module, filename, packageName);
70
+ if (handler) {
71
+ handlers.push(handler);
72
+ }
73
+ }
74
+ catch (error) {
75
+ console.warn(chalk.yellow(` Failed to load command file ${filename}:`), error);
76
+ }
77
+ }
78
+ return handlers;
79
+ }
80
+ function getVersion() {
20
81
  try {
21
- // Try to read from package.json relative to this file
22
- const __filename = fileURLToPath(import.meta.url);
23
- const __dirname = path.dirname(__filename);
24
- // Try multiple possible locations for package.json
25
- // In dev: src/index.ts -> ../package.json
26
- // In dist: dist/src/index.js -> ../../package.json
27
82
  const possiblePaths = [
28
- path.join(__dirname, '..', 'package.json'), // dev environment
29
- path.join(__dirname, '..', '..', 'package.json'), // dist build
83
+ path.join(__dirname, '..', 'package.json'),
84
+ path.join(__dirname, '..', '..', 'package.json'),
30
85
  ];
31
86
  for (const packageJsonPath of possiblePaths) {
32
87
  if (fs.existsSync(packageJsonPath)) {
@@ -36,425 +91,248 @@ const getVersion = () => {
36
91
  }
37
92
  }
38
93
  catch {
39
- // Fall through to env variable
94
+ // Fall through
40
95
  }
41
- // Fallback to npm_package_version (works when run via npm scripts)
42
- // If neither works, show unknown
43
96
  return process.env.npm_package_version ?? 'unknown';
44
- };
97
+ }
45
98
  const VERSION = getVersion();
46
- const checkNodeVersion = () => {
47
- const nodeVersion = process.version;
48
- const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
49
- if (majorVersion < 18) {
50
- console.error(chalk.red(`Error: Node.js version ${nodeVersion} is not supported.`));
51
- console.error(chalk.yellow('Auto-engineer requires Node.js 18.0.0 or higher.'));
52
- console.error(chalk.blue('Please upgrade Node.js and try again.'));
53
- process.exit(1);
99
+ async function loadPipelineConfig(configPath) {
100
+ try {
101
+ if (configPath.endsWith('.ts')) {
102
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
103
+ return await jiti.import(configPath);
104
+ }
105
+ const configUrl = pathToFileURL(path.resolve(configPath)).href;
106
+ const configModule = (await import(configUrl));
107
+ return configModule.default ?? configModule;
54
108
  }
55
- };
56
- const setupSignalHandlers = () => {
57
- process.on('SIGINT', () => {
58
- if (serverInstance !== null && serverInstance !== undefined) {
59
- console.log('\n' + chalk.yellow('Shutting down server...'));
60
- try {
61
- void serverInstance.stop();
62
- }
63
- catch (error) {
64
- console.error('Error stopping server:', error);
65
- }
109
+ catch (error) {
110
+ console.error(chalk.red('Failed to load config:'), error);
111
+ return null;
112
+ }
113
+ }
114
+ async function tryLoadFromPath(modulePath) {
115
+ if (!fs.existsSync(modulePath))
116
+ return null;
117
+ const packageUrl = pathToFileURL(modulePath).href;
118
+ return (await import(packageUrl));
119
+ }
120
+ async function loadPlugin(packageName) {
121
+ const packageShortName = packageName.replace('@auto-engineer/', '');
122
+ const workspaceBase = path.join(process.cwd(), 'packages', packageShortName, 'dist');
123
+ const nodeModulesBase = path.join(process.cwd(), 'node_modules', packageName, 'dist');
124
+ const pathsToTry = [
125
+ path.join(workspaceBase, 'index.js'),
126
+ path.join(workspaceBase, 'src', 'index.js'),
127
+ path.join(nodeModulesBase, 'index.js'),
128
+ path.join(nodeModulesBase, 'src', 'index.js'),
129
+ ];
130
+ const nodePathsToTry = [path.join(workspaceBase, 'src', 'node.js'), path.join(nodeModulesBase, 'src', 'node.js')];
131
+ try {
132
+ for (const modulePath of pathsToTry) {
133
+ const plugin = await tryLoadFromPath(modulePath);
134
+ if (plugin?.COMMANDS)
135
+ return plugin;
66
136
  }
67
- else {
68
- console.log('\n' + chalk.yellow('Operation cancelled by user'));
137
+ for (const modulePath of nodePathsToTry) {
138
+ const plugin = await tryLoadFromPath(modulePath);
139
+ if (plugin?.COMMANDS)
140
+ return plugin;
69
141
  }
70
- process.exit(0);
71
- });
72
- process.on('SIGTERM', () => {
73
- if (serverInstance !== null && serverInstance !== undefined) {
74
- console.log('\n' + chalk.yellow('Shutting down server...'));
75
- try {
76
- void serverInstance.stop();
77
- }
78
- catch (error) {
79
- console.error('Error stopping server:', error);
80
- }
142
+ const dynamicPlugin = (await import(packageName));
143
+ if (dynamicPlugin?.COMMANDS)
144
+ return dynamicPlugin;
145
+ try {
146
+ const nodeImport = (await import(`${packageName}/node`));
147
+ if (nodeImport?.COMMANDS)
148
+ return nodeImport;
81
149
  }
82
- else {
83
- console.log('\n' + chalk.yellow('Process terminated'));
150
+ catch {
151
+ // /node subpath not available for this package
84
152
  }
85
- process.exit(0);
86
- });
87
- process.on('uncaughtException', (error) => {
88
- console.error(chalk.red('Uncaught Exception:'), error);
89
- process.exit(1);
90
- });
91
- process.on('unhandledRejection', (reason, promise) => {
92
- console.error(chalk.red('Unhandled Rejection at:'), promise, chalk.red('reason:'), reason);
93
- process.exit(1);
94
- });
95
- };
96
- const createCLI = () => {
97
- const program = new Command();
98
- program
99
- .name('auto-engineer')
100
- .description('Auto Engineer - Build production ready full-stack apps with AI')
101
- .version(VERSION, '-v, --version')
102
- .option('-d, --debug', 'Enable debug mode')
103
- .option('--no-color', 'Disable colored output')
104
- .option('--json', 'Output in JSON format')
105
- .option('--api-token <token>', 'API token for external services')
106
- .option('--project-path <path>', 'Project path to work with')
107
- .option('--host <url>', 'Message bus server URL (e.g., localhost:5555)');
108
- return program;
109
- };
110
- const prepareCommandData = (alias, args, options, pluginLoader) => {
111
- const mapper = pluginLoader.getCommandMapper(alias);
112
- if (mapper !== undefined) {
113
- return mapper(args, options);
153
+ return null;
114
154
  }
115
- // Default handling for unknown commands
116
- const baseData = {};
117
- args.forEach((arg, index) => {
118
- if (arg !== '') {
119
- baseData[`arg${index + 1}`] = arg;
155
+ catch (error) {
156
+ console.warn(chalk.yellow(`Warning: Failed to load plugin ${packageName}:`), error);
157
+ return null;
158
+ }
159
+ }
160
+ async function loadCommandHandlers(plugins) {
161
+ const handlers = [];
162
+ for (const packageName of plugins) {
163
+ const plugin = await loadPlugin(packageName);
164
+ if (plugin?.COMMANDS) {
165
+ handlers.push(...plugin.COMMANDS);
166
+ continue;
120
167
  }
121
- });
122
- Object.keys(options).forEach((key) => {
123
- if (options[key] !== undefined && key !== '_') {
124
- baseData[key] = options[key];
168
+ const detectedHandlers = await detectCommandsByConvention(packageName);
169
+ if (detectedHandlers.length > 0) {
170
+ handlers.push(...detectedHandlers);
125
171
  }
126
- });
127
- return baseData;
128
- };
129
- const filterNonEmptyArgs = (args) => {
130
- return args.filter((arg) => {
131
- if (typeof arg === 'string')
132
- return arg !== '';
133
- if (Array.isArray(arg))
134
- return true;
135
- return false;
136
- });
137
- };
138
- const debugCommandExecution = (args, cmdArgs, options) => {
139
- if (process.env.DEBUG !== undefined && process.env.DEBUG.includes('cli:')) {
140
- console.error('DEBUG cli: Raw args:', args);
141
- console.error('DEBUG cli: Command args:', cmdArgs);
142
- console.error('DEBUG cli: Options:', options);
143
- }
144
- };
145
- const debugCommandData = (commandData) => {
146
- if (process.env.DEBUG !== undefined && process.env.DEBUG.includes('cli:')) {
147
- console.error('DEBUG cli: Prepared command data:', commandData);
148
- }
149
- };
150
- const handleVersionOption = (options, alias, loadedCommands) => {
151
- if (options.version === true) {
152
- const commandInfo = loadedCommands.get(alias);
153
- const version = commandInfo && 'version' in commandInfo && typeof commandInfo.version === 'string'
154
- ? commandInfo.version
155
- : 'unknown';
156
- console.log(version);
157
- return true;
158
- }
159
- return false;
160
- };
161
- const executeCommandAction = async (args, alias, pluginLoader, loadedCommands, config, analytics) => {
162
- const cmdObject = args[args.length - 1];
163
- const cmdArgs = args.slice(0, -1);
164
- const options = cmdObject.opts();
165
- const globalOpts = cmdObject.parent?.opts();
166
- const hostOption = globalOpts?.host;
167
- if (handleVersionOption(options, alias, loadedCommands)) {
168
- return;
169
172
  }
170
- await analytics.track({ command: alias, success: true });
171
- debugCommandExecution(args, cmdArgs, options);
172
- const nonEmptyArgs = filterNonEmptyArgs(cmdArgs);
173
- const commandData = prepareCommandData(alias, nonEmptyArgs, options, pluginLoader);
174
- debugCommandData(commandData);
175
- if (hostOption !== undefined) {
176
- pluginLoader.setHost(hostOption);
177
- }
178
- await pluginLoader.executeCommand(alias, commandData);
179
- if (hostOption !== undefined) {
180
- pluginLoader.setHost(config.host);
173
+ return handlers;
174
+ }
175
+ async function dispatchCommand(baseUrl, commandType, data) {
176
+ const response = await fetch(`${baseUrl}/command`, {
177
+ method: 'POST',
178
+ headers: { 'Content-Type': 'application/json' },
179
+ body: JSON.stringify({ type: commandType, data }),
180
+ });
181
+ return response.json();
182
+ }
183
+ function findConfigFile() {
184
+ const candidates = ['auto.config.ts', 'auto.config.js'];
185
+ for (const candidate of candidates) {
186
+ const fullPath = path.join(process.cwd(), candidate);
187
+ if (fs.existsSync(fullPath)) {
188
+ return fullPath;
189
+ }
181
190
  }
182
- await analytics.track({ command: alias, success: true });
183
- };
184
- const setupProgram = async (config) => {
185
- const program = createCLI();
186
- const analytics = new Analytics(config);
187
- const output = createOutput(config);
188
- const loadedCommands = new Map();
189
- // Configure help to be minimal since we add our own
190
- program.configureHelp({
191
- sortSubcommands: true,
192
- subcommandTerm: (cmd) => cmd.name(),
193
- // Hide the Commands section
194
- formatHelp: (cmd, helper) => {
195
- const termWidth = helper.padWidth(cmd, helper);
196
- const helpWidth = helper.helpWidth ?? 80;
197
- const itemIndentWidth = 2;
198
- const itemSeparatorWidth = 2;
199
- function formatItem(term, description) {
200
- if (description !== '') {
201
- const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
202
- return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth);
203
- }
204
- return term;
205
- }
206
- function formatList(textArray) {
207
- return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
208
- }
209
- const output = [];
210
- // Usage
211
- const commandUsage = helper.commandUsage(cmd);
212
- output.push('Usage: ' + commandUsage, '');
213
- // Description
214
- const commandDescription = helper.commandDescription(cmd);
215
- if (commandDescription !== '') {
216
- output.push(commandDescription, '');
217
- }
218
- // Options
219
- const optionList = helper.visibleOptions(cmd).map((option) => {
220
- return formatItem(helper.optionTerm(option), helper.optionDescription(option));
221
- });
222
- if (optionList.length > 0) {
223
- output.push('Options:', formatList(optionList), '');
224
- }
225
- // Skip the Commands section - we'll add our own
226
- return output.join('\n');
227
- },
191
+ return null;
192
+ }
193
+ async function startFileSyncServer(syncPort, watchDir) {
194
+ const httpServer = createServer();
195
+ const io = new SocketIOServer(httpServer, {
196
+ cors: { origin: '*' },
228
197
  });
229
- // Check for auto.config.ts or auto.config.js file
230
- let configPath = path.resolve(process.cwd(), 'auto.config.ts');
231
- let hasPluginConfig = fs.existsSync(configPath);
232
- if (!hasPluginConfig) {
233
- configPath = path.resolve(process.cwd(), 'auto.config.js');
234
- hasPluginConfig = fs.existsSync(configPath);
198
+ const fileSyncer = new FileSyncer(io, watchDir);
199
+ fileSyncer.start();
200
+ return new Promise((resolve) => {
201
+ httpServer.listen(syncPort, () => {
202
+ resolve({ fileSyncer, httpServer });
203
+ });
204
+ });
205
+ }
206
+ async function startServer(opts) {
207
+ const port = parseInt(opts.port, 10);
208
+ const actualPort = await getPort({ port: portNumbers(port, port + 100) });
209
+ const syncPort = await getPort({ port: portNumbers(actualPort + 1, actualPort + 100) });
210
+ const configPath = opts.config ?? findConfigFile();
211
+ if (!configPath) {
212
+ console.error(chalk.red('No pipeline config found. Create an auto.config.ts file.'));
213
+ process.exit(1);
235
214
  }
236
- if (!hasPluginConfig) {
237
- output.error('No auto.config.ts or auto.config.js found. Please create one with your required plugins.');
238
- output.info('Example auto.config.js:');
239
- console.log(`
240
- export default {
241
- plugins: [
242
- '@auto-engineer/narrative',
243
- '@auto-engineer/server-generator-apollo-emmett',
244
- '@auto-engineer/server-implementer',
245
- // Add more plugins as needed
246
- ]
247
- };`);
215
+ const spinner = ora('Loading pipeline config...').start();
216
+ const config = await loadPipelineConfig(configPath);
217
+ if (!config) {
218
+ spinner.fail('Failed to load pipeline config');
248
219
  process.exit(1);
249
220
  }
250
- // Use plugin system
251
- debug('Loading plugins from %s', configPath);
221
+ spinner.text = 'Loading plugins...';
222
+ const commandHandlers = await loadCommandHandlers(config.plugins);
223
+ spinner.succeed(`Loaded ${commandHandlers.length} command handlers from ${config.plugins.length} plugins`);
224
+ spinner.start('Starting pipeline server...');
252
225
  try {
253
- const pluginLoader = new PluginLoader(config.host);
254
- const commands = await pluginLoader.loadPlugins(configPath);
255
- // Store loaded commands for dynamic help generation
256
- commands.forEach((command, alias) => {
257
- loadedCommands.set(alias, command);
226
+ const server = new PipelineServer({ port: actualPort });
227
+ server.registerCommandHandlers(commandHandlers);
228
+ server.registerPipeline(config.pipeline);
229
+ const watchDir = path.dirname(configPath);
230
+ const { fileSyncer, httpServer: syncHttpServer } = await startFileSyncServer(syncPort, watchDir);
231
+ process.on('SIGINT', () => {
232
+ console.log(chalk.yellow('\nShutting down servers...'));
233
+ fileSyncer.stop();
234
+ syncHttpServer.close();
235
+ void server.stop();
236
+ process.exit(0);
258
237
  });
259
- // Register CLI commands that will dispatch through the message bus
260
- for (const [alias, command] of commands.entries()) {
261
- // Parse the command alias to get parts for commander
262
- const parts = alias.split(':');
263
- const primaryCommand = parts[0];
264
- const subCommand = parts.slice(1).join(':');
265
- let cmd;
266
- if (subCommand) {
267
- // Create as subcommand (e.g., "create:example" becomes "create example")
268
- cmd = new Command(`${primaryCommand}:${subCommand}`);
269
- }
270
- else {
271
- // Single command
272
- cmd = new Command(primaryCommand);
273
- }
274
- cmd.description(command.description);
275
- // Add arguments from the command manifest
276
- if (command.args) {
277
- command.args.forEach((arg) => {
278
- // Handle variadic arguments (e.g., flows...)
279
- const isVariadic = arg.name.endsWith('...');
280
- const argName = isVariadic ? arg.name : arg.name;
281
- const argString = arg.required === true ? `<${argName}>` : `[${argName}]`;
282
- cmd.argument(argString, arg.description ?? '');
283
- });
284
- }
285
- // Add options from the command manifest
286
- if (command.options) {
287
- command.options.forEach((opt) => {
288
- cmd.option(opt.name, opt.description ?? '');
289
- });
290
- }
291
- // Add version option to each command if package has version
292
- const commandInfo = loadedCommands.get(alias);
293
- if (commandInfo && 'version' in commandInfo) {
294
- cmd.option('--version', 'Display version of the package providing this command');
295
- }
296
- cmd.action(async (...args) => {
297
- try {
298
- await executeCommandAction(args, alias, pluginLoader, loadedCommands, config, analytics);
299
- }
300
- catch (error) {
301
- await analytics.track({ command: alias, success: false });
302
- if (error instanceof Error) {
303
- handleError(error);
304
- }
305
- else {
306
- handleError(new Error(String(error)));
307
- }
308
- }
309
- });
310
- program.addCommand(cmd);
311
- }
312
- const pluginCount = pluginLoader.getLoadedPluginCount();
313
- debug('Loaded %d commands from %d plugins', commands.size, pluginCount);
238
+ await server.start();
239
+ spinner.succeed(`Pipeline server running at http://localhost:${actualPort}`);
240
+ console.log(chalk.cyan('\nEndpoints:'));
241
+ console.log(` ${chalk.gray('Health:')} http://localhost:${actualPort}/health`);
242
+ console.log(` ${chalk.gray('Registry:')} http://localhost:${actualPort}/registry`);
243
+ console.log(` ${chalk.gray('Pipeline:')} http://localhost:${actualPort}/pipeline`);
244
+ console.log(` ${chalk.gray('Diagram:')} http://localhost:${actualPort}/pipeline/diagram`);
245
+ console.log(` ${chalk.gray('Events:')} http://localhost:${actualPort}/events (SSE)`);
246
+ console.log(` ${chalk.gray('FileSync:')} ws://localhost:${syncPort} (Socket.IO)`);
247
+ console.log(chalk.gray('\nPress Ctrl+C to stop'));
314
248
  }
315
249
  catch (error) {
316
- output.error('Failed to load plugins');
250
+ spinner.fail('Failed to start server');
317
251
  console.error(error);
318
252
  process.exit(1);
319
253
  }
320
- // Generate dynamic help text from loaded commands
321
- const generateDynamicHelp = () => {
322
- const categories = new Map();
323
- // Group commands by category
324
- loadedCommands.forEach((command, alias) => {
325
- const category = command.category ?? 'Other Commands';
326
- if (!categories.has(category)) {
327
- categories.set(category, []);
328
- }
329
- categories.get(category).push({ alias, command });
330
- });
331
- const helpText = [];
332
- helpText.push('\nCommands:\n');
333
- // Build help text for each category
334
- categories.forEach((commands, category) => {
335
- helpText.push(`\n ${chalk.cyan(category)}\n`);
336
- commands.forEach(({ alias, command }) => {
337
- const usage = command.usage ?? alias;
338
- const description = command.description ?? 'No description available';
339
- // Calculate padding for alignment
340
- const padding = ' '.repeat(Math.max(0, 60 - usage.length));
341
- helpText.push(` ${usage}${padding}${description}\n`);
342
- // Add examples indented under the command
343
- if (command.examples && command.examples.length > 0) {
344
- command.examples.forEach((example) => {
345
- helpText.push(` ${chalk.gray(example)}\n`);
346
- });
347
- }
348
- });
349
- });
350
- // Add environment variables section
351
- helpText.push(`
352
- \nEnvironment Variables:
353
- ${chalk.gray('AI Providers (need at least one):')}
354
- ANTHROPIC_API_KEY Anthropic Claude API key
355
- OPENAI_API_KEY OpenAI API key
356
- GEMINI_API_KEY Google Gemini API key
357
- XAI_API_KEY X.AI Grok API key
358
-
359
- ${chalk.gray('Debugging & Configuration:')}
360
- DEBUG=* Enable all debug output
361
- DEBUG=auto-engineer:* Enable auto-engineer debug only
362
- NO_COLOR=1 Disable colored output
363
- AUTO_ENGINEER_ANALYTICS=false Disable usage analytics
364
-
365
- Tips:
366
- • Use DEBUG=* to troubleshoot command issues
367
- • Run 'pnpm install' after create:example
368
- • Commands available depend on plugins in auto.config.ts
369
-
370
- For docs & support: https://github.com/SamHatoum/auto-engineer\n`);
371
- return helpText.join('');
372
- };
373
- program.addHelpText('after', generateDynamicHelp());
374
- return program;
375
- };
376
- const loadEnvFile = () => {
377
- const envPath = path.resolve(process.cwd(), '.env');
378
- if (fs.existsSync(envPath)) {
379
- dotenv.config({ path: envPath });
380
- }
381
- };
382
- const initializeEnvironment = () => {
383
- checkNodeVersion();
384
- loadEnvFile();
385
- setupSignalHandlers();
386
- };
387
- const handleProgramError = (error) => {
388
- if (error instanceof Error &&
389
- (error.message.includes('commander') ||
390
- error.message.includes('helpDisplayed') ||
391
- error.message.includes('version'))) {
392
- process.exit(0);
393
- }
394
- if (error instanceof Error) {
395
- handleError(error);
396
- }
397
- else {
398
- console.error(chalk.red('Unknown error:'), error);
399
- process.exit(1);
400
- }
401
- };
402
- let serverStarted = false;
403
- let serverInstance = null;
404
- const startMessageBusServer = async () => {
405
- if (serverStarted)
406
- return;
407
- else
408
- serverStarted = true;
409
- const { MessageBusServer } = await import('./server/server.js');
410
- const { loadMessageBusConfig } = await import('./server/config-loader.js');
411
- debug('Starting Auto Engineer Server...');
412
- const server = new MessageBusServer({
413
- port: await getPort({ port: portNumbers(5555, 6000) }),
414
- enableFileSync: true,
415
- fileSyncDir: process.cwd(),
416
- fileSyncExtensions: ['.js', '.ts', '.tsx', '.jsx', '.html', '.css', 'auto.config'],
254
+ }
255
+ async function main() {
256
+ const program = new Command();
257
+ program
258
+ .name('auto')
259
+ .description('Auto Engineer - Build apps with Narrative Driven Development')
260
+ .version(VERSION, '-v, --version')
261
+ .option('-p, --port <number>', 'Server port', '5555')
262
+ .option('-d, --debug', 'Enable debug mode')
263
+ .option('-c, --config <path>', 'Path to pipeline config file')
264
+ .option('--host <url>', 'Connect to existing server instead of starting one');
265
+ program
266
+ .command('start', { isDefault: true })
267
+ .description('Start the pipeline server with loaded config (default)')
268
+ .action(async () => {
269
+ const opts = program.opts();
270
+ await startServer(opts);
417
271
  });
418
- const configPath = path.resolve(process.cwd(), 'auto.config.ts');
419
- if (fs.existsSync(configPath)) {
420
- await loadMessageBusConfig(configPath, server);
421
- }
422
- serverInstance = server;
423
- await server.start();
424
- };
425
- // TODO make this check for the latest version and make it easy for devs to upgrade
426
- const main = async () => {
427
- try {
428
- initializeEnvironment();
429
- // For help/version, we need to load the config with defaults to show all commands
430
- const hostArgIndex = process.argv.indexOf('--host');
431
- const hostValue = hostArgIndex !== -1 && hostArgIndex + 1 < process.argv.length ? process.argv[hostArgIndex + 1] : undefined;
432
- const config = loadConfig({
433
- debug: process.argv.includes('-d') || process.argv.includes('--debug'),
434
- noColor: process.argv.includes('--no-color'),
435
- output: process.argv.includes('--json') ? 'json' : 'text',
436
- host: hostValue,
437
- });
438
- validateConfig(config);
439
- createOutput(config);
440
- // Check if no command arguments provided (just 'auto')
441
- if (process.argv.length === 2 || (process.argv.length === 3 && process.argv[2] === 'serve')) {
442
- await startMessageBusServer();
443
- return;
272
+ program
273
+ .command('dispatch <command>')
274
+ .description('Dispatch a command to the pipeline server')
275
+ .option('--data <json>', 'Command data as JSON', '{}')
276
+ .action(async (commandType, options) => {
277
+ const opts = program.opts();
278
+ const baseUrl = opts.host ?? `http://localhost:${opts.port}`;
279
+ const spinner = ora(`Dispatching ${commandType}...`).start();
280
+ try {
281
+ const data = JSON.parse(options.data);
282
+ const result = await dispatchCommand(baseUrl, commandType, data);
283
+ spinner.succeed(`Command dispatched: ${commandType}`);
284
+ console.log(chalk.gray(JSON.stringify(result, null, 2)));
444
285
  }
445
- const fullProgram = await setupProgram(config);
446
- await fullProgram.parseAsync(process.argv);
447
- }
448
- catch (error) {
449
- handleProgramError(error);
450
- }
451
- };
452
- // Only run main() if this is the entry point
453
- if (import.meta.url === `file://${process.argv[1]}`) {
454
- main().catch((error) => {
455
- const errorMessage = error instanceof Error ? error.message : String(error);
456
- console.error(chalk.red('Fatal error:'), errorMessage);
457
- process.exit(1);
286
+ catch (error) {
287
+ spinner.fail(`Failed to dispatch ${commandType}`);
288
+ console.error(error);
289
+ process.exit(1);
290
+ }
291
+ });
292
+ program
293
+ .command('status')
294
+ .description('Check pipeline server status')
295
+ .action(async () => {
296
+ const opts = program.opts();
297
+ const baseUrl = opts.host ?? `http://localhost:${opts.port}`;
298
+ try {
299
+ const healthResponse = await fetch(`${baseUrl}/health`);
300
+ if (!healthResponse.ok) {
301
+ console.log(chalk.red('Server is not healthy'));
302
+ process.exit(1);
303
+ }
304
+ const health = (await healthResponse.json());
305
+ console.log(chalk.green('Server is healthy'));
306
+ console.log(chalk.gray(` Status: ${health.status}`));
307
+ console.log(chalk.gray(` Uptime: ${Math.round(health.uptime)}s`));
308
+ const registryResponse = await fetch(`${baseUrl}/registry`);
309
+ const registry = (await registryResponse.json());
310
+ console.log(chalk.cyan('\nRegistry:'));
311
+ console.log(chalk.gray(` Commands: ${registry.commandHandlers.length}`));
312
+ console.log(chalk.gray(` Events: ${registry.eventHandlers.length}`));
313
+ }
314
+ catch {
315
+ console.log(chalk.red('Server is not running'));
316
+ process.exit(1);
317
+ }
318
+ });
319
+ program
320
+ .command('diagram')
321
+ .description('Open the pipeline diagram in a browser')
322
+ .action(async () => {
323
+ const opts = program.opts();
324
+ const baseUrl = opts.host ?? `http://localhost:${opts.port}`;
325
+ const diagramUrl = `${baseUrl}/pipeline/diagram`;
326
+ console.log(chalk.cyan(`Opening diagram at ${diagramUrl}`));
327
+ const { exec } = await import('node:child_process');
328
+ const platform = process.platform;
329
+ const command = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open';
330
+ exec(`${command} ${diagramUrl}`);
458
331
  });
332
+ await program.parseAsync(process.argv);
459
333
  }
334
+ main().catch((error) => {
335
+ console.error(chalk.red('Error:'), error);
336
+ process.exit(1);
337
+ });
460
338
  //# sourceMappingURL=index.js.map