@defai.digital/cli 13.1.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 (138) hide show
  1. package/LICENSE +214 -0
  2. package/dist/bin.d.ts +3 -0
  3. package/dist/bin.d.ts.map +1 -0
  4. package/dist/bin.js +11 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/bootstrap.d.ts +144 -0
  7. package/dist/bootstrap.d.ts.map +1 -0
  8. package/dist/bootstrap.js +315 -0
  9. package/dist/bootstrap.js.map +1 -0
  10. package/dist/cli.d.ts +14 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +84 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/commands/ability.d.ts +17 -0
  15. package/dist/commands/ability.d.ts.map +1 -0
  16. package/dist/commands/ability.js +286 -0
  17. package/dist/commands/ability.js.map +1 -0
  18. package/dist/commands/agent.d.ts +18 -0
  19. package/dist/commands/agent.d.ts.map +1 -0
  20. package/dist/commands/agent.js +361 -0
  21. package/dist/commands/agent.js.map +1 -0
  22. package/dist/commands/call.d.ts +15 -0
  23. package/dist/commands/call.d.ts.map +1 -0
  24. package/dist/commands/call.js +503 -0
  25. package/dist/commands/call.js.map +1 -0
  26. package/dist/commands/cleanup.d.ts +18 -0
  27. package/dist/commands/cleanup.d.ts.map +1 -0
  28. package/dist/commands/cleanup.js +300 -0
  29. package/dist/commands/cleanup.js.map +1 -0
  30. package/dist/commands/config.d.ts +16 -0
  31. package/dist/commands/config.d.ts.map +1 -0
  32. package/dist/commands/config.js +513 -0
  33. package/dist/commands/config.js.map +1 -0
  34. package/dist/commands/discuss.d.ts +16 -0
  35. package/dist/commands/discuss.d.ts.map +1 -0
  36. package/dist/commands/discuss.js +700 -0
  37. package/dist/commands/discuss.js.map +1 -0
  38. package/dist/commands/doctor.d.ts +48 -0
  39. package/dist/commands/doctor.d.ts.map +1 -0
  40. package/dist/commands/doctor.js +356 -0
  41. package/dist/commands/doctor.js.map +1 -0
  42. package/dist/commands/guard.d.ts +12 -0
  43. package/dist/commands/guard.d.ts.map +1 -0
  44. package/dist/commands/guard.js +225 -0
  45. package/dist/commands/guard.js.map +1 -0
  46. package/dist/commands/help.d.ts +11 -0
  47. package/dist/commands/help.d.ts.map +1 -0
  48. package/dist/commands/help.js +180 -0
  49. package/dist/commands/help.js.map +1 -0
  50. package/dist/commands/history.d.ts +19 -0
  51. package/dist/commands/history.d.ts.map +1 -0
  52. package/dist/commands/history.js +200 -0
  53. package/dist/commands/history.js.map +1 -0
  54. package/dist/commands/index.d.ts +23 -0
  55. package/dist/commands/index.d.ts.map +1 -0
  56. package/dist/commands/index.js +26 -0
  57. package/dist/commands/index.js.map +1 -0
  58. package/dist/commands/iterate.d.ts +16 -0
  59. package/dist/commands/iterate.d.ts.map +1 -0
  60. package/dist/commands/iterate.js +72 -0
  61. package/dist/commands/iterate.js.map +1 -0
  62. package/dist/commands/list.d.ts +6 -0
  63. package/dist/commands/list.d.ts.map +1 -0
  64. package/dist/commands/list.js +62 -0
  65. package/dist/commands/list.js.map +1 -0
  66. package/dist/commands/mcp.d.ts +16 -0
  67. package/dist/commands/mcp.d.ts.map +1 -0
  68. package/dist/commands/mcp.js +57 -0
  69. package/dist/commands/mcp.js.map +1 -0
  70. package/dist/commands/resume.d.ts +18 -0
  71. package/dist/commands/resume.d.ts.map +1 -0
  72. package/dist/commands/resume.js +208 -0
  73. package/dist/commands/resume.js.map +1 -0
  74. package/dist/commands/review.d.ts +13 -0
  75. package/dist/commands/review.d.ts.map +1 -0
  76. package/dist/commands/review.js +450 -0
  77. package/dist/commands/review.js.map +1 -0
  78. package/dist/commands/run.d.ts +6 -0
  79. package/dist/commands/run.d.ts.map +1 -0
  80. package/dist/commands/run.js +158 -0
  81. package/dist/commands/run.js.map +1 -0
  82. package/dist/commands/scaffold.d.ts +20 -0
  83. package/dist/commands/scaffold.d.ts.map +1 -0
  84. package/dist/commands/scaffold.js +924 -0
  85. package/dist/commands/scaffold.js.map +1 -0
  86. package/dist/commands/session.d.ts +20 -0
  87. package/dist/commands/session.d.ts.map +1 -0
  88. package/dist/commands/session.js +504 -0
  89. package/dist/commands/session.js.map +1 -0
  90. package/dist/commands/setup.d.ts +14 -0
  91. package/dist/commands/setup.d.ts.map +1 -0
  92. package/dist/commands/setup.js +762 -0
  93. package/dist/commands/setup.js.map +1 -0
  94. package/dist/commands/status.d.ts +17 -0
  95. package/dist/commands/status.d.ts.map +1 -0
  96. package/dist/commands/status.js +227 -0
  97. package/dist/commands/status.js.map +1 -0
  98. package/dist/commands/trace.d.ts +6 -0
  99. package/dist/commands/trace.d.ts.map +1 -0
  100. package/dist/commands/trace.js +204 -0
  101. package/dist/commands/trace.js.map +1 -0
  102. package/dist/commands/update.d.ts +24 -0
  103. package/dist/commands/update.d.ts.map +1 -0
  104. package/dist/commands/update.js +296 -0
  105. package/dist/commands/update.js.map +1 -0
  106. package/dist/index.d.ts +8 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +13 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/parser.d.ts +14 -0
  111. package/dist/parser.d.ts.map +1 -0
  112. package/dist/parser.js +288 -0
  113. package/dist/parser.js.map +1 -0
  114. package/dist/types.d.ts +91 -0
  115. package/dist/types.d.ts.map +1 -0
  116. package/dist/types.js +29 -0
  117. package/dist/types.js.map +1 -0
  118. package/dist/utils/dangerous-op-guard.d.ts +33 -0
  119. package/dist/utils/dangerous-op-guard.d.ts.map +1 -0
  120. package/dist/utils/dangerous-op-guard.js +112 -0
  121. package/dist/utils/dangerous-op-guard.js.map +1 -0
  122. package/dist/utils/database.d.ts +85 -0
  123. package/dist/utils/database.d.ts.map +1 -0
  124. package/dist/utils/database.js +184 -0
  125. package/dist/utils/database.js.map +1 -0
  126. package/dist/utils/index.d.ts +7 -0
  127. package/dist/utils/index.d.ts.map +1 -0
  128. package/dist/utils/index.js +7 -0
  129. package/dist/utils/index.js.map +1 -0
  130. package/dist/utils/provider-factory.d.ts +31 -0
  131. package/dist/utils/provider-factory.d.ts.map +1 -0
  132. package/dist/utils/provider-factory.js +109 -0
  133. package/dist/utils/provider-factory.js.map +1 -0
  134. package/dist/utils/storage-instances.d.ts +19 -0
  135. package/dist/utils/storage-instances.d.ts.map +1 -0
  136. package/dist/utils/storage-instances.js +20 -0
  137. package/dist/utils/storage-instances.js.map +1 -0
  138. package/package.json +77 -0
@@ -0,0 +1,762 @@
1
+ /**
2
+ * Setup command - Interactive setup wizard for AutomatosX
3
+ *
4
+ * Features:
5
+ * - Provider detection
6
+ * - Configuration creation
7
+ * - Non-interactive mode for CI
8
+ */
9
+ import { exec } from 'node:child_process';
10
+ import { promisify } from 'node:util';
11
+ import { mkdir, writeFile, readFile, access } from 'node:fs/promises';
12
+ import { join } from 'node:path';
13
+ import { createConfigStore, initConfigDirectory, } from '@defai.digital/config-domain';
14
+ import { DEFAULT_CONFIG, KNOWN_PROVIDERS, PROVIDER_DEFAULTS, DATA_DIR_NAME, CONFIG_FILENAME, TIMEOUT_SETUP_ADD, TIMEOUT_SETUP_REMOVE, TIMEOUT_HEALTH_CHECK, TIMEOUT_WORKFLOW_STEP, DEFAULT_SCHEMA_VERSION, ITERATE_MAX_DEFAULT, ITERATE_TIMEOUT_DEFAULT, } from '@defai.digital/contracts';
15
+ import { CONTEXT_DIRECTORY } from '@defai.digital/context-domain';
16
+ import { PROVIDER_CHECKS, checkProviderCLI, } from './doctor.js';
17
+ const execAsync = promisify(exec);
18
+ // ============================================================================
19
+ // Constants
20
+ // ============================================================================
21
+ // ============================================================================
22
+ // MCP Constants
23
+ // ============================================================================
24
+ /** MCP server name registered with provider CLIs */
25
+ const MCP_SERVER_NAME = 'automatosx';
26
+ /** MCP subcommands */
27
+ const MCP_COMMANDS = {
28
+ add: 'mcp add',
29
+ remove: 'mcp remove',
30
+ serverArgs: 'mcp server',
31
+ };
32
+ /** MCP command flags for different formats */
33
+ const MCP_FLAGS = {
34
+ /** Claude uses -s local for user-scope config */
35
+ claudeScope: '-s local',
36
+ /** ax-wrapper uses -c for command */
37
+ command: '-c',
38
+ /** ax-wrapper uses -a for arguments */
39
+ args: '-a',
40
+ };
41
+ /** Pattern to detect successful MCP server addition in output */
42
+ const MCP_SUCCESS_PATTERN = /Added MCP server|server.*added|successfully added/i;
43
+ // ============================================================================
44
+ // CLI Constants
45
+ // ============================================================================
46
+ /** Fallback CLI command when binary path cannot be determined */
47
+ const CLI_FALLBACK_COMMAND = 'ax';
48
+ /** Node.js executable for running scripts */
49
+ const NODE_EXECUTABLE = 'node';
50
+ /** Conventions file name in context directory */
51
+ const CONVENTIONS_FILENAME = 'conventions.md';
52
+ /** Provider name display width for aligned output */
53
+ const PROVIDER_DISPLAY_WIDTH = 10;
54
+ /** Default error message when error type is unknown */
55
+ const FALLBACK_ERROR_MESSAGE = 'Unknown error';
56
+ /** Stderr redirect suffix for shell commands */
57
+ const STDERR_REDIRECT = '2>&1';
58
+ /** Regex pattern to extract semver version from CLI output */
59
+ const SEMVER_PATTERN = /(\d+\.\d+\.\d+)/;
60
+ /** Platform-specific command to find executables */
61
+ const WHICH_COMMAND = process.platform === 'win32' ? 'where' : 'which';
62
+ /** JSON formatting indentation */
63
+ const JSON_INDENT = 2;
64
+ /** Exit codes for CLI commands */
65
+ const EXIT_CODE = {
66
+ SUCCESS: 0,
67
+ FAILURE: 1,
68
+ };
69
+ /** Config scope values */
70
+ const CONFIG_SCOPE = {
71
+ GLOBAL: 'global',
72
+ LOCAL: 'local',
73
+ };
74
+ /** Health check status values */
75
+ const HEALTH_STATUS = {
76
+ PASS: 'pass',
77
+ FAIL: 'fail',
78
+ };
79
+ /** CLI argument flags */
80
+ const CLI_FLAGS = {
81
+ force: ['--force', '-f'],
82
+ nonInteractive: ['--non-interactive', '-y'],
83
+ local: ['--local', '-l'],
84
+ global: ['--global', '-g'],
85
+ skipProject: ['--skip-project', '--no-project'],
86
+ };
87
+ /** Terminal color codes */
88
+ const COLORS = {
89
+ reset: '\x1b[0m',
90
+ green: '\x1b[32m',
91
+ red: '\x1b[31m',
92
+ yellow: '\x1b[33m',
93
+ cyan: '\x1b[36m',
94
+ bold: '\x1b[1m',
95
+ dim: '\x1b[2m',
96
+ };
97
+ /** Terminal output icons */
98
+ const ICONS = {
99
+ check: `${COLORS.green}\u2713${COLORS.reset}`,
100
+ cross: `${COLORS.red}\u2717${COLORS.reset}`,
101
+ warn: `${COLORS.yellow}\u26A0${COLORS.reset}`,
102
+ arrow: `${COLORS.cyan}\u2192${COLORS.reset}`,
103
+ };
104
+ /** MCP configuration for each provider (null = provider doesn't support MCP) */
105
+ const PROVIDER_MCP_CONFIGS = {
106
+ claude: { cliName: 'claude', format: 'claude' },
107
+ gemini: { cliName: 'gemini', format: 'standard' },
108
+ codex: { cliName: 'codex', format: 'standard' },
109
+ qwen: { cliName: 'qwen', format: 'standard' },
110
+ glm: { cliName: 'ax-glm', format: 'ax-wrapper' },
111
+ grok: { cliName: 'ax-grok', format: 'ax-wrapper' },
112
+ 'ax-cli': null, // ax-cli doesn't support MCP yet
113
+ };
114
+ /**
115
+ * Get the absolute path to the CLI binary.
116
+ * Uses process.argv[1] which works for both global and local installations.
117
+ */
118
+ function getCLIBinaryPath() {
119
+ const binaryPath = process.argv[1];
120
+ return binaryPath || CLI_FALLBACK_COMMAND;
121
+ }
122
+ /**
123
+ * Check if a path is absolute (Unix or Windows style)
124
+ */
125
+ function isAbsolutePath(filePath) {
126
+ return filePath.startsWith('/') || filePath.includes('\\');
127
+ }
128
+ /**
129
+ * Build the MCP server command parts based on binary path.
130
+ * Returns { executable, arguments } for use in MCP add commands.
131
+ */
132
+ function buildMCPServerCommand(binaryPath) {
133
+ if (isAbsolutePath(binaryPath)) {
134
+ return { executable: NODE_EXECUTABLE, arguments: `"${binaryPath}" ${MCP_COMMANDS.serverArgs}` };
135
+ }
136
+ return { executable: binaryPath, arguments: MCP_COMMANDS.serverArgs };
137
+ }
138
+ /**
139
+ * Get the CLI command to add AutomatosX MCP server for a provider.
140
+ * Uses provider's native CLI to ensure proper formatting and validation.
141
+ */
142
+ function buildMCPAddCommand(providerId) {
143
+ const mcpConfig = PROVIDER_MCP_CONFIGS[providerId];
144
+ if (!mcpConfig)
145
+ return null;
146
+ const binaryPath = getCLIBinaryPath();
147
+ const { executable, arguments: execArgs } = buildMCPServerCommand(binaryPath);
148
+ const { cliName, format } = mcpConfig;
149
+ switch (format) {
150
+ case 'standard':
151
+ return `${cliName} ${MCP_COMMANDS.add} ${MCP_SERVER_NAME} ${executable} ${execArgs}`;
152
+ case 'claude':
153
+ return `${cliName} ${MCP_COMMANDS.add} ${MCP_SERVER_NAME} ${MCP_FLAGS.claudeScope} ${executable} ${execArgs}`;
154
+ case 'ax-wrapper': {
155
+ const command = isAbsolutePath(binaryPath) ? NODE_EXECUTABLE : CLI_FALLBACK_COMMAND;
156
+ const commandArgs = isAbsolutePath(binaryPath) ? `${binaryPath} ${MCP_COMMANDS.serverArgs}` : MCP_COMMANDS.serverArgs;
157
+ return `${cliName} ${MCP_COMMANDS.add} ${MCP_SERVER_NAME} ${MCP_FLAGS.command} ${command} ${MCP_FLAGS.args} ${commandArgs}`;
158
+ }
159
+ default:
160
+ return null;
161
+ }
162
+ }
163
+ /**
164
+ * Get the CLI command to remove MCP server for a provider.
165
+ */
166
+ function buildMCPRemoveCommand(providerId) {
167
+ const mcpConfig = PROVIDER_MCP_CONFIGS[providerId];
168
+ if (!mcpConfig)
169
+ return null;
170
+ const scopeFlag = mcpConfig.format === 'claude' ? ` ${MCP_FLAGS.claudeScope}` : '';
171
+ return `${mcpConfig.cliName} ${MCP_COMMANDS.remove} ${MCP_SERVER_NAME}${scopeFlag}`;
172
+ }
173
+ /**
174
+ * Get the settings file path for ax-wrapper providers.
175
+ * ax-glm and ax-grok store configs in .ax-glm/ and .ax-grok/ directories.
176
+ */
177
+ function getAxWrapperSettingsPath(cliName) {
178
+ // For project-local config, use current working directory
179
+ return join(process.cwd(), `.${cliName}`, 'settings.json');
180
+ }
181
+ /**
182
+ * Configure MCP for ax-wrapper providers by writing config file directly.
183
+ * This is needed because ax-wrapper CLIs default to 'content-length' framing,
184
+ * but automatosx MCP server uses 'ndjson' framing (MCP SDK default).
185
+ */
186
+ async function configureAxWrapperMCP(cliName) {
187
+ const binaryPath = getCLIBinaryPath();
188
+ const settingsPath = getAxWrapperSettingsPath(cliName);
189
+ const settingsDir = join(process.cwd(), `.${cliName}`);
190
+ try {
191
+ // Ensure settings directory exists
192
+ await mkdir(settingsDir, { recursive: true });
193
+ // Read existing config or create new one
194
+ let existingConfig = { mcpServers: {} };
195
+ try {
196
+ const content = await readFile(settingsPath, 'utf-8');
197
+ existingConfig = JSON.parse(content);
198
+ if (!existingConfig.mcpServers) {
199
+ existingConfig.mcpServers = {};
200
+ }
201
+ }
202
+ catch {
203
+ // File doesn't exist or is invalid - use empty config
204
+ }
205
+ // Build args based on binary path
206
+ const args = isAbsolutePath(binaryPath)
207
+ ? [binaryPath, 'mcp', 'server']
208
+ : ['mcp', 'server'];
209
+ const command = isAbsolutePath(binaryPath) ? NODE_EXECUTABLE : binaryPath;
210
+ // Add automatosx MCP server config with ndjson framing
211
+ existingConfig.mcpServers[MCP_SERVER_NAME] = {
212
+ name: MCP_SERVER_NAME,
213
+ transport: {
214
+ type: 'stdio',
215
+ command,
216
+ args,
217
+ env: {},
218
+ framing: 'ndjson',
219
+ },
220
+ };
221
+ // Write updated config
222
+ await writeFile(settingsPath, JSON.stringify(existingConfig, null, JSON_INDENT) + '\n');
223
+ return { success: true, skipped: false };
224
+ }
225
+ catch (err) {
226
+ return {
227
+ success: false,
228
+ skipped: false,
229
+ error: err instanceof Error ? err.message : FALLBACK_ERROR_MESSAGE,
230
+ };
231
+ }
232
+ }
233
+ /**
234
+ * Check if command output indicates successful MCP server addition.
235
+ * Some providers output success message even when validation times out.
236
+ */
237
+ function isMCPAdditionSuccessful(commandOutput) {
238
+ return MCP_SUCCESS_PATTERN.test(commandOutput);
239
+ }
240
+ /**
241
+ * Extract clean error message from exec error.
242
+ */
243
+ function extractErrorMessage(rawError) {
244
+ if (rawError.includes('Command failed')) {
245
+ return rawError.split('\n').pop() || rawError;
246
+ }
247
+ return rawError;
248
+ }
249
+ /**
250
+ * Configure MCP for a detected provider.
251
+ *
252
+ * For ax-wrapper providers (ax-glm, ax-grok):
253
+ * - Write config file directly with ndjson framing (required for MCP SDK compatibility)
254
+ *
255
+ * For other providers:
256
+ * - Use native CLI commands (remove-then-add for clean state)
257
+ */
258
+ async function configureMCPForProvider(providerId) {
259
+ const mcpConfig = PROVIDER_MCP_CONFIGS[providerId];
260
+ if (!mcpConfig) {
261
+ return { success: true, skipped: true };
262
+ }
263
+ // For ax-wrapper providers, write config file directly with ndjson framing
264
+ // This is needed because ax-wrapper CLIs default to 'content-length' framing,
265
+ // but automatosx MCP server uses 'ndjson' framing (MCP SDK default)
266
+ if (mcpConfig.format === 'ax-wrapper') {
267
+ return configureAxWrapperMCP(mcpConfig.cliName);
268
+ }
269
+ const addCommand = buildMCPAddCommand(providerId);
270
+ const removeCommand = buildMCPRemoveCommand(providerId);
271
+ if (!addCommand) {
272
+ return { success: true, skipped: true };
273
+ }
274
+ try {
275
+ // Step 1: Remove existing config for clean state (ignore if not exists)
276
+ if (removeCommand) {
277
+ try {
278
+ await execAsync(`${removeCommand} ${STDERR_REDIRECT}`, { timeout: TIMEOUT_SETUP_REMOVE });
279
+ }
280
+ catch {
281
+ // Server might not exist - that's expected
282
+ }
283
+ }
284
+ // Step 2: Add MCP server using provider's native CLI
285
+ const { stdout, stderr } = await execAsync(`${addCommand} ${STDERR_REDIRECT}`, { timeout: TIMEOUT_SETUP_ADD });
286
+ const commandOutput = `${stdout}${stderr}`;
287
+ if (isMCPAdditionSuccessful(commandOutput)) {
288
+ return { success: true, skipped: false };
289
+ }
290
+ return { success: true, skipped: false };
291
+ }
292
+ catch (err) {
293
+ // Node's exec error includes stdout/stderr as properties
294
+ const execResult = err;
295
+ const errorMsg = execResult.message || FALLBACK_ERROR_MESSAGE;
296
+ const fullOutput = `${execResult.stdout || ''}${execResult.stderr || ''}${errorMsg}`;
297
+ // Check if server was added despite command failure (validation timeout)
298
+ if (isMCPAdditionSuccessful(fullOutput)) {
299
+ return { success: true, skipped: false };
300
+ }
301
+ return {
302
+ success: false,
303
+ skipped: false,
304
+ error: extractErrorMessage(errorMsg),
305
+ };
306
+ }
307
+ }
308
+ /**
309
+ * Use ax doctor logic to check which provider CLIs are installed
310
+ * This ensures consistent detection between 'ax doctor' and 'ax setup'
311
+ */
312
+ async function getInstalledProviderCLIs() {
313
+ const results = new Map();
314
+ // Run doctor-style checks for all providers
315
+ for (const provider of PROVIDER_CHECKS) {
316
+ const checkResult = await checkProviderCLI(provider);
317
+ results.set(provider.id, checkResult);
318
+ }
319
+ return results;
320
+ }
321
+ /**
322
+ * Configure MCP for all detected providers using their native CLI commands.
323
+ *
324
+ * Uses 'ax doctor' check logic to determine which CLIs are installed.
325
+ * Only configures MCP for providers that pass the doctor check.
326
+ */
327
+ async function configureMCPForAllProviders() {
328
+ const result = {
329
+ configured: [],
330
+ skipped: [],
331
+ notInstalled: [],
332
+ failed: [],
333
+ };
334
+ const installedProviders = await getInstalledProviderCLIs();
335
+ for (const [providerId, healthCheck] of installedProviders) {
336
+ if (healthCheck.status === HEALTH_STATUS.FAIL) {
337
+ result.notInstalled.push(providerId);
338
+ continue;
339
+ }
340
+ const configResult = await configureMCPForProvider(providerId);
341
+ if (configResult.skipped) {
342
+ result.skipped.push(providerId);
343
+ }
344
+ else if (configResult.success) {
345
+ result.configured.push(providerId);
346
+ }
347
+ else {
348
+ result.failed.push({
349
+ providerId,
350
+ error: configResult.error || FALLBACK_ERROR_MESSAGE,
351
+ });
352
+ }
353
+ }
354
+ return result;
355
+ }
356
+ // ============================================================================
357
+ // Provider Detection
358
+ // ============================================================================
359
+ /**
360
+ * Checks if a command is available on PATH
361
+ */
362
+ async function isCommandAvailable(command) {
363
+ try {
364
+ await execAsync(`${WHICH_COMMAND} ${command}`);
365
+ return true;
366
+ }
367
+ catch {
368
+ return false;
369
+ }
370
+ }
371
+ /**
372
+ * Gets the version of a CLI command
373
+ */
374
+ async function getCommandVersion(command) {
375
+ try {
376
+ const { stdout } = await execAsync(`${command} --version ${STDERR_REDIRECT}`, {
377
+ timeout: TIMEOUT_HEALTH_CHECK,
378
+ });
379
+ const versionMatch = SEMVER_PATTERN.exec(stdout);
380
+ return versionMatch?.[1];
381
+ }
382
+ catch {
383
+ return undefined;
384
+ }
385
+ }
386
+ /**
387
+ * Detects a single provider
388
+ *
389
+ * Note: AutomatosX does NOT check for authentication.
390
+ * All CLIs handle their own authentication internally.
391
+ * If a CLI is detected, it's considered ready to use.
392
+ */
393
+ async function detectProvider(providerId) {
394
+ const providerDefaults = PROVIDER_DEFAULTS[providerId];
395
+ const isDetected = await isCommandAvailable(providerDefaults.command);
396
+ if (!isDetected) {
397
+ return {
398
+ providerId,
399
+ detected: false,
400
+ command: providerDefaults.command,
401
+ };
402
+ }
403
+ const version = await getCommandVersion(providerDefaults.command);
404
+ return {
405
+ providerId,
406
+ detected: true,
407
+ command: providerDefaults.command,
408
+ version,
409
+ };
410
+ }
411
+ /**
412
+ * Detects all providers in parallel
413
+ */
414
+ async function detectAllProviders() {
415
+ const results = await Promise.all(KNOWN_PROVIDERS.map((id) => detectProvider(id)));
416
+ return results;
417
+ }
418
+ // ============================================================================
419
+ // Project Structure Creation
420
+ // ============================================================================
421
+ /**
422
+ * Template for project conventions file
423
+ */
424
+ const CONVENTIONS_TEMPLATE = `# Project Conventions
425
+
426
+ ## Code Style
427
+ <!-- Describe your coding standards -->
428
+ - Example: Use TypeScript strict mode
429
+ - Example: Prefer functional components over class components
430
+ - Example: Use named exports over default exports
431
+
432
+ ## Architecture
433
+ <!-- Describe your project structure -->
434
+ - Example: Domain-driven design with packages/core/*/
435
+ - Example: Contract-first: all types in packages/contracts/
436
+ - Example: No circular dependencies between packages
437
+
438
+ ## Testing
439
+ <!-- Describe testing practices -->
440
+ - Example: Use vitest for unit tests
441
+ - Example: Co-locate tests with source: *.test.ts
442
+ - Example: Mock external dependencies, not internal modules
443
+
444
+ ## Naming Conventions
445
+ <!-- Describe naming conventions -->
446
+ - Example: Use camelCase for variables and functions
447
+ - Example: Use PascalCase for types and classes
448
+ - Example: Prefix interfaces with I (e.g., IUserService)
449
+ `;
450
+ /**
451
+ * Template for project config.json
452
+ */
453
+ const PROJECT_CONFIG_TEMPLATE = {
454
+ version: DEFAULT_SCHEMA_VERSION,
455
+ iterate: {
456
+ maxIterations: ITERATE_MAX_DEFAULT,
457
+ maxTimeMs: ITERATE_TIMEOUT_DEFAULT,
458
+ autoConfirm: false,
459
+ },
460
+ };
461
+ /**
462
+ * Creates the .automatosx/ project structure
463
+ */
464
+ async function createProjectStructure(projectDir, force) {
465
+ const created = [];
466
+ const skipped = [];
467
+ const automatosxDir = join(projectDir, DATA_DIR_NAME);
468
+ const contextDir = join(automatosxDir, CONTEXT_DIRECTORY);
469
+ const configPath = join(automatosxDir, CONFIG_FILENAME);
470
+ const conventionsPath = join(contextDir, CONVENTIONS_FILENAME);
471
+ // Create data directory
472
+ try {
473
+ await mkdir(automatosxDir, { recursive: true });
474
+ }
475
+ catch {
476
+ // Directory may already exist
477
+ }
478
+ // Create context directory
479
+ try {
480
+ await mkdir(contextDir, { recursive: true });
481
+ }
482
+ catch {
483
+ // Directory may already exist
484
+ }
485
+ // Create project config.json
486
+ const configExists = await fileExists(configPath);
487
+ if (!configExists || force) {
488
+ await writeFile(configPath, JSON.stringify(PROJECT_CONFIG_TEMPLATE, null, JSON_INDENT) + '\n');
489
+ created.push(`${DATA_DIR_NAME}/${CONFIG_FILENAME}`);
490
+ }
491
+ else {
492
+ skipped.push(`${DATA_DIR_NAME}/${CONFIG_FILENAME} (already exists)`);
493
+ }
494
+ // Create conventions template
495
+ const conventionsExists = await fileExists(conventionsPath);
496
+ if (!conventionsExists || force) {
497
+ await writeFile(conventionsPath, CONVENTIONS_TEMPLATE);
498
+ created.push(`${DATA_DIR_NAME}/${CONTEXT_DIRECTORY}/${CONVENTIONS_FILENAME}`);
499
+ }
500
+ else {
501
+ skipped.push(`${DATA_DIR_NAME}/${CONTEXT_DIRECTORY}/${CONVENTIONS_FILENAME} (already exists)`);
502
+ }
503
+ return { created, skipped };
504
+ }
505
+ /**
506
+ * Check if a file exists
507
+ */
508
+ async function fileExists(path) {
509
+ try {
510
+ await access(path);
511
+ return true;
512
+ }
513
+ catch {
514
+ return false;
515
+ }
516
+ }
517
+ /**
518
+ * Runs the setup process
519
+ */
520
+ async function runSetup(options) {
521
+ const configStore = createConfigStore();
522
+ const configFilePath = configStore.getPath(options.scope);
523
+ // Check if config already exists
524
+ const configExists = await configStore.exists(options.scope);
525
+ if (configExists && !options.force) {
526
+ throw new Error(`Configuration already exists at ${configFilePath}. Use --force to overwrite.`);
527
+ }
528
+ // Initialize directory
529
+ await initConfigDirectory(options.scope);
530
+ // Detect providers
531
+ const allProviders = await detectAllProviders();
532
+ const availableProviders = allProviders.filter((provider) => provider.detected);
533
+ // Build providers config record
534
+ const providersConfig = {};
535
+ for (const provider of availableProviders) {
536
+ const providerDefaults = PROVIDER_DEFAULTS[provider.providerId];
537
+ providersConfig[provider.providerId] = {
538
+ enabled: true,
539
+ priority: providerDefaults.priority,
540
+ timeout: TIMEOUT_WORKFLOW_STEP,
541
+ command: providerDefaults.command,
542
+ };
543
+ }
544
+ // Sort provider IDs by priority to find default (highest first)
545
+ const providersByPriority = Object.keys(providersConfig).sort((a, b) => (providersConfig[b]?.priority ?? 0) - (providersConfig[a]?.priority ?? 0));
546
+ const config = {
547
+ ...DEFAULT_CONFIG,
548
+ providers: providersConfig,
549
+ defaultProvider: providersByPriority[0],
550
+ createdAt: new Date().toISOString(),
551
+ updatedAt: new Date().toISOString(),
552
+ };
553
+ // Save config
554
+ await configStore.write(config, options.scope);
555
+ return {
556
+ success: true,
557
+ config,
558
+ providers: allProviders,
559
+ configPath: configFilePath,
560
+ };
561
+ }
562
+ // ============================================================================
563
+ // Command Handler
564
+ // ============================================================================
565
+ /**
566
+ * Check if an argument matches any of the given flags
567
+ */
568
+ function matchesFlag(arg, flags) {
569
+ return flags.includes(arg);
570
+ }
571
+ /**
572
+ * Parses setup-specific arguments
573
+ */
574
+ function parseSetupArgs(args) {
575
+ let force = false;
576
+ let nonInteractive = false;
577
+ let scope = CONFIG_SCOPE.GLOBAL;
578
+ let skipProjectStructure = false;
579
+ for (const arg of args) {
580
+ if (matchesFlag(arg, CLI_FLAGS.force)) {
581
+ force = true;
582
+ }
583
+ else if (matchesFlag(arg, CLI_FLAGS.nonInteractive)) {
584
+ nonInteractive = true;
585
+ }
586
+ else if (matchesFlag(arg, CLI_FLAGS.local)) {
587
+ scope = CONFIG_SCOPE.LOCAL;
588
+ }
589
+ else if (matchesFlag(arg, CLI_FLAGS.global)) {
590
+ scope = CONFIG_SCOPE.GLOBAL;
591
+ }
592
+ else if (matchesFlag(arg, CLI_FLAGS.skipProject)) {
593
+ skipProjectStructure = true;
594
+ }
595
+ }
596
+ return { force, nonInteractive, scope, skipProjectStructure };
597
+ }
598
+ /**
599
+ * Formats provider detection result for display.
600
+ */
601
+ function formatProviderResult(detection) {
602
+ const statusText = detection.detected
603
+ ? `${ICONS.check} Detected${detection.version ? ` (v${detection.version})` : ''}`
604
+ : `${ICONS.cross} Not detected`;
605
+ return ` ${detection.providerId.padEnd(PROVIDER_DISPLAY_WIDTH)} ${statusText}`;
606
+ }
607
+ /**
608
+ * Setup command handler
609
+ */
610
+ export async function setupCommand(args, options) {
611
+ const setupOptions = parseSetupArgs(args);
612
+ const isJsonFormat = options.format === 'json';
613
+ const outputLines = [];
614
+ try {
615
+ // Header
616
+ if (!isJsonFormat) {
617
+ outputLines.push('');
618
+ outputLines.push(`${COLORS.bold}AutomatosX Setup${COLORS.reset}`);
619
+ outputLines.push('');
620
+ }
621
+ // Step 1: Check prerequisites
622
+ if (!isJsonFormat) {
623
+ outputLines.push(`${COLORS.bold}Step 1: System Check${COLORS.reset}`);
624
+ outputLines.push(` ${ICONS.check} Node.js: ${process.version}`);
625
+ outputLines.push('');
626
+ }
627
+ // Step 2: Detect providers
628
+ if (!isJsonFormat) {
629
+ outputLines.push(`${COLORS.bold}Step 2: Provider Detection${COLORS.reset}`);
630
+ }
631
+ const detectedProviders = await detectAllProviders();
632
+ if (!isJsonFormat) {
633
+ for (const provider of detectedProviders) {
634
+ outputLines.push(formatProviderResult(provider));
635
+ }
636
+ outputLines.push('');
637
+ }
638
+ // Step 3: Create configuration
639
+ if (!isJsonFormat) {
640
+ outputLines.push(`${COLORS.bold}Step 3: Creating Configuration${COLORS.reset}`);
641
+ }
642
+ const setupResult = await runSetup(setupOptions);
643
+ if (!isJsonFormat) {
644
+ outputLines.push(` ${ICONS.check} Configuration saved to: ${setupResult.configPath}`);
645
+ outputLines.push('');
646
+ }
647
+ // Step 4: Create project structure (unless skipped)
648
+ let projectStructure;
649
+ if (!setupOptions.skipProjectStructure) {
650
+ if (!isJsonFormat) {
651
+ outputLines.push(`${COLORS.bold}Step 4: Project Structure${COLORS.reset}`);
652
+ }
653
+ projectStructure = await createProjectStructure(process.cwd(), setupOptions.force);
654
+ if (!isJsonFormat) {
655
+ for (const filePath of projectStructure.created) {
656
+ outputLines.push(` ${ICONS.check} Created ${filePath}`);
657
+ }
658
+ for (const filePath of projectStructure.skipped) {
659
+ outputLines.push(` ${ICONS.warn} Skipped ${filePath}`);
660
+ }
661
+ outputLines.push('');
662
+ }
663
+ }
664
+ // Step 5: Configure MCP for all detected providers
665
+ if (!isJsonFormat) {
666
+ outputLines.push(`${COLORS.bold}Step 5: MCP Configuration${COLORS.reset}`);
667
+ outputLines.push(` ${COLORS.dim}Using 'ax doctor' check to verify installed CLIs...${COLORS.reset}`);
668
+ }
669
+ const mcpResult = await configureMCPForAllProviders();
670
+ if (!isJsonFormat) {
671
+ for (const providerId of mcpResult.configured) {
672
+ outputLines.push(` ${ICONS.check} ${providerId}: AutomatosX MCP configured`);
673
+ }
674
+ for (const providerId of mcpResult.notInstalled) {
675
+ outputLines.push(` ${COLORS.dim} - ${providerId}: CLI not installed, skipped${COLORS.reset}`);
676
+ }
677
+ for (const { providerId, error } of mcpResult.failed) {
678
+ outputLines.push(` ${ICONS.warn} ${providerId}: ${error}`);
679
+ }
680
+ if (mcpResult.configured.length === 0 && mcpResult.failed.length === 0 && mcpResult.notInstalled.length === 0) {
681
+ outputLines.push(` ${ICONS.warn} No providers detected for MCP configuration`);
682
+ }
683
+ outputLines.push('');
684
+ }
685
+ if (!isJsonFormat) {
686
+ // Summary
687
+ const detectedCount = detectedProviders.filter((provider) => provider.detected).length;
688
+ const enabledCount = Object.keys(setupResult.config.providers).length;
689
+ outputLines.push(`${COLORS.bold}Summary${COLORS.reset}`);
690
+ outputLines.push(` Providers detected: ${detectedCount}/${KNOWN_PROVIDERS.length}`);
691
+ outputLines.push(` Providers enabled: ${enabledCount}`);
692
+ if (setupResult.config.defaultProvider !== undefined) {
693
+ outputLines.push(` Default provider: ${setupResult.config.defaultProvider}`);
694
+ }
695
+ if (projectStructure !== undefined) {
696
+ outputLines.push(` Project files created: ${projectStructure.created.length}`);
697
+ }
698
+ outputLines.push(` MCP configured: ${mcpResult.configured.length} provider(s)`);
699
+ outputLines.push('');
700
+ outputLines.push(`${COLORS.bold}Next Steps${COLORS.reset}`);
701
+ outputLines.push(` 1. Run ${COLORS.cyan}ax doctor${COLORS.reset} to verify installation`);
702
+ outputLines.push(` 2. Edit ${COLORS.cyan}${DATA_DIR_NAME}/${CONTEXT_DIRECTORY}/${CONVENTIONS_FILENAME}${COLORS.reset} to add your project conventions`);
703
+ outputLines.push(` 3. Run ${COLORS.cyan}ax call --iterate <provider> "task"${COLORS.reset} to use iterate mode`);
704
+ outputLines.push('');
705
+ }
706
+ if (isJsonFormat) {
707
+ return {
708
+ success: true,
709
+ message: undefined,
710
+ data: {
711
+ success: true,
712
+ configPath: setupResult.configPath,
713
+ providers: {
714
+ detected: detectedProviders.filter((provider) => provider.detected).map((provider) => provider.providerId),
715
+ enabled: Object.keys(setupResult.config.providers).filter(id => setupResult.config.providers[id]?.enabled),
716
+ },
717
+ defaultProvider: setupResult.config.defaultProvider,
718
+ version: setupResult.config.version,
719
+ projectStructure: projectStructure !== undefined
720
+ ? {
721
+ created: projectStructure.created,
722
+ skipped: projectStructure.skipped,
723
+ }
724
+ : undefined,
725
+ mcpConfiguration: {
726
+ configured: mcpResult.configured,
727
+ skipped: mcpResult.skipped,
728
+ notInstalled: mcpResult.notInstalled,
729
+ failed: mcpResult.failed,
730
+ },
731
+ },
732
+ exitCode: EXIT_CODE.SUCCESS,
733
+ };
734
+ }
735
+ return {
736
+ success: true,
737
+ message: outputLines.join('\n'),
738
+ data: undefined,
739
+ exitCode: EXIT_CODE.SUCCESS,
740
+ };
741
+ }
742
+ catch (err) {
743
+ const errorMsg = err instanceof Error ? err.message : FALLBACK_ERROR_MESSAGE;
744
+ if (isJsonFormat) {
745
+ return {
746
+ success: false,
747
+ message: undefined,
748
+ data: { error: errorMsg },
749
+ exitCode: EXIT_CODE.FAILURE,
750
+ };
751
+ }
752
+ outputLines.push(`${ICONS.cross} Setup failed: ${errorMsg}`);
753
+ outputLines.push('');
754
+ return {
755
+ success: false,
756
+ message: outputLines.join('\n'),
757
+ data: undefined,
758
+ exitCode: EXIT_CODE.FAILURE,
759
+ };
760
+ }
761
+ }
762
+ //# sourceMappingURL=setup.js.map