@defai.digital/ax-cli 4.3.12 → 4.3.14

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 (50) hide show
  1. package/README.md +7 -6
  2. package/config-defaults/models.yaml +16 -1
  3. package/dist/agent/llm-agent.d.ts +2 -0
  4. package/dist/agent/llm-agent.js +5 -2
  5. package/dist/agent/llm-agent.js.map +1 -1
  6. package/dist/commands/design.js +7 -2
  7. package/dist/commands/design.js.map +1 -1
  8. package/dist/commands/frontend.js +2 -2
  9. package/dist/commands/mcp.js +69 -5
  10. package/dist/commands/mcp.js.map +1 -1
  11. package/dist/commands/setup.js +1 -1
  12. package/dist/commands/setup.js.map +1 -1
  13. package/dist/commands/status.js +195 -0
  14. package/dist/commands/status.js.map +1 -1
  15. package/dist/constants.d.ts +4 -0
  16. package/dist/constants.js +4 -0
  17. package/dist/constants.js.map +1 -1
  18. package/dist/index.js +9 -765
  19. package/dist/index.js.map +1 -1
  20. package/dist/llm/types.d.ts +23 -0
  21. package/dist/llm/types.js +10 -0
  22. package/dist/llm/types.js.map +1 -1
  23. package/dist/mcp/cancellation.d.ts +8 -2
  24. package/dist/mcp/cancellation.js +1 -1
  25. package/dist/mcp/cancellation.js.map +1 -1
  26. package/dist/mcp/client-v2.d.ts +2 -0
  27. package/dist/mcp/client-v2.js +8 -2
  28. package/dist/mcp/client-v2.js.map +1 -1
  29. package/dist/mcp/zai-templates.d.ts +1 -1
  30. package/dist/mcp/zai-templates.js +2 -2
  31. package/dist/planner/plan-storage.d.ts +13 -0
  32. package/dist/planner/plan-storage.js +52 -0
  33. package/dist/planner/plan-storage.js.map +1 -1
  34. package/dist/provider/config.js +16 -19
  35. package/dist/provider/config.js.map +1 -1
  36. package/dist/schemas/api-schemas.d.ts +2 -2
  37. package/dist/schemas/settings-schemas.d.ts +1 -1
  38. package/dist/types/project-analysis.d.ts +6 -0
  39. package/dist/ui/hooks/use-input-handler.d.ts +1 -1
  40. package/dist/ui/utils/image-handler.js +1 -1
  41. package/dist/utils/llm-optimized-instruction-generator.d.ts +1 -0
  42. package/dist/utils/llm-optimized-instruction-generator.js +23 -2
  43. package/dist/utils/llm-optimized-instruction-generator.js.map +1 -1
  44. package/dist/utils/project-analyzer.d.ts +8 -0
  45. package/dist/utils/project-analyzer.js +101 -0
  46. package/dist/utils/project-analyzer.js.map +1 -1
  47. package/dist/utils/retry-helper.d.ts +6 -2
  48. package/dist/utils/retry-helper.js +6 -2
  49. package/dist/utils/retry-helper.js.map +1 -1
  50. package/package.json +21 -22
package/dist/index.js CHANGED
@@ -1,768 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import React from "react";
3
- import { render } from "ink";
4
- import { program } from "commander";
5
- import * as dotenv from "dotenv";
6
- import { LLMAgent } from "./agent/llm-agent.js";
7
- import ChatInterface from "./ui/components/chat-interface.js";
8
- import { getSettingsManager } from "./utils/settings-manager.js";
9
- import { ConfirmationService } from "./utils/confirmation-service.js";
10
- import { extractErrorMessage } from "./utils/error-handler.js";
11
- import { createMCPCommand } from "./commands/mcp.js";
12
- import { createMCPMigrateCommand } from "./commands/mcp-migrate.js";
13
- import { createFrontendCommand } from "./commands/frontend.js";
14
- import { createInitCommand } from "./commands/init.js";
15
- import { createUpdateCommand, checkForUpdatesOnStartup, promptAndInstallUpdate } from "./commands/update.js";
16
- import { createSetupCommand } from "./commands/setup.js";
17
- import { createUsageCommand } from "./commands/usage.js";
18
- import { createTemplatesCommand } from "./commands/templates.js";
19
- import { createMemoryCommand } from "./commands/memory.js";
20
- import { createCacheCommand } from "./commands/cache.js";
21
- import { createModelsCommand } from "./commands/models.js";
22
- import { createDoctorCommand } from "./commands/doctor.js";
23
- import { createStatusCommand } from "./commands/status.js";
24
- import { createVSCodeCommand } from "./commands/vscode.js";
25
- import { createDesignCommand } from "./commands/design.js";
26
- import { getVersionString } from "./utils/version.js";
27
- import { migrateCommandHistory } from "./utils/history-migration.js";
28
- import { AGENT_CONFIG } from "./constants.js";
29
- import { getVSCodeIPCClient, disposeVSCodeIPCClient } from "./ipc/index.js";
30
- // Load environment variables
31
- // Note: DOTENV_CONFIG_QUIET is set by bin/ax-cli wrapper to suppress v17+ messages
32
- dotenv.config();
33
- // Set process title for terminal display (shows "ax-cli" instead of "node")
34
- process.title = 'ax-cli';
35
- // Global agent tracker for cleanup on exit
36
- let activeAgent = null;
37
- // Disable default SIGINT handling to let Ink handle Ctrl+C
38
- // We'll handle exit through the input system instead
39
- process.on("SIGTERM", () => {
40
- // Clean up active agent if exists
41
- if (activeAgent) {
42
- activeAgent.dispose();
43
- activeAgent = null;
44
- }
45
- // Restore terminal to normal mode before exit
46
- if (process.stdin.isTTY && process.stdin.setRawMode) {
47
- try {
48
- process.stdin.setRawMode(false);
49
- }
50
- catch (error) {
51
- // Log error for debugging but don't block shutdown
52
- console.error('[DEBUG] Failed to reset raw mode:', error instanceof Error ? error.message : String(error));
53
- }
54
- }
55
- console.log("\nGracefully shutting down...");
56
- process.exit(0);
2
+ import { createRequire } from 'module';
3
+ import { runCLI, AX_CLI_PROVIDER } from '@defai.digital/ax-core';
4
+ // Get version from package.json
5
+ const require = createRequire(import.meta.url);
6
+ const pkg = require('../package.json');
7
+ // Run the CLI with AX_CLI_PROVIDER configuration
8
+ runCLI({
9
+ provider: AX_CLI_PROVIDER,
10
+ version: pkg.version,
57
11
  });
58
- // Handle uncaught exceptions to prevent hanging
59
- process.on("uncaughtException", (error) => {
60
- console.error("Uncaught exception:", error);
61
- // Clean up active agent before exit
62
- if (activeAgent) {
63
- try {
64
- activeAgent.dispose();
65
- }
66
- catch (cleanupError) {
67
- // Log cleanup errors for debugging but don't block shutdown
68
- console.error('[DEBUG] Cleanup error during exception:', cleanupError instanceof Error ? cleanupError.message : String(cleanupError));
69
- }
70
- activeAgent = null;
71
- }
72
- process.exit(1);
73
- });
74
- process.on("unhandledRejection", (reason, promise) => {
75
- console.error("Unhandled rejection at:", promise, "reason:", reason);
76
- // Clean up active agent before exit
77
- if (activeAgent) {
78
- try {
79
- activeAgent.dispose();
80
- }
81
- catch (cleanupError) {
82
- // Log cleanup errors for debugging but don't block shutdown
83
- console.error('[DEBUG] Cleanup error during rejection:', cleanupError instanceof Error ? cleanupError.message : String(cleanupError));
84
- }
85
- activeAgent = null;
86
- }
87
- process.exit(1);
88
- });
89
- // Ensure user settings are initialized
90
- function ensureUserSettingsDirectory() {
91
- try {
92
- const manager = getSettingsManager();
93
- // This will create default settings if they don't exist
94
- manager.loadUserSettings();
95
- }
96
- catch (error) {
97
- // Log to stderr for debugging but don't block startup
98
- // This is non-critical - settings will use defaults
99
- console.error('[DEBUG] Failed to initialize user settings:', error instanceof Error ? error.message : String(error));
100
- }
101
- }
102
- // Check if configuration is valid and complete
103
- function isConfigValid() {
104
- try {
105
- const manager = getSettingsManager();
106
- const apiKey = manager.getApiKey();
107
- const baseURL = manager.getBaseURL();
108
- const model = manager.getCurrentModel();
109
- // All required fields must be present and non-empty
110
- return !!(apiKey && apiKey.trim() && baseURL && baseURL.trim() && model && model.trim());
111
- }
112
- catch (error) {
113
- // Config validation failed - log for debugging and return false
114
- if (process.env.AX_DEBUG === 'true') {
115
- console.error('[DEBUG] Config validation error:', error instanceof Error ? error.message : String(error));
116
- }
117
- return false;
118
- }
119
- }
120
- // Load API key from environment variables or user settings
121
- function loadApiKey() {
122
- // First check environment variable
123
- const envApiKey = process.env.YOUR_API_KEY;
124
- if (envApiKey) {
125
- return envApiKey;
126
- }
127
- // Fall back to settings manager
128
- const manager = getSettingsManager();
129
- return manager.getApiKey();
130
- }
131
- // Load base URL from environment variables or user settings
132
- function loadBaseURL() {
133
- // First check environment variable
134
- const envBaseUrl = process.env.AI_BASE_URL;
135
- if (envBaseUrl) {
136
- return envBaseUrl;
137
- }
138
- // Fall back to settings manager
139
- const manager = getSettingsManager();
140
- return manager.getBaseURL();
141
- }
142
- // Save command line settings to user settings file
143
- async function saveCommandLineSettings(apiKey, baseURL) {
144
- try {
145
- const manager = getSettingsManager();
146
- // Update with command line values
147
- if (apiKey) {
148
- manager.updateUserSetting("apiKey", apiKey);
149
- console.log(`✅ API key saved to ${manager.getUserSettingsPath()}`);
150
- }
151
- if (baseURL) {
152
- manager.updateUserSetting("baseURL", baseURL);
153
- console.log(`✅ Base URL saved to ${manager.getUserSettingsPath()}`);
154
- }
155
- }
156
- catch (error) {
157
- console.warn("⚠️ Could not save settings to file:", error instanceof Error ? error.message : "Unknown error");
158
- }
159
- }
160
- // Load model from user settings if not in environment
161
- function loadModel() {
162
- // First check environment variables
163
- let model = process.env.AI_MODEL;
164
- if (!model) {
165
- // Use the unified model loading from settings manager
166
- try {
167
- const manager = getSettingsManager();
168
- model = manager.getCurrentModel();
169
- }
170
- catch (error) {
171
- // Model loading failed - log for debugging, model remains undefined
172
- if (process.env.AX_DEBUG === 'true') {
173
- console.error('[DEBUG] Model loading error:', error instanceof Error ? error.message : String(error));
174
- }
175
- }
176
- }
177
- return model;
178
- }
179
- // Handle commit-and-push command in headless mode
180
- async function handleCommitAndPushHeadless(apiKey, baseURL, model, maxToolRounds) {
181
- let agent = null;
182
- try {
183
- agent = new LLMAgent(apiKey, baseURL, model, maxToolRounds);
184
- // Configure confirmation service for headless mode (auto-approve all operations)
185
- const confirmationService = ConfirmationService.getInstance();
186
- confirmationService.setSessionFlag("allOperations", true);
187
- console.log("🤖 Processing commit and push...\n");
188
- console.log("> /commit-and-push\n");
189
- // First check if there are any changes at all
190
- const initialStatusResult = await agent.executeBashCommand("git status --porcelain");
191
- if (!initialStatusResult.success || !initialStatusResult.output?.trim()) {
192
- console.log("❌ No changes to commit. Working directory is clean.");
193
- process.exit(1);
194
- }
195
- console.log("✅ git status: Changes detected");
196
- // Add all changes
197
- const addResult = await agent.executeBashCommand("git add .");
198
- if (!addResult.success) {
199
- console.log(`❌ git add: ${addResult.error || "Failed to stage changes"}`);
200
- process.exit(1);
201
- }
202
- console.log("✅ git add: Changes staged");
203
- // Get staged changes for commit message generation
204
- const diffResult = await agent.executeBashCommand("git diff --cached");
205
- // Generate commit message using AI
206
- const commitPrompt = `Generate a concise, professional git commit message for these changes:
207
-
208
- Git Status:
209
- ${initialStatusResult.output}
210
-
211
- Git Diff (staged changes):
212
- ${diffResult.output || "No staged changes shown"}
213
-
214
- Follow conventional commit format (feat:, fix:, docs:, etc.) and keep it under 72 characters.
215
- Respond with ONLY the commit message, no additional text.`;
216
- console.log("🤖 Generating commit message...");
217
- const commitMessageEntries = await agent.processUserMessage(commitPrompt);
218
- let commitMessage = "";
219
- // Extract the commit message from the AI response
220
- for (const entry of commitMessageEntries) {
221
- if (entry.type === "assistant" && entry.content.trim()) {
222
- commitMessage = entry.content.trim();
223
- break;
224
- }
225
- }
226
- if (!commitMessage) {
227
- console.log("❌ Failed to generate commit message");
228
- process.exit(1);
229
- }
230
- // Clean the commit message (remove leading/trailing quotes)
231
- const cleanCommitMessage = commitMessage.replace(/^["']|["']$/g, "");
232
- // Remove newlines to ensure single-line commit message
233
- const singleLineMessage = cleanCommitMessage.replace(/\n/g, " ").trim();
234
- console.log(`✅ Generated commit message: "${singleLineMessage}"`);
235
- // Execute the commit with proper shell escaping to prevent injection
236
- // Use single quotes and escape any single quotes in the message
237
- const escapedMessage = `'${singleLineMessage.replace(/'/g, "'\\''")}'`;
238
- const commitCommand = `git commit -m ${escapedMessage}`;
239
- const commitResult = await agent.executeBashCommand(commitCommand);
240
- if (commitResult.success) {
241
- // Safely extract first line with proper fallback
242
- const firstLine = commitResult.output?.split("\n").filter(line => line.trim())?.[0];
243
- console.log(`✅ git commit: ${firstLine || "Commit successful"}`);
244
- // If commit was successful, push to remote
245
- // First try regular push, if it fails try with upstream setup
246
- let pushResult = await agent.executeBashCommand("git push");
247
- if (!pushResult.success &&
248
- pushResult.error?.includes("no upstream branch")) {
249
- console.log("🔄 Setting upstream and pushing...");
250
- pushResult = await agent.executeBashCommand("git push -u origin HEAD");
251
- }
252
- if (pushResult.success) {
253
- // Safely extract first line with proper fallback
254
- const firstLine = pushResult.output?.split("\n").filter(line => line.trim())?.[0];
255
- console.log(`✅ git push: ${firstLine || "Push successful"}`);
256
- }
257
- else {
258
- console.log(`❌ git push: ${pushResult.error || "Push failed"}`);
259
- process.exit(1);
260
- }
261
- }
262
- else {
263
- console.log(`❌ git commit: ${commitResult.error || "Commit failed"}`);
264
- process.exit(1);
265
- }
266
- }
267
- catch (error) {
268
- console.error("❌ Error during commit and push:", extractErrorMessage(error));
269
- process.exit(1);
270
- }
271
- finally {
272
- // Clean up agent resources
273
- if (agent) {
274
- agent.dispose();
275
- }
276
- }
277
- }
278
- // Build context from VSCode integration flags
279
- async function buildContextFromFlags(options) {
280
- const contextParts = [];
281
- // Add file context
282
- if (options.file) {
283
- try {
284
- const fs = await import("fs/promises");
285
- const path = await import("path");
286
- // SECURITY FIX: Prevent path traversal attacks by using path.relative instead of startsWith
287
- // (startsWith can be bypassed with sibling directories like /repo-evil still sharing the prefix)
288
- const filePath = path.resolve(options.file);
289
- const cwd = process.cwd();
290
- const relativePath = path.relative(cwd, filePath);
291
- // Deny if the resolved path escapes the current working directory
292
- if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
293
- contextParts.push(`Error: Access denied. File must be within current working directory.`);
294
- // Skip to next iteration
295
- }
296
- else {
297
- // Validate file exists and is not a directory
298
- try {
299
- const stats = await fs.stat(filePath);
300
- if (stats.isDirectory()) {
301
- contextParts.push(`Error: ${options.file} is a directory, not a file.`);
302
- // Skip reading
303
- }
304
- else {
305
- let fileContent = await fs.readFile(filePath, "utf-8");
306
- // Apply line range if specified
307
- if (options.lineRange) {
308
- const match = options.lineRange.match(/^(\d+)-(\d+)$/);
309
- if (match) {
310
- const startLine = parseInt(match[1], 10);
311
- const endLine = parseInt(match[2], 10);
312
- // Validate parsed integers are valid numbers
313
- if (Number.isNaN(startLine) || Number.isNaN(endLine)) {
314
- contextParts.push(`Error: Invalid line range format. Expected format: START-END (e.g., 10-20).`);
315
- }
316
- else {
317
- const lines = fileContent.split("\n");
318
- // Validate line range
319
- if (startLine < 1 || startLine > lines.length) {
320
- contextParts.push(`Error: Invalid start line ${startLine}. File has ${lines.length} lines.`);
321
- }
322
- else if (endLine < startLine) {
323
- contextParts.push(`Error: Invalid line range ${startLine}-${endLine}. End line must be >= start line.`);
324
- }
325
- else {
326
- // Clamp endLine to file length
327
- const validEndLine = Math.min(endLine, lines.length);
328
- fileContent = lines.slice(startLine - 1, validEndLine).join("\n");
329
- contextParts.push(`File: ${filePath} (lines ${startLine}-${validEndLine}):\n\`\`\`\n${fileContent}\n\`\`\``);
330
- }
331
- }
332
- }
333
- else {
334
- contextParts.push(`File: ${filePath}:\n\`\`\`\n${fileContent}\n\`\`\``);
335
- }
336
- }
337
- else {
338
- contextParts.push(`File: ${filePath}:\n\`\`\`\n${fileContent}\n\`\`\``);
339
- }
340
- }
341
- }
342
- catch (statError) {
343
- contextParts.push(`Error accessing file ${options.file}: ${extractErrorMessage(statError)}`);
344
- }
345
- }
346
- }
347
- catch (error) {
348
- contextParts.push(`Error reading file ${options.file}: ${extractErrorMessage(error)}`);
349
- }
350
- }
351
- // Add selection context
352
- if (options.selection) {
353
- contextParts.push(`Selected code:\n\`\`\`\n${options.selection}\n\`\`\``);
354
- }
355
- // Add git diff context
356
- if (options.gitDiff) {
357
- try {
358
- const { execSync } = await import("child_process");
359
- const gitDiff = execSync("git diff", {
360
- encoding: "utf-8",
361
- timeout: 10000, // 10 second timeout to prevent hanging
362
- maxBuffer: 10 * 1024 * 1024 // 10MB max buffer for large diffs
363
- });
364
- if (gitDiff.trim()) {
365
- contextParts.push(`Git diff:\n\`\`\`diff\n${gitDiff}\n\`\`\``);
366
- }
367
- }
368
- catch (error) {
369
- contextParts.push(`Error getting git diff: ${extractErrorMessage(error)}`);
370
- }
371
- }
372
- return contextParts.join("\n\n");
373
- }
374
- // Headless mode processing function
375
- async function processPromptHeadless(prompt, apiKey, baseURL, model, maxToolRounds, options) {
376
- let agent = null;
377
- try {
378
- agent = new LLMAgent(apiKey, baseURL, model, maxToolRounds);
379
- // Configure thinking mode: CLI flag takes priority, then settings
380
- if (options?.think === true) {
381
- agent.setThinkingConfig({ type: "enabled" });
382
- }
383
- else if (options?.think === false) {
384
- agent.setThinkingConfig({ type: "disabled" });
385
- }
386
- else {
387
- // No CLI flag - check settings (env > project > user)
388
- const manager = getSettingsManager();
389
- const thinkingSettings = manager.getThinkingSettings();
390
- if (thinkingSettings?.enabled === true) {
391
- agent.setThinkingConfig({ type: "enabled" });
392
- }
393
- else if (thinkingSettings?.enabled === false) {
394
- agent.setThinkingConfig({ type: "disabled" });
395
- }
396
- }
397
- // Configure confirmation service for headless mode (auto-approve all operations)
398
- const confirmationService = ConfirmationService.getInstance();
399
- confirmationService.setSessionFlag("allOperations", true);
400
- // Build context from VSCode flags
401
- let fullPrompt = prompt;
402
- if (options && (options.file || options.selection || options.gitDiff)) {
403
- const context = await buildContextFromFlags(options);
404
- if (context) {
405
- fullPrompt = `${context}\n\n${prompt}`;
406
- }
407
- }
408
- // Process the user message
409
- const chatEntries = await agent.processUserMessage(fullPrompt);
410
- // Convert chat entries to OpenAI compatible message objects
411
- const messages = [];
412
- for (const entry of chatEntries) {
413
- switch (entry.type) {
414
- case "user":
415
- messages.push({
416
- role: "user",
417
- content: entry.content,
418
- });
419
- break;
420
- case "assistant":
421
- const assistantMessage = {
422
- role: "assistant",
423
- content: entry.content,
424
- };
425
- // Add tool calls if present
426
- if (entry.toolCalls && entry.toolCalls.length > 0) {
427
- assistantMessage.tool_calls = entry.toolCalls.map((toolCall) => ({
428
- id: toolCall.id,
429
- type: "function",
430
- function: {
431
- name: toolCall.function.name,
432
- arguments: toolCall.function.arguments,
433
- },
434
- }));
435
- }
436
- messages.push(assistantMessage);
437
- break;
438
- case "tool_result":
439
- if (entry.toolCall) {
440
- messages.push({
441
- role: "tool",
442
- tool_call_id: entry.toolCall.id,
443
- content: entry.content,
444
- });
445
- }
446
- break;
447
- }
448
- }
449
- // Output based on format flag
450
- if (options?.json) {
451
- // JSON output mode for IDE integration
452
- const response = {
453
- messages: messages,
454
- model: agent.getCurrentModel(),
455
- timestamp: new Date().toISOString(),
456
- };
457
- console.log(JSON.stringify(response, null, options.vscode ? 2 : 0));
458
- }
459
- else {
460
- // Standard output (existing behavior)
461
- for (const message of messages) {
462
- console.log(JSON.stringify(message));
463
- }
464
- }
465
- }
466
- catch (error) {
467
- const errorMessage = extractErrorMessage(error);
468
- const errorType = error instanceof Error ? error.constructor.name : 'Error';
469
- if (options?.json) {
470
- // JSON error output for IDE integration
471
- console.log(JSON.stringify({
472
- error: {
473
- message: errorMessage,
474
- type: errorType,
475
- },
476
- timestamp: new Date().toISOString(),
477
- }, null, options.vscode ? 2 : 0));
478
- }
479
- else {
480
- // Standard error output
481
- console.log(JSON.stringify({
482
- role: "assistant",
483
- content: `Error: ${errorMessage}`,
484
- }));
485
- }
486
- process.exit(1);
487
- }
488
- finally {
489
- // Clean up agent resources
490
- if (agent) {
491
- agent.dispose();
492
- }
493
- }
494
- }
495
- program
496
- .name("ax-cli")
497
- .description("Enterprise-Class AI Command Line Interface - Primary support for GLM (General Language Model) with multi-provider AI orchestration")
498
- .version(getVersionString(), "-v, --version", "output the current version")
499
- // Enable positional options parsing - options after a subcommand are parsed by that subcommand
500
- // This fixes conflicts between global --json and subcommand --json options
501
- .enablePositionalOptions()
502
- .argument("[message...]", "Initial message to send to AI")
503
- .option("-d, --directory <dir>", "set working directory", process.cwd())
504
- .option("-k, --api-key <key>", "AI API key (or set YOUR_API_KEY env var)")
505
- .option("-u, --base-url <url>", "AI API base URL (or set AI_BASE_URL env var)")
506
- .option("-m, --model <model>", "AI model to use (e.g., glm-4.6, llama3.1:8b, gpt-4) (or set AI_MODEL env var)")
507
- .option("-p, --prompt <prompt>", "process a single prompt and exit (headless mode)")
508
- .option("--max-tool-rounds <rounds>", `maximum number of tool execution rounds (default: ${AGENT_CONFIG.MAX_TOOL_ROUNDS})`, String(AGENT_CONFIG.MAX_TOOL_ROUNDS))
509
- .option("-c, --continue", "continue the most recent conversation from the current directory")
510
- // Thinking/Reasoning Options
511
- .option("--think", "enable thinking/reasoning mode for complex tasks (GLM-4.6)")
512
- .option("--no-think", "disable thinking mode (use standard responses)")
513
- // Sampling/Reproducibility Options
514
- .option("--deterministic", "enable deterministic mode (do_sample=false) for reproducible outputs")
515
- .option("--seed <number>", "random seed for reproducible sampling (implies --deterministic)")
516
- .option("--top-p <number>", "nucleus sampling parameter (0.0-1.0, alternative to temperature)")
517
- // VSCode Integration Flags (Phase 1)
518
- .option("--json", "output responses in JSON format (for IDE integration)")
519
- .option("--file <path>", "include file context from specified path")
520
- .option("--selection <text>", "include selected text as context")
521
- .option("--line-range <range>", "include specific line range (e.g., 10-20)")
522
- .option("--git-diff", "include git diff as context")
523
- .option("--vscode", "optimize output for VSCode integration")
524
- // Agent-First Mode Flags
525
- .option("--no-agent", "bypass agent-first mode, use direct LLM")
526
- .option("--agent <name>", "force use of specific AutomatosX agent (e.g., backend, frontend, security)")
527
- .action(async (message, options) => {
528
- if (options.directory) {
529
- try {
530
- process.chdir(options.directory);
531
- }
532
- catch (error) {
533
- console.error(`Error changing directory to ${options.directory}:`, extractErrorMessage(error));
534
- process.exit(1);
535
- }
536
- }
537
- try {
538
- // Check if running in interactive mode (no prompt, api-key, or base-url flags)
539
- const isInteractiveMode = !options.prompt && !options.apiKey && !options.baseUrl;
540
- // If interactive mode and config is invalid, automatically run setup
541
- if (isInteractiveMode && !isConfigValid()) {
542
- console.log("⚠️ Configuration file not found or incomplete.\n");
543
- console.log("Let's set up AX CLI first...\n");
544
- // Import and run setup command
545
- const { createSetupCommand } = await import("./commands/setup.js");
546
- const setupCommand = createSetupCommand();
547
- // Run setup command with empty args (will prompt user)
548
- await setupCommand.parseAsync(["node", "ax-cli", "setup"], { from: "user" });
549
- // After setup completes, re-check config
550
- if (!isConfigValid()) {
551
- console.error("\n❌ Setup did not complete successfully. Please try again.");
552
- process.exit(1);
553
- }
554
- console.log("\n");
555
- }
556
- // Get API key from options, environment, or user settings
557
- const apiKey = options.apiKey || loadApiKey();
558
- const baseURL = options.baseUrl || loadBaseURL();
559
- const model = options.model || loadModel();
560
- const parsedMaxToolRounds = options.maxToolRounds ? parseInt(options.maxToolRounds.toString(), 10) : AGENT_CONFIG.MAX_TOOL_ROUNDS;
561
- const maxToolRounds = Number.isFinite(parsedMaxToolRounds) && parsedMaxToolRounds > 0 ? parsedMaxToolRounds : AGENT_CONFIG.MAX_TOOL_ROUNDS;
562
- if (!apiKey) {
563
- console.error("❌ Error: API key required. Set YOUR_API_KEY environment variable, use --api-key flag, or save to ~/.ax-cli/config.json");
564
- process.exit(1);
565
- }
566
- // Save API key and base URL to user settings if provided via command line
567
- if (options.apiKey || options.baseUrl) {
568
- await saveCommandLineSettings(options.apiKey, options.baseUrl);
569
- }
570
- // Headless mode: process prompt and exit
571
- if (options.prompt) {
572
- await processPromptHeadless(options.prompt, apiKey, baseURL, model, maxToolRounds, {
573
- json: options.json,
574
- file: options.file,
575
- selection: options.selection,
576
- lineRange: options.lineRange,
577
- gitDiff: options.gitDiff,
578
- vscode: options.vscode,
579
- think: options.think,
580
- });
581
- return;
582
- }
583
- // Interactive mode: launch UI
584
- // Check if stdin supports raw mode (required for Ink)
585
- if (!process.stdin.isTTY || !process.stdin.setRawMode) {
586
- console.error("❌ Interactive mode not supported: Terminal does not support raw mode");
587
- console.error("💡 Use --prompt flag for headless mode instead");
588
- console.error(" Example: ax-cli --prompt 'your message here'");
589
- process.exit(1);
590
- }
591
- // Check for updates on startup (respects user settings)
592
- const updateResult = await checkForUpdatesOnStartup();
593
- if (updateResult.hasUpdate) {
594
- const updated = await promptAndInstallUpdate(updateResult.currentVersion, updateResult.latestVersion);
595
- if (updated) {
596
- // Exit after update so user restarts with new version
597
- process.exit(0);
598
- }
599
- }
600
- const agent = new LLMAgent(apiKey, baseURL, model, maxToolRounds);
601
- activeAgent = agent; // Track for cleanup on exit
602
- // Configure thinking mode: CLI flag takes priority, then settings
603
- if (options.think === true) {
604
- agent.setThinkingConfig({ type: "enabled" });
605
- }
606
- else if (options.think === false) {
607
- agent.setThinkingConfig({ type: "disabled" });
608
- }
609
- else {
610
- // No CLI flag - check settings (env > project > user)
611
- const manager = getSettingsManager();
612
- const thinkingSettings = manager.getThinkingSettings();
613
- if (thinkingSettings?.enabled === true) {
614
- agent.setThinkingConfig({ type: "enabled" });
615
- }
616
- else if (thinkingSettings?.enabled === false) {
617
- agent.setThinkingConfig({ type: "disabled" });
618
- }
619
- }
620
- // Handle --continue flag: load directory-specific session
621
- if (options.continue) {
622
- const currentDir = process.cwd();
623
- const { getHistoryManager } = await import("./utils/history-manager.js");
624
- // Create a new history manager instance for this project directory
625
- const historyManager = getHistoryManager(currentDir, true);
626
- const previousHistory = historyManager.loadHistory();
627
- if (previousHistory.length > 0) {
628
- console.log(`🔄 Continuing conversation from ${currentDir}`);
629
- console.log(`📜 Loaded ${previousHistory.length} previous messages\n`);
630
- }
631
- else {
632
- console.log(`💬 Starting new conversation in ${currentDir}\n`);
633
- }
634
- console.log("🤖 Starting AX CLI AI Assistant...\n");
635
- }
636
- else {
637
- console.log("🤖 Starting AX CLI AI Assistant...\n");
638
- }
639
- ensureUserSettingsDirectory();
640
- // Support variadic positional arguments for multi-word initial message
641
- const initialMessage = Array.isArray(message)
642
- ? message.join(" ")
643
- : message;
644
- // v3.8.0: Enable bracketed paste mode for reliable paste detection
645
- // Check if user has enabled it in settings (default: true)
646
- const manager = getSettingsManager();
647
- const pasteSettings = manager.getPasteSettings();
648
- const enableBracketedPaste = pasteSettings.enableBracketedPaste ?? true;
649
- if (enableBracketedPaste) {
650
- // Enable bracketed paste mode
651
- // Terminal will send \x1b[200~ before paste and \x1b[201~ after
652
- process.stdout.write('\x1b[?2004h');
653
- }
654
- const { waitUntilExit } = render(React.createElement(ChatInterface, {
655
- agent,
656
- initialMessage,
657
- loadPreviousHistory: options.continue || false,
658
- agentFirstDisabled: options.agent === false, // --no-agent flag
659
- forcedAgent: typeof options.agent === 'string' ? options.agent : undefined, // --agent <name>
660
- }));
661
- // Wait for app to exit and clean up
662
- await waitUntilExit();
663
- // Disable bracketed paste mode on exit
664
- if (enableBracketedPaste) {
665
- process.stdout.write('\x1b[?2004l');
666
- }
667
- if (activeAgent) {
668
- activeAgent.dispose();
669
- activeAgent = null;
670
- }
671
- }
672
- catch (error) {
673
- console.error("❌ Error initializing AX CLI:", extractErrorMessage(error));
674
- process.exit(1);
675
- }
676
- });
677
- // Git subcommand
678
- const gitCommand = program
679
- .command("git")
680
- .description("Git operations with AI assistance");
681
- gitCommand
682
- .command("commit-and-push")
683
- .description("Generate AI commit message and push to remote")
684
- .option("-d, --directory <dir>", "set working directory", process.cwd())
685
- .option("-k, --api-key <key>", "AI API key (or set YOUR_API_KEY env var)")
686
- .option("-u, --base-url <url>", "AI API base URL (or set AI_BASE_URL env var)")
687
- .option("-m, --model <model>", "AI model to use (e.g., glm-4.6, llama3.1:8b, gpt-4) (or set AI_MODEL env var)")
688
- .option("--max-tool-rounds <rounds>", `maximum number of tool execution rounds (default: ${AGENT_CONFIG.MAX_TOOL_ROUNDS})`, String(AGENT_CONFIG.MAX_TOOL_ROUNDS))
689
- .action(async (options) => {
690
- if (options.directory) {
691
- try {
692
- process.chdir(options.directory);
693
- }
694
- catch (error) {
695
- console.error(`Error changing directory to ${options.directory}:`, extractErrorMessage(error));
696
- process.exit(1);
697
- }
698
- }
699
- try {
700
- // Get API key from options, environment, or user settings
701
- const apiKey = options.apiKey || loadApiKey();
702
- const baseURL = options.baseUrl || loadBaseURL();
703
- const model = options.model || loadModel();
704
- const parsedMaxToolRounds = options.maxToolRounds ? parseInt(options.maxToolRounds.toString(), 10) : AGENT_CONFIG.MAX_TOOL_ROUNDS;
705
- const maxToolRounds = Number.isFinite(parsedMaxToolRounds) && parsedMaxToolRounds > 0 ? parsedMaxToolRounds : AGENT_CONFIG.MAX_TOOL_ROUNDS;
706
- if (!apiKey) {
707
- console.error("❌ Error: API key required. Set YOUR_API_KEY environment variable, use --api-key flag, or save to ~/.ax-cli/config.json");
708
- process.exit(1);
709
- }
710
- // Save API key and base URL to user settings if provided via command line
711
- if (options.apiKey || options.baseUrl) {
712
- await saveCommandLineSettings(options.apiKey, options.baseUrl);
713
- }
714
- await handleCommitAndPushHeadless(apiKey, baseURL, model, maxToolRounds);
715
- }
716
- catch (error) {
717
- console.error("❌ Error during git commit-and-push:", extractErrorMessage(error));
718
- process.exit(1);
719
- }
720
- });
721
- // MCP command (with migration subcommand)
722
- const mcpCommand = createMCPCommand();
723
- mcpCommand.addCommand(createMCPMigrateCommand());
724
- program.addCommand(mcpCommand);
725
- // Frontend command (workflows for front-end development)
726
- program.addCommand(createFrontendCommand());
727
- // Init command
728
- program.addCommand(createInitCommand());
729
- // Templates command
730
- program.addCommand(createTemplatesCommand());
731
- // Memory command
732
- program.addCommand(createMemoryCommand());
733
- // Update command
734
- program.addCommand(createUpdateCommand());
735
- // Setup command
736
- program.addCommand(createSetupCommand());
737
- // Usage command
738
- program.addCommand(createUsageCommand());
739
- // Cache command
740
- program.addCommand(createCacheCommand());
741
- // Models command
742
- program.addCommand(createModelsCommand());
743
- // Doctor command
744
- program.addCommand(createDoctorCommand());
745
- // Status command
746
- program.addCommand(createStatusCommand());
747
- // VSCode command
748
- program.addCommand(createVSCodeCommand());
749
- // Design command (Figma integration)
750
- program.addCommand(createDesignCommand());
751
- // Run migration for command history security fix (only runs once)
752
- migrateCommandHistory();
753
- // Try to connect to VS Code extension IPC (async, non-blocking)
754
- // This enables diff preview in VS Code when the extension is running
755
- const ipcClient = getVSCodeIPCClient();
756
- ipcClient.connect().catch((error) => {
757
- // VS Code extension IPC is optional - log at debug level only
758
- // This is expected to fail when not running from VS Code
759
- if (process.env.AX_DEBUG === 'true') {
760
- console.error('[DEBUG] VS Code IPC connection not available:', error instanceof Error ? error.message : String(error));
761
- }
762
- });
763
- // Cleanup IPC on exit
764
- process.on('exit', () => {
765
- disposeVSCodeIPCClient();
766
- });
767
- program.parse();
768
12
  //# sourceMappingURL=index.js.map