@defai.digital/cli 13.1.16 → 13.2.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/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +1 -3
- package/dist/bootstrap.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/discuss.d.ts +1 -1
- package/dist/commands/discuss.js +5 -5
- package/dist/commands/discuss.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +1 -13
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +5 -2
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/init.d.ts +26 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +524 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/iterate.js +1 -1
- package/dist/commands/setup.d.ts +11 -2
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +132 -468
- package/dist/commands/setup.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +6 -1
- package/dist/parser.js.map +1 -1
- package/dist/utils/provider-factory.js +1 -1
- package/package.json +21 -21
package/dist/commands/setup.js
CHANGED
|
@@ -1,54 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Setup command -
|
|
2
|
+
* Setup command - Global setup wizard for AutomatosX
|
|
3
|
+
*
|
|
4
|
+
* Creates global configuration with detected providers.
|
|
5
|
+
* For per-project initialization (MCP registration), use `ax init`.
|
|
3
6
|
*
|
|
4
7
|
* Features:
|
|
5
8
|
* - Provider detection
|
|
6
|
-
* -
|
|
9
|
+
* - Global configuration creation
|
|
7
10
|
* - Non-interactive mode for CI
|
|
8
11
|
*/
|
|
9
12
|
import { exec } from 'node:child_process';
|
|
10
13
|
import { promisify } from 'node:util';
|
|
11
|
-
import { mkdir, writeFile, readFile, access } from 'node:fs/promises';
|
|
12
|
-
import { join } from 'node:path';
|
|
13
14
|
import { createConfigStore, initConfigDirectory, } from '@defai.digital/config-domain';
|
|
14
|
-
import { DEFAULT_CONFIG, KNOWN_PROVIDERS, PROVIDER_DEFAULTS,
|
|
15
|
-
import { CONTEXT_DIRECTORY } from '@defai.digital/context-domain';
|
|
16
|
-
import { PROVIDER_CHECKS, checkProviderCLI, } from './doctor.js';
|
|
15
|
+
import { DEFAULT_CONFIG, KNOWN_PROVIDERS, PROVIDER_DEFAULTS, TIMEOUT_HEALTH_CHECK, TIMEOUT_WORKFLOW_STEP, } from '@defai.digital/contracts';
|
|
17
16
|
const execAsync = promisify(exec);
|
|
18
17
|
// ============================================================================
|
|
19
18
|
// Constants
|
|
20
19
|
// ============================================================================
|
|
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
20
|
/** Provider name display width for aligned output */
|
|
53
21
|
const PROVIDER_DISPLAY_WIDTH = 10;
|
|
54
22
|
/** Default error message when error type is unknown */
|
|
@@ -59,8 +27,6 @@ const STDERR_REDIRECT = '2>&1';
|
|
|
59
27
|
const SEMVER_PATTERN = /(\d+\.\d+\.\d+)/;
|
|
60
28
|
/** Platform-specific command to find executables */
|
|
61
29
|
const WHICH_COMMAND = process.platform === 'win32' ? 'where' : 'which';
|
|
62
|
-
/** JSON formatting indentation */
|
|
63
|
-
const JSON_INDENT = 2;
|
|
64
30
|
/** Exit codes for CLI commands */
|
|
65
31
|
const EXIT_CODE = {
|
|
66
32
|
SUCCESS: 0,
|
|
@@ -71,11 +37,6 @@ const CONFIG_SCOPE = {
|
|
|
71
37
|
GLOBAL: 'global',
|
|
72
38
|
LOCAL: 'local',
|
|
73
39
|
};
|
|
74
|
-
/** Health check status values */
|
|
75
|
-
const HEALTH_STATUS = {
|
|
76
|
-
PASS: 'pass',
|
|
77
|
-
FAIL: 'fail',
|
|
78
|
-
};
|
|
79
40
|
/** CLI argument flags */
|
|
80
41
|
const CLI_FLAGS = {
|
|
81
42
|
force: ['--force', '-f'],
|
|
@@ -83,7 +44,6 @@ const CLI_FLAGS = {
|
|
|
83
44
|
silent: ['--silent', '-s'],
|
|
84
45
|
local: ['--local', '-l'],
|
|
85
46
|
global: ['--global', '-g'],
|
|
86
|
-
skipProject: ['--skip-project', '--no-project'],
|
|
87
47
|
};
|
|
88
48
|
/** Terminal color codes */
|
|
89
49
|
const COLORS = {
|
|
@@ -102,258 +62,6 @@ const ICONS = {
|
|
|
102
62
|
warn: `${COLORS.yellow}\u26A0${COLORS.reset}`,
|
|
103
63
|
arrow: `${COLORS.cyan}\u2192${COLORS.reset}`,
|
|
104
64
|
};
|
|
105
|
-
/** MCP configuration for each provider (null = provider doesn't support MCP) */
|
|
106
|
-
const PROVIDER_MCP_CONFIGS = {
|
|
107
|
-
claude: { cliName: 'claude', format: 'claude' },
|
|
108
|
-
gemini: { cliName: 'gemini', format: 'standard' },
|
|
109
|
-
codex: { cliName: 'codex', format: 'standard' },
|
|
110
|
-
qwen: { cliName: 'qwen', format: 'standard' },
|
|
111
|
-
glm: { cliName: 'ax-glm', format: 'ax-wrapper' },
|
|
112
|
-
grok: { cliName: 'ax-grok', format: 'ax-wrapper' },
|
|
113
|
-
'ax-cli': null, // ax-cli doesn't support MCP yet
|
|
114
|
-
};
|
|
115
|
-
/**
|
|
116
|
-
* Get the absolute path to the CLI binary.
|
|
117
|
-
* Uses process.argv[1] which works for both global and local installations.
|
|
118
|
-
*/
|
|
119
|
-
function getCLIBinaryPath() {
|
|
120
|
-
const binaryPath = process.argv[1];
|
|
121
|
-
return binaryPath || CLI_FALLBACK_COMMAND;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Check if a path is absolute (Unix or Windows style)
|
|
125
|
-
*/
|
|
126
|
-
function isAbsolutePath(filePath) {
|
|
127
|
-
return filePath.startsWith('/') || filePath.includes('\\');
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Build the MCP server command parts based on binary path.
|
|
131
|
-
* Returns { executable, arguments } for use in MCP add commands.
|
|
132
|
-
*/
|
|
133
|
-
function buildMCPServerCommand(binaryPath) {
|
|
134
|
-
if (isAbsolutePath(binaryPath)) {
|
|
135
|
-
return { executable: NODE_EXECUTABLE, arguments: `"${binaryPath}" ${MCP_COMMANDS.serverArgs}` };
|
|
136
|
-
}
|
|
137
|
-
return { executable: binaryPath, arguments: MCP_COMMANDS.serverArgs };
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Get the CLI command to add AutomatosX MCP server for a provider.
|
|
141
|
-
* Uses provider's native CLI to ensure proper formatting and validation.
|
|
142
|
-
*/
|
|
143
|
-
function buildMCPAddCommand(providerId) {
|
|
144
|
-
const mcpConfig = PROVIDER_MCP_CONFIGS[providerId];
|
|
145
|
-
if (!mcpConfig)
|
|
146
|
-
return null;
|
|
147
|
-
const binaryPath = getCLIBinaryPath();
|
|
148
|
-
const { executable, arguments: execArgs } = buildMCPServerCommand(binaryPath);
|
|
149
|
-
const { cliName, format } = mcpConfig;
|
|
150
|
-
switch (format) {
|
|
151
|
-
case 'standard':
|
|
152
|
-
return `${cliName} ${MCP_COMMANDS.add} ${MCP_SERVER_NAME} ${executable} ${execArgs}`;
|
|
153
|
-
case 'claude':
|
|
154
|
-
return `${cliName} ${MCP_COMMANDS.add} ${MCP_SERVER_NAME} ${MCP_FLAGS.claudeScope} ${executable} ${execArgs}`;
|
|
155
|
-
case 'ax-wrapper': {
|
|
156
|
-
const command = isAbsolutePath(binaryPath) ? NODE_EXECUTABLE : CLI_FALLBACK_COMMAND;
|
|
157
|
-
const commandArgs = isAbsolutePath(binaryPath) ? `${binaryPath} ${MCP_COMMANDS.serverArgs}` : MCP_COMMANDS.serverArgs;
|
|
158
|
-
return `${cliName} ${MCP_COMMANDS.add} ${MCP_SERVER_NAME} ${MCP_FLAGS.command} ${command} ${MCP_FLAGS.args} ${commandArgs}`;
|
|
159
|
-
}
|
|
160
|
-
default:
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Get the CLI command to remove MCP server for a provider.
|
|
166
|
-
*/
|
|
167
|
-
function buildMCPRemoveCommand(providerId) {
|
|
168
|
-
const mcpConfig = PROVIDER_MCP_CONFIGS[providerId];
|
|
169
|
-
if (!mcpConfig)
|
|
170
|
-
return null;
|
|
171
|
-
const scopeFlag = mcpConfig.format === 'claude' ? ` ${MCP_FLAGS.claudeScope}` : '';
|
|
172
|
-
return `${mcpConfig.cliName} ${MCP_COMMANDS.remove} ${MCP_SERVER_NAME}${scopeFlag}`;
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Get the settings file path for ax-wrapper providers.
|
|
176
|
-
* ax-glm and ax-grok store configs in .ax-glm/ and .ax-grok/ directories.
|
|
177
|
-
*/
|
|
178
|
-
function getAxWrapperSettingsPath(cliName) {
|
|
179
|
-
// For project-local config, use current working directory
|
|
180
|
-
return join(process.cwd(), `.${cliName}`, 'settings.json');
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Configure MCP for ax-wrapper providers by writing config file directly.
|
|
184
|
-
* This is needed because ax-wrapper CLIs default to 'content-length' framing,
|
|
185
|
-
* but automatosx MCP server uses 'ndjson' framing (MCP SDK default).
|
|
186
|
-
*/
|
|
187
|
-
async function configureAxWrapperMCP(cliName) {
|
|
188
|
-
const binaryPath = getCLIBinaryPath();
|
|
189
|
-
const settingsPath = getAxWrapperSettingsPath(cliName);
|
|
190
|
-
const settingsDir = join(process.cwd(), `.${cliName}`);
|
|
191
|
-
try {
|
|
192
|
-
// Ensure settings directory exists
|
|
193
|
-
await mkdir(settingsDir, { recursive: true });
|
|
194
|
-
// Read existing config or create new one
|
|
195
|
-
let existingConfig = { mcpServers: {} };
|
|
196
|
-
try {
|
|
197
|
-
const content = await readFile(settingsPath, 'utf-8');
|
|
198
|
-
existingConfig = JSON.parse(content);
|
|
199
|
-
if (!existingConfig.mcpServers) {
|
|
200
|
-
existingConfig.mcpServers = {};
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
catch {
|
|
204
|
-
// File doesn't exist or is invalid - use empty config
|
|
205
|
-
}
|
|
206
|
-
// Build args based on binary path
|
|
207
|
-
const args = isAbsolutePath(binaryPath)
|
|
208
|
-
? [binaryPath, 'mcp', 'server']
|
|
209
|
-
: ['mcp', 'server'];
|
|
210
|
-
const command = isAbsolutePath(binaryPath) ? NODE_EXECUTABLE : binaryPath;
|
|
211
|
-
// Add automatosx MCP server config with ndjson framing
|
|
212
|
-
existingConfig.mcpServers[MCP_SERVER_NAME] = {
|
|
213
|
-
name: MCP_SERVER_NAME,
|
|
214
|
-
transport: {
|
|
215
|
-
type: 'stdio',
|
|
216
|
-
command,
|
|
217
|
-
args,
|
|
218
|
-
env: {},
|
|
219
|
-
framing: 'ndjson',
|
|
220
|
-
},
|
|
221
|
-
};
|
|
222
|
-
// Write updated config
|
|
223
|
-
await writeFile(settingsPath, JSON.stringify(existingConfig, null, JSON_INDENT) + '\n');
|
|
224
|
-
return { success: true, skipped: false };
|
|
225
|
-
}
|
|
226
|
-
catch (err) {
|
|
227
|
-
return {
|
|
228
|
-
success: false,
|
|
229
|
-
skipped: false,
|
|
230
|
-
error: err instanceof Error ? err.message : FALLBACK_ERROR_MESSAGE,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Check if command output indicates successful MCP server addition.
|
|
236
|
-
* Some providers output success message even when validation times out.
|
|
237
|
-
*/
|
|
238
|
-
function isMCPAdditionSuccessful(commandOutput) {
|
|
239
|
-
return MCP_SUCCESS_PATTERN.test(commandOutput);
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Extract clean error message from exec error.
|
|
243
|
-
*/
|
|
244
|
-
function extractErrorMessage(rawError) {
|
|
245
|
-
if (rawError.includes('Command failed')) {
|
|
246
|
-
return rawError.split('\n').pop() || rawError;
|
|
247
|
-
}
|
|
248
|
-
return rawError;
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Configure MCP for a detected provider.
|
|
252
|
-
*
|
|
253
|
-
* For ax-wrapper providers (ax-glm, ax-grok):
|
|
254
|
-
* - Write config file directly with ndjson framing (required for MCP SDK compatibility)
|
|
255
|
-
*
|
|
256
|
-
* For other providers:
|
|
257
|
-
* - Use native CLI commands (remove-then-add for clean state)
|
|
258
|
-
*/
|
|
259
|
-
async function configureMCPForProvider(providerId) {
|
|
260
|
-
const mcpConfig = PROVIDER_MCP_CONFIGS[providerId];
|
|
261
|
-
if (!mcpConfig) {
|
|
262
|
-
return { success: true, skipped: true };
|
|
263
|
-
}
|
|
264
|
-
// For ax-wrapper providers, write config file directly with ndjson framing
|
|
265
|
-
// This is needed because ax-wrapper CLIs default to 'content-length' framing,
|
|
266
|
-
// but automatosx MCP server uses 'ndjson' framing (MCP SDK default)
|
|
267
|
-
if (mcpConfig.format === 'ax-wrapper') {
|
|
268
|
-
return configureAxWrapperMCP(mcpConfig.cliName);
|
|
269
|
-
}
|
|
270
|
-
const addCommand = buildMCPAddCommand(providerId);
|
|
271
|
-
const removeCommand = buildMCPRemoveCommand(providerId);
|
|
272
|
-
if (!addCommand) {
|
|
273
|
-
return { success: true, skipped: true };
|
|
274
|
-
}
|
|
275
|
-
try {
|
|
276
|
-
// Step 1: Remove existing config for clean state (ignore if not exists)
|
|
277
|
-
if (removeCommand) {
|
|
278
|
-
try {
|
|
279
|
-
await execAsync(`${removeCommand} ${STDERR_REDIRECT}`, { timeout: TIMEOUT_SETUP_REMOVE });
|
|
280
|
-
}
|
|
281
|
-
catch {
|
|
282
|
-
// Server might not exist - that's expected
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
// Step 2: Add MCP server using provider's native CLI
|
|
286
|
-
const { stdout, stderr } = await execAsync(`${addCommand} ${STDERR_REDIRECT}`, { timeout: TIMEOUT_SETUP_ADD });
|
|
287
|
-
const commandOutput = `${stdout}${stderr}`;
|
|
288
|
-
if (isMCPAdditionSuccessful(commandOutput)) {
|
|
289
|
-
return { success: true, skipped: false };
|
|
290
|
-
}
|
|
291
|
-
return { success: true, skipped: false };
|
|
292
|
-
}
|
|
293
|
-
catch (err) {
|
|
294
|
-
// Node's exec error includes stdout/stderr as properties
|
|
295
|
-
const execResult = err;
|
|
296
|
-
const errorMsg = execResult.message || FALLBACK_ERROR_MESSAGE;
|
|
297
|
-
const fullOutput = `${execResult.stdout || ''}${execResult.stderr || ''}${errorMsg}`;
|
|
298
|
-
// Check if server was added despite command failure (validation timeout)
|
|
299
|
-
if (isMCPAdditionSuccessful(fullOutput)) {
|
|
300
|
-
return { success: true, skipped: false };
|
|
301
|
-
}
|
|
302
|
-
return {
|
|
303
|
-
success: false,
|
|
304
|
-
skipped: false,
|
|
305
|
-
error: extractErrorMessage(errorMsg),
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Use ax doctor logic to check which provider CLIs are installed
|
|
311
|
-
* This ensures consistent detection between 'ax doctor' and 'ax setup'
|
|
312
|
-
*/
|
|
313
|
-
async function getInstalledProviderCLIs() {
|
|
314
|
-
const results = new Map();
|
|
315
|
-
// Run doctor-style checks for all providers
|
|
316
|
-
for (const provider of PROVIDER_CHECKS) {
|
|
317
|
-
const checkResult = await checkProviderCLI(provider);
|
|
318
|
-
results.set(provider.id, checkResult);
|
|
319
|
-
}
|
|
320
|
-
return results;
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Configure MCP for all detected providers using their native CLI commands.
|
|
324
|
-
*
|
|
325
|
-
* Uses 'ax doctor' check logic to determine which CLIs are installed.
|
|
326
|
-
* Only configures MCP for providers that pass the doctor check.
|
|
327
|
-
*/
|
|
328
|
-
async function configureMCPForAllProviders() {
|
|
329
|
-
const result = {
|
|
330
|
-
configured: [],
|
|
331
|
-
skipped: [],
|
|
332
|
-
notInstalled: [],
|
|
333
|
-
failed: [],
|
|
334
|
-
};
|
|
335
|
-
const installedProviders = await getInstalledProviderCLIs();
|
|
336
|
-
for (const [providerId, healthCheck] of installedProviders) {
|
|
337
|
-
if (healthCheck.status === HEALTH_STATUS.FAIL) {
|
|
338
|
-
result.notInstalled.push(providerId);
|
|
339
|
-
continue;
|
|
340
|
-
}
|
|
341
|
-
const configResult = await configureMCPForProvider(providerId);
|
|
342
|
-
if (configResult.skipped) {
|
|
343
|
-
result.skipped.push(providerId);
|
|
344
|
-
}
|
|
345
|
-
else if (configResult.success) {
|
|
346
|
-
result.configured.push(providerId);
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
result.failed.push({
|
|
350
|
-
providerId,
|
|
351
|
-
error: configResult.error || FALLBACK_ERROR_MESSAGE,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
return result;
|
|
356
|
-
}
|
|
357
65
|
// ============================================================================
|
|
358
66
|
// Provider Detection
|
|
359
67
|
// ============================================================================
|
|
@@ -416,121 +124,48 @@ async function detectAllProviders() {
|
|
|
416
124
|
const results = await Promise.all(KNOWN_PROVIDERS.map((id) => detectProvider(id)));
|
|
417
125
|
return results;
|
|
418
126
|
}
|
|
419
|
-
// ============================================================================
|
|
420
|
-
// Project Structure Creation
|
|
421
|
-
// ============================================================================
|
|
422
|
-
/**
|
|
423
|
-
* Template for project conventions file
|
|
424
|
-
*/
|
|
425
|
-
const CONVENTIONS_TEMPLATE = `# Project Conventions
|
|
426
|
-
|
|
427
|
-
## Code Style
|
|
428
|
-
<!-- Describe your coding standards -->
|
|
429
|
-
- Example: Use TypeScript strict mode
|
|
430
|
-
- Example: Prefer functional components over class components
|
|
431
|
-
- Example: Use named exports over default exports
|
|
432
|
-
|
|
433
|
-
## Architecture
|
|
434
|
-
<!-- Describe your project structure -->
|
|
435
|
-
- Example: Domain-driven design with packages/core/*/
|
|
436
|
-
- Example: Contract-first: all types in packages/contracts/
|
|
437
|
-
- Example: No circular dependencies between packages
|
|
438
|
-
|
|
439
|
-
## Testing
|
|
440
|
-
<!-- Describe testing practices -->
|
|
441
|
-
- Example: Use vitest for unit tests
|
|
442
|
-
- Example: Co-locate tests with source: *.test.ts
|
|
443
|
-
- Example: Mock external dependencies, not internal modules
|
|
444
|
-
|
|
445
|
-
## Naming Conventions
|
|
446
|
-
<!-- Describe naming conventions -->
|
|
447
|
-
- Example: Use camelCase for variables and functions
|
|
448
|
-
- Example: Use PascalCase for types and classes
|
|
449
|
-
- Example: Prefix interfaces with I (e.g., IUserService)
|
|
450
|
-
`;
|
|
451
|
-
/**
|
|
452
|
-
* Template for project config.json
|
|
453
|
-
*/
|
|
454
|
-
const PROJECT_CONFIG_TEMPLATE = {
|
|
455
|
-
version: DEFAULT_SCHEMA_VERSION,
|
|
456
|
-
iterate: {
|
|
457
|
-
maxIterations: ITERATE_MAX_DEFAULT,
|
|
458
|
-
maxTimeMs: ITERATE_TIMEOUT_DEFAULT,
|
|
459
|
-
autoConfirm: false,
|
|
460
|
-
},
|
|
461
|
-
};
|
|
462
|
-
/**
|
|
463
|
-
* Creates the .automatosx/ project structure
|
|
464
|
-
*/
|
|
465
|
-
async function createProjectStructure(projectDir, force) {
|
|
466
|
-
const created = [];
|
|
467
|
-
const skipped = [];
|
|
468
|
-
const automatosxDir = join(projectDir, DATA_DIR_NAME);
|
|
469
|
-
const contextDir = join(automatosxDir, CONTEXT_DIRECTORY);
|
|
470
|
-
const configPath = join(automatosxDir, CONFIG_FILENAME);
|
|
471
|
-
const conventionsPath = join(contextDir, CONVENTIONS_FILENAME);
|
|
472
|
-
// Create data directory
|
|
473
|
-
try {
|
|
474
|
-
await mkdir(automatosxDir, { recursive: true });
|
|
475
|
-
}
|
|
476
|
-
catch {
|
|
477
|
-
// Directory may already exist
|
|
478
|
-
}
|
|
479
|
-
// Create context directory
|
|
480
|
-
try {
|
|
481
|
-
await mkdir(contextDir, { recursive: true });
|
|
482
|
-
}
|
|
483
|
-
catch {
|
|
484
|
-
// Directory may already exist
|
|
485
|
-
}
|
|
486
|
-
// Create project config.json
|
|
487
|
-
const configExists = await fileExists(configPath);
|
|
488
|
-
if (!configExists || force) {
|
|
489
|
-
await writeFile(configPath, JSON.stringify(PROJECT_CONFIG_TEMPLATE, null, JSON_INDENT) + '\n');
|
|
490
|
-
created.push(`${DATA_DIR_NAME}/${CONFIG_FILENAME}`);
|
|
491
|
-
}
|
|
492
|
-
else {
|
|
493
|
-
skipped.push(`${DATA_DIR_NAME}/${CONFIG_FILENAME} (already exists)`);
|
|
494
|
-
}
|
|
495
|
-
// Create conventions template
|
|
496
|
-
const conventionsExists = await fileExists(conventionsPath);
|
|
497
|
-
if (!conventionsExists || force) {
|
|
498
|
-
await writeFile(conventionsPath, CONVENTIONS_TEMPLATE);
|
|
499
|
-
created.push(`${DATA_DIR_NAME}/${CONTEXT_DIRECTORY}/${CONVENTIONS_FILENAME}`);
|
|
500
|
-
}
|
|
501
|
-
else {
|
|
502
|
-
skipped.push(`${DATA_DIR_NAME}/${CONTEXT_DIRECTORY}/${CONVENTIONS_FILENAME} (already exists)`);
|
|
503
|
-
}
|
|
504
|
-
return { created, skipped };
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* Check if a file exists
|
|
508
|
-
*/
|
|
509
|
-
async function fileExists(path) {
|
|
510
|
-
try {
|
|
511
|
-
await access(path);
|
|
512
|
-
return true;
|
|
513
|
-
}
|
|
514
|
-
catch {
|
|
515
|
-
return false;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
127
|
/**
|
|
519
128
|
* Runs the setup process
|
|
520
129
|
*/
|
|
521
130
|
async function runSetup(options) {
|
|
522
131
|
const configStore = createConfigStore();
|
|
523
132
|
const configFilePath = configStore.getPath(options.scope);
|
|
133
|
+
// Detect providers first (needed for comparison)
|
|
134
|
+
const allProviders = await detectAllProviders();
|
|
135
|
+
const availableProviders = allProviders.filter((provider) => provider.detected);
|
|
136
|
+
const detectedProviderIds = new Set(availableProviders.map((p) => p.providerId));
|
|
524
137
|
// Check if config already exists
|
|
525
138
|
const configExists = await configStore.exists(options.scope);
|
|
526
139
|
if (configExists && !options.force) {
|
|
527
|
-
|
|
140
|
+
// Read existing config and compare
|
|
141
|
+
const existingConfig = await configStore.read(options.scope);
|
|
142
|
+
const existingProviderIds = new Set(Object.keys(existingConfig?.providers ?? {}).filter((id) => existingConfig?.providers[id]?.enabled));
|
|
143
|
+
// Find differences
|
|
144
|
+
const newProviders = [...detectedProviderIds].filter((id) => !existingProviderIds.has(id));
|
|
145
|
+
const removedProviders = [...existingProviderIds].filter((id) => !detectedProviderIds.has(id));
|
|
146
|
+
// If no changes, return "up to date"
|
|
147
|
+
if (newProviders.length === 0 && removedProviders.length === 0) {
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
config: existingConfig,
|
|
151
|
+
providers: allProviders,
|
|
152
|
+
configPath: configFilePath,
|
|
153
|
+
alreadyUpToDate: true,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// Changes detected but no --force flag
|
|
157
|
+
return {
|
|
158
|
+
success: true,
|
|
159
|
+
config: existingConfig,
|
|
160
|
+
providers: allProviders,
|
|
161
|
+
configPath: configFilePath,
|
|
162
|
+
alreadyUpToDate: false,
|
|
163
|
+
newProvidersDetected: newProviders,
|
|
164
|
+
removedProviders: removedProviders,
|
|
165
|
+
};
|
|
528
166
|
}
|
|
529
167
|
// Initialize directory
|
|
530
168
|
await initConfigDirectory(options.scope);
|
|
531
|
-
// Detect providers
|
|
532
|
-
const allProviders = await detectAllProviders();
|
|
533
|
-
const availableProviders = allProviders.filter((provider) => provider.detected);
|
|
534
169
|
// Build providers config record
|
|
535
170
|
const providersConfig = {};
|
|
536
171
|
for (const provider of availableProviders) {
|
|
@@ -577,7 +212,6 @@ function parseSetupArgs(args) {
|
|
|
577
212
|
let nonInteractive = false;
|
|
578
213
|
let silent = false;
|
|
579
214
|
let scope = CONFIG_SCOPE.GLOBAL;
|
|
580
|
-
let skipProjectStructure = false;
|
|
581
215
|
for (const arg of args) {
|
|
582
216
|
if (matchesFlag(arg, CLI_FLAGS.force)) {
|
|
583
217
|
force = true;
|
|
@@ -587,8 +221,8 @@ function parseSetupArgs(args) {
|
|
|
587
221
|
}
|
|
588
222
|
else if (matchesFlag(arg, CLI_FLAGS.silent)) {
|
|
589
223
|
silent = true;
|
|
590
|
-
nonInteractive = true;
|
|
591
|
-
force = true;
|
|
224
|
+
nonInteractive = true;
|
|
225
|
+
force = true;
|
|
592
226
|
}
|
|
593
227
|
else if (matchesFlag(arg, CLI_FLAGS.local)) {
|
|
594
228
|
scope = CONFIG_SCOPE.LOCAL;
|
|
@@ -596,11 +230,8 @@ function parseSetupArgs(args) {
|
|
|
596
230
|
else if (matchesFlag(arg, CLI_FLAGS.global)) {
|
|
597
231
|
scope = CONFIG_SCOPE.GLOBAL;
|
|
598
232
|
}
|
|
599
|
-
else if (matchesFlag(arg, CLI_FLAGS.skipProject)) {
|
|
600
|
-
skipProjectStructure = true;
|
|
601
|
-
}
|
|
602
233
|
}
|
|
603
|
-
return { force, nonInteractive, silent, scope
|
|
234
|
+
return { force, nonInteractive, silent, scope };
|
|
604
235
|
}
|
|
605
236
|
/**
|
|
606
237
|
* Formats provider detection result for display.
|
|
@@ -613,6 +244,12 @@ function formatProviderResult(detection) {
|
|
|
613
244
|
}
|
|
614
245
|
/**
|
|
615
246
|
* Setup command handler
|
|
247
|
+
*
|
|
248
|
+
* Global setup for AutomatosX:
|
|
249
|
+
* 1. Detects installed provider CLIs
|
|
250
|
+
* 2. Creates global configuration
|
|
251
|
+
*
|
|
252
|
+
* For per-project initialization (MCP registration), use `ax init`.
|
|
616
253
|
*/
|
|
617
254
|
export async function setupCommand(args, options) {
|
|
618
255
|
const setupOptions = parseSetupArgs(args);
|
|
@@ -633,62 +270,104 @@ export async function setupCommand(args, options) {
|
|
|
633
270
|
outputLines.push(` ${ICONS.check} Node.js: ${process.version}`);
|
|
634
271
|
outputLines.push('');
|
|
635
272
|
}
|
|
636
|
-
// Step 2: Detect providers
|
|
273
|
+
// Step 2 & 3: Detect providers and create/check configuration
|
|
637
274
|
if (showOutput) {
|
|
638
|
-
outputLines.push(`${COLORS.bold}Step 2: Provider Detection${COLORS.reset}`);
|
|
275
|
+
outputLines.push(`${COLORS.bold}Step 2: Provider Detection & Configuration${COLORS.reset}`);
|
|
639
276
|
}
|
|
640
|
-
const
|
|
277
|
+
const setupResult = await runSetup(setupOptions);
|
|
278
|
+
const detectedProviders = setupResult.providers;
|
|
641
279
|
if (showOutput) {
|
|
642
280
|
for (const provider of detectedProviders) {
|
|
643
281
|
outputLines.push(formatProviderResult(provider));
|
|
644
282
|
}
|
|
645
283
|
outputLines.push('');
|
|
646
284
|
}
|
|
647
|
-
//
|
|
648
|
-
if (
|
|
649
|
-
outputLines.push(`${COLORS.bold}Step 3: Creating Configuration${COLORS.reset}`);
|
|
650
|
-
}
|
|
651
|
-
const setupResult = await runSetup(setupOptions);
|
|
652
|
-
if (showOutput) {
|
|
653
|
-
outputLines.push(` ${ICONS.check} Configuration saved to: ${setupResult.configPath}`);
|
|
654
|
-
outputLines.push('');
|
|
655
|
-
}
|
|
656
|
-
// Step 4: Create project structure (unless skipped)
|
|
657
|
-
let projectStructure;
|
|
658
|
-
if (!setupOptions.skipProjectStructure) {
|
|
285
|
+
// Handle idempotent cases
|
|
286
|
+
if (setupResult.alreadyUpToDate) {
|
|
659
287
|
if (showOutput) {
|
|
660
|
-
outputLines.push(`${
|
|
288
|
+
outputLines.push(`${ICONS.check} Configuration is up to date at: ${setupResult.configPath}`);
|
|
289
|
+
outputLines.push('');
|
|
290
|
+
outputLines.push(`${COLORS.bold}Summary${COLORS.reset}`);
|
|
291
|
+
outputLines.push(` No changes needed - providers match existing configuration`);
|
|
292
|
+
outputLines.push('');
|
|
293
|
+
outputLines.push(`${COLORS.bold}Next Steps${COLORS.reset}`);
|
|
294
|
+
outputLines.push(` 1. Run ${COLORS.cyan}ax doctor${COLORS.reset} to verify providers are working`);
|
|
295
|
+
outputLines.push(` 2. Run ${COLORS.cyan}ax init${COLORS.reset} in your project directory to register MCP`);
|
|
296
|
+
outputLines.push('');
|
|
661
297
|
}
|
|
662
|
-
|
|
298
|
+
if (isJsonFormat) {
|
|
299
|
+
return {
|
|
300
|
+
success: true,
|
|
301
|
+
message: undefined,
|
|
302
|
+
data: {
|
|
303
|
+
success: true,
|
|
304
|
+
configPath: setupResult.configPath,
|
|
305
|
+
alreadyUpToDate: true,
|
|
306
|
+
providers: {
|
|
307
|
+
detected: detectedProviders
|
|
308
|
+
.filter((provider) => provider.detected)
|
|
309
|
+
.map((provider) => provider.providerId),
|
|
310
|
+
enabled: Object.keys(setupResult.config.providers).filter((id) => setupResult.config.providers[id]?.enabled),
|
|
311
|
+
},
|
|
312
|
+
defaultProvider: setupResult.config.defaultProvider,
|
|
313
|
+
version: setupResult.config.version,
|
|
314
|
+
},
|
|
315
|
+
exitCode: EXIT_CODE.SUCCESS,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
success: true,
|
|
320
|
+
message: outputLines.join('\n'),
|
|
321
|
+
data: undefined,
|
|
322
|
+
exitCode: EXIT_CODE.SUCCESS,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
// Changes detected but no --force
|
|
326
|
+
if (setupResult.newProvidersDetected || setupResult.removedProviders) {
|
|
663
327
|
if (showOutput) {
|
|
664
|
-
|
|
665
|
-
|
|
328
|
+
outputLines.push(`${ICONS.warn} Configuration exists but providers have changed:`);
|
|
329
|
+
if (setupResult.newProvidersDetected && setupResult.newProvidersDetected.length > 0) {
|
|
330
|
+
outputLines.push(` ${COLORS.green}+ New:${COLORS.reset} ${setupResult.newProvidersDetected.join(', ')}`);
|
|
666
331
|
}
|
|
667
|
-
|
|
668
|
-
outputLines.push(` ${
|
|
332
|
+
if (setupResult.removedProviders && setupResult.removedProviders.length > 0) {
|
|
333
|
+
outputLines.push(` ${COLORS.red}- Removed:${COLORS.reset} ${setupResult.removedProviders.join(', ')}`);
|
|
669
334
|
}
|
|
670
335
|
outputLines.push('');
|
|
336
|
+
outputLines.push(`Run ${COLORS.cyan}ax setup --force${COLORS.reset} to update configuration.`);
|
|
337
|
+
outputLines.push('');
|
|
671
338
|
}
|
|
339
|
+
if (isJsonFormat) {
|
|
340
|
+
return {
|
|
341
|
+
success: true,
|
|
342
|
+
message: undefined,
|
|
343
|
+
data: {
|
|
344
|
+
success: true,
|
|
345
|
+
configPath: setupResult.configPath,
|
|
346
|
+
alreadyUpToDate: false,
|
|
347
|
+
changesDetected: {
|
|
348
|
+
newProviders: setupResult.newProvidersDetected,
|
|
349
|
+
removedProviders: setupResult.removedProviders,
|
|
350
|
+
},
|
|
351
|
+
providers: {
|
|
352
|
+
detected: detectedProviders
|
|
353
|
+
.filter((provider) => provider.detected)
|
|
354
|
+
.map((provider) => provider.providerId),
|
|
355
|
+
enabled: Object.keys(setupResult.config.providers).filter((id) => setupResult.config.providers[id]?.enabled),
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
exitCode: EXIT_CODE.SUCCESS,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
success: true,
|
|
363
|
+
message: outputLines.join('\n'),
|
|
364
|
+
data: undefined,
|
|
365
|
+
exitCode: EXIT_CODE.SUCCESS,
|
|
366
|
+
};
|
|
672
367
|
}
|
|
673
|
-
//
|
|
674
|
-
if (showOutput) {
|
|
675
|
-
outputLines.push(`${COLORS.bold}Step 5: MCP Configuration${COLORS.reset}`);
|
|
676
|
-
outputLines.push(` ${COLORS.dim}Using 'ax doctor' check to verify installed CLIs...${COLORS.reset}`);
|
|
677
|
-
}
|
|
678
|
-
const mcpResult = await configureMCPForAllProviders();
|
|
368
|
+
// Normal setup (new configuration created)
|
|
679
369
|
if (showOutput) {
|
|
680
|
-
|
|
681
|
-
outputLines.push(` ${ICONS.check} ${providerId}: AutomatosX MCP configured`);
|
|
682
|
-
}
|
|
683
|
-
for (const providerId of mcpResult.notInstalled) {
|
|
684
|
-
outputLines.push(` ${COLORS.dim} - ${providerId}: CLI not installed, skipped${COLORS.reset}`);
|
|
685
|
-
}
|
|
686
|
-
for (const { providerId, error } of mcpResult.failed) {
|
|
687
|
-
outputLines.push(` ${ICONS.warn} ${providerId}: ${error}`);
|
|
688
|
-
}
|
|
689
|
-
if (mcpResult.configured.length === 0 && mcpResult.failed.length === 0 && mcpResult.notInstalled.length === 0) {
|
|
690
|
-
outputLines.push(` ${ICONS.warn} No providers detected for MCP configuration`);
|
|
691
|
-
}
|
|
370
|
+
outputLines.push(`${ICONS.check} Configuration saved to: ${setupResult.configPath}`);
|
|
692
371
|
outputLines.push('');
|
|
693
372
|
}
|
|
694
373
|
if (showOutput) {
|
|
@@ -701,15 +380,10 @@ export async function setupCommand(args, options) {
|
|
|
701
380
|
if (setupResult.config.defaultProvider !== undefined) {
|
|
702
381
|
outputLines.push(` Default provider: ${setupResult.config.defaultProvider}`);
|
|
703
382
|
}
|
|
704
|
-
if (projectStructure !== undefined) {
|
|
705
|
-
outputLines.push(` Project files created: ${projectStructure.created.length}`);
|
|
706
|
-
}
|
|
707
|
-
outputLines.push(` MCP configured: ${mcpResult.configured.length} provider(s)`);
|
|
708
383
|
outputLines.push('');
|
|
709
384
|
outputLines.push(`${COLORS.bold}Next Steps${COLORS.reset}`);
|
|
710
|
-
outputLines.push(` 1. Run ${COLORS.cyan}ax doctor${COLORS.reset} to verify
|
|
711
|
-
outputLines.push(` 2.
|
|
712
|
-
outputLines.push(` 3. Run ${COLORS.cyan}ax call --iterate <provider> "task"${COLORS.reset} to use iterate mode`);
|
|
385
|
+
outputLines.push(` 1. Run ${COLORS.cyan}ax doctor${COLORS.reset} to verify providers are working`);
|
|
386
|
+
outputLines.push(` 2. Run ${COLORS.cyan}ax init${COLORS.reset} in your project directory to register MCP`);
|
|
713
387
|
outputLines.push('');
|
|
714
388
|
}
|
|
715
389
|
if (isJsonFormat) {
|
|
@@ -720,23 +394,13 @@ export async function setupCommand(args, options) {
|
|
|
720
394
|
success: true,
|
|
721
395
|
configPath: setupResult.configPath,
|
|
722
396
|
providers: {
|
|
723
|
-
detected: detectedProviders
|
|
724
|
-
|
|
397
|
+
detected: detectedProviders
|
|
398
|
+
.filter((provider) => provider.detected)
|
|
399
|
+
.map((provider) => provider.providerId),
|
|
400
|
+
enabled: Object.keys(setupResult.config.providers).filter((id) => setupResult.config.providers[id]?.enabled),
|
|
725
401
|
},
|
|
726
402
|
defaultProvider: setupResult.config.defaultProvider,
|
|
727
403
|
version: setupResult.config.version,
|
|
728
|
-
projectStructure: projectStructure !== undefined
|
|
729
|
-
? {
|
|
730
|
-
created: projectStructure.created,
|
|
731
|
-
skipped: projectStructure.skipped,
|
|
732
|
-
}
|
|
733
|
-
: undefined,
|
|
734
|
-
mcpConfiguration: {
|
|
735
|
-
configured: mcpResult.configured,
|
|
736
|
-
skipped: mcpResult.skipped,
|
|
737
|
-
notInstalled: mcpResult.notInstalled,
|
|
738
|
-
failed: mcpResult.failed,
|
|
739
|
-
},
|
|
740
404
|
},
|
|
741
405
|
exitCode: EXIT_CODE.SUCCESS,
|
|
742
406
|
};
|