@mndrk/agx 1.0.2 → 1.3.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/.claude/settings.local.json +10 -0
- package/README.md +162 -3
- package/index.js +794 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,14 +4,55 @@ Unified AI Agent Wrapper for Gemini, Claude, and Ollama.
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### Via npm (recommended)
|
|
8
|
+
|
|
8
9
|
```bash
|
|
10
|
+
npm install -g @mndrk/agx
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### From source
|
|
14
|
+
|
|
15
|
+
Clone the repository and link locally:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
git clone https://github.com/ramarlina/agx.git
|
|
19
|
+
cd agx
|
|
9
20
|
npm link
|
|
10
21
|
```
|
|
11
|
-
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
When you run `agx` for the first time, it will automatically start the setup wizard:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
agx
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The setup wizard will:
|
|
32
|
+
1. Detect which AI providers are installed on your system
|
|
33
|
+
2. Guide you through installing any missing providers
|
|
34
|
+
3. Help you authenticate with your chosen providers
|
|
35
|
+
4. Set your default provider
|
|
36
|
+
|
|
37
|
+
After setup, you can start using agx immediately:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
agx --prompt "hello world"
|
|
41
|
+
```
|
|
12
42
|
|
|
13
43
|
## Usage
|
|
14
44
|
|
|
45
|
+
### With Default Provider
|
|
46
|
+
|
|
47
|
+
Once configured, you can run prompts without specifying a provider:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
agx --prompt "explain this code"
|
|
51
|
+
agx -p "summarize the file"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### With Specific Provider
|
|
55
|
+
|
|
15
56
|
```bash
|
|
16
57
|
agx <provider> [options] --prompt "<prompt>"
|
|
17
58
|
```
|
|
@@ -44,11 +85,105 @@ Use `--` to pass arguments directly to the underlying CLI:
|
|
|
44
85
|
agx claude -- --resume
|
|
45
86
|
```
|
|
46
87
|
|
|
88
|
+
## Configuration Commands
|
|
89
|
+
|
|
90
|
+
### `agx init`
|
|
91
|
+
|
|
92
|
+
Run the setup wizard manually. This is useful if you want to reconfigure agx or add new providers:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
agx init
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The wizard will:
|
|
99
|
+
- Detect installed providers (claude, gemini, ollama)
|
|
100
|
+
- Guide you through installation and authentication
|
|
101
|
+
- Let you set or change your default provider
|
|
102
|
+
|
|
103
|
+
### `agx config`
|
|
104
|
+
|
|
105
|
+
Open an interactive configuration menu to manage your agx settings:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
agx config
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### `agx add <provider>`
|
|
112
|
+
|
|
113
|
+
Install a specific AI provider:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
agx add claude # Install Claude CLI
|
|
117
|
+
agx add gemini # Install Gemini CLI
|
|
118
|
+
agx add ollama # Install Ollama
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `agx login <provider>`
|
|
122
|
+
|
|
123
|
+
Authenticate with a provider:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
agx login claude # Login to Claude
|
|
127
|
+
agx login gemini # Login to Gemini
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### `agx status`
|
|
131
|
+
|
|
132
|
+
View your current configuration, including installed providers and default settings:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
agx status
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Example output:
|
|
139
|
+
```
|
|
140
|
+
agx Configuration Status
|
|
141
|
+
------------------------
|
|
142
|
+
Default Provider: claude
|
|
143
|
+
|
|
144
|
+
Installed Providers:
|
|
145
|
+
✓ claude (authenticated)
|
|
146
|
+
✓ gemini (authenticated)
|
|
147
|
+
✓ ollama (running)
|
|
148
|
+
|
|
149
|
+
Config file: ~/.agx/config.json
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Skill System
|
|
153
|
+
|
|
154
|
+
agx includes a skill system that helps AI agents understand how to use agx effectively.
|
|
155
|
+
|
|
156
|
+
### `agx skill`
|
|
157
|
+
|
|
158
|
+
View the agx skill (LLM instructions):
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
agx skill
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
This displays the skill file that describes agx's capabilities and usage patterns for AI agents.
|
|
165
|
+
|
|
166
|
+
### `agx skill install`
|
|
167
|
+
|
|
168
|
+
Install the agx skill to Claude and/or Gemini so AI agents know how to use agx:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
agx skill install
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
This adds the agx skill to your AI provider's skill directory, enabling features like:
|
|
175
|
+
- AI agents can call other AI providers through agx
|
|
176
|
+
- Cross-provider collaboration (e.g., Claude can ask Gemini for help)
|
|
177
|
+
- Consistent command patterns across all providers
|
|
178
|
+
|
|
47
179
|
## LLM-Predictable Command Patterns
|
|
48
180
|
|
|
49
181
|
For LLMs constructing commands, use these canonical patterns:
|
|
50
182
|
|
|
51
183
|
```bash
|
|
184
|
+
# Pattern: agx --prompt "<prompt>" (uses default provider)
|
|
185
|
+
agx --prompt "explain this code"
|
|
186
|
+
|
|
52
187
|
# Pattern: agx <provider> --prompt "<prompt>"
|
|
53
188
|
agx claude --prompt "explain this code"
|
|
54
189
|
agx gemini --prompt "summarize the file"
|
|
@@ -69,7 +204,7 @@ agx claude --print --prompt "what is 2+2"
|
|
|
69
204
|
### Command Structure
|
|
70
205
|
|
|
71
206
|
```
|
|
72
|
-
agx
|
|
207
|
+
agx [provider] [--model <name>] [--yolo] [--print] --prompt "<prompt>"
|
|
73
208
|
```
|
|
74
209
|
|
|
75
210
|
**Rules for LLMs:**
|
|
@@ -77,6 +212,30 @@ agx <provider> [--model <name>] [--yolo] [--print] --prompt "<prompt>"
|
|
|
77
212
|
2. Quote the prompt with double quotes
|
|
78
213
|
3. Place options before `--prompt`
|
|
79
214
|
4. Use full provider names (`claude`, `gemini`, `ollama`) for clarity
|
|
215
|
+
5. Provider is optional if a default is configured
|
|
216
|
+
|
|
217
|
+
## Configuration File
|
|
218
|
+
|
|
219
|
+
agx stores its configuration in `~/.agx/config.json`:
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"defaultProvider": "claude",
|
|
224
|
+
"providers": {
|
|
225
|
+
"claude": {
|
|
226
|
+
"installed": true,
|
|
227
|
+
"authenticated": true
|
|
228
|
+
},
|
|
229
|
+
"gemini": {
|
|
230
|
+
"installed": true,
|
|
231
|
+
"authenticated": true
|
|
232
|
+
},
|
|
233
|
+
"ollama": {
|
|
234
|
+
"installed": true
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
80
239
|
|
|
81
240
|
## Ollama Support
|
|
82
241
|
|
package/index.js
CHANGED
|
@@ -1,9 +1,746 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { spawn } = require('child_process');
|
|
3
|
+
const { spawn, execSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
|
|
8
|
+
// Config paths
|
|
9
|
+
const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.agx');
|
|
10
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
11
|
+
|
|
12
|
+
// agx skill - instructions for LLMs on how to use agx
|
|
13
|
+
const AGX_SKILL = `---
|
|
14
|
+
name: agx
|
|
15
|
+
description: Use agx to run AI agents (Claude, Gemini, Ollama) from the command line
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# agx - Unified AI Agent CLI
|
|
19
|
+
|
|
20
|
+
Use \`agx\` to run AI agents from the command line. It wraps Claude Code, Gemini CLI, and Ollama.
|
|
21
|
+
|
|
22
|
+
## Syntax
|
|
23
|
+
|
|
24
|
+
\`\`\`bash
|
|
25
|
+
agx <provider> [options] --prompt "<prompt>"
|
|
26
|
+
agx [options] --prompt "<prompt>" # uses default provider
|
|
27
|
+
\`\`\`
|
|
28
|
+
|
|
29
|
+
## Providers
|
|
30
|
+
|
|
31
|
+
| Provider | Aliases | Description |
|
|
32
|
+
|----------|---------|-------------|
|
|
33
|
+
| claude | c, cl | Anthropic Claude Code |
|
|
34
|
+
| gemini | g, gem | Google Gemini CLI |
|
|
35
|
+
| ollama | o, ol | Local Ollama models |
|
|
36
|
+
|
|
37
|
+
## Common Options
|
|
38
|
+
|
|
39
|
+
| Option | Short | Description |
|
|
40
|
+
|--------|-------|-------------|
|
|
41
|
+
| --prompt | -p | The prompt to send |
|
|
42
|
+
| --model | -m | Model name to use |
|
|
43
|
+
| --yolo | -y | Skip permission prompts |
|
|
44
|
+
| --print | | Non-interactive output |
|
|
45
|
+
| --interactive | -i | Force interactive mode |
|
|
46
|
+
|
|
47
|
+
## Examples
|
|
48
|
+
|
|
49
|
+
\`\`\`bash
|
|
50
|
+
# Simple prompt
|
|
51
|
+
agx claude --prompt "explain this code"
|
|
52
|
+
|
|
53
|
+
# Use default provider
|
|
54
|
+
agx --prompt "what does this function do?"
|
|
55
|
+
|
|
56
|
+
# Skip confirmations
|
|
57
|
+
agx c --yolo --prompt "fix the tests"
|
|
58
|
+
|
|
59
|
+
# Specify model
|
|
60
|
+
agx ollama --model qwen3:8b --prompt "write a function"
|
|
61
|
+
|
|
62
|
+
# Non-interactive (just output)
|
|
63
|
+
agx gemini --print --prompt "summarize README.md"
|
|
64
|
+
\`\`\`
|
|
65
|
+
|
|
66
|
+
## When to use agx
|
|
67
|
+
|
|
68
|
+
- Quick AI queries from terminal
|
|
69
|
+
- Running prompts across different AI providers
|
|
70
|
+
- Scripting AI interactions
|
|
71
|
+
- When you need --yolo mode to skip confirmations
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
// ANSI colors
|
|
75
|
+
const c = {
|
|
76
|
+
reset: '\x1b[0m',
|
|
77
|
+
bold: '\x1b[1m',
|
|
78
|
+
dim: '\x1b[2m',
|
|
79
|
+
green: '\x1b[32m',
|
|
80
|
+
yellow: '\x1b[33m',
|
|
81
|
+
blue: '\x1b[34m',
|
|
82
|
+
cyan: '\x1b[36m',
|
|
83
|
+
red: '\x1b[31m',
|
|
84
|
+
magenta: '\x1b[35m'
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Check if a command exists
|
|
88
|
+
function commandExists(cmd) {
|
|
89
|
+
try {
|
|
90
|
+
execSync(`which ${cmd}`, { stdio: 'ignore' });
|
|
91
|
+
return true;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Load config
|
|
98
|
+
function loadConfig() {
|
|
99
|
+
try {
|
|
100
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
101
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
102
|
+
}
|
|
103
|
+
} catch {}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Save config
|
|
108
|
+
function saveConfig(config) {
|
|
109
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
110
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Interactive prompt helper
|
|
116
|
+
function prompt(question) {
|
|
117
|
+
const rl = readline.createInterface({
|
|
118
|
+
input: process.stdin,
|
|
119
|
+
output: process.stdout
|
|
120
|
+
});
|
|
121
|
+
return new Promise(resolve => {
|
|
122
|
+
rl.question(question, answer => {
|
|
123
|
+
rl.close();
|
|
124
|
+
resolve(answer.trim());
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Detect available providers
|
|
130
|
+
function detectProviders() {
|
|
131
|
+
return {
|
|
132
|
+
claude: commandExists('claude'),
|
|
133
|
+
gemini: commandExists('gemini'),
|
|
134
|
+
ollama: commandExists('ollama')
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Print provider status
|
|
139
|
+
function printProviderStatus(providers) {
|
|
140
|
+
console.log(`\n${c.bold}Detected Providers:${c.reset}\n`);
|
|
141
|
+
|
|
142
|
+
const status = (installed) => installed
|
|
143
|
+
? `${c.green}✓ installed${c.reset}`
|
|
144
|
+
: `${c.dim}✗ not found${c.reset}`;
|
|
145
|
+
|
|
146
|
+
console.log(` ${c.cyan}claude${c.reset} │ Anthropic Claude Code │ ${status(providers.claude)}`);
|
|
147
|
+
console.log(` ${c.cyan}gemini${c.reset} │ Google Gemini CLI │ ${status(providers.gemini)}`);
|
|
148
|
+
console.log(` ${c.cyan}ollama${c.reset} │ Local Ollama │ ${status(providers.ollama)}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Run a command with inherited stdio (interactive)
|
|
152
|
+
function runInteractive(cmd, args = [], options = {}) {
|
|
153
|
+
return new Promise((resolve) => {
|
|
154
|
+
const child = spawn(cmd, args, {
|
|
155
|
+
stdio: 'inherit',
|
|
156
|
+
shell: true,
|
|
157
|
+
...options
|
|
158
|
+
});
|
|
159
|
+
child.on('close', (code) => resolve(code === 0));
|
|
160
|
+
child.on('error', () => resolve(false));
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Run a command silently and return success
|
|
165
|
+
function runSilent(cmd) {
|
|
166
|
+
try {
|
|
167
|
+
execSync(cmd, { stdio: 'ignore' });
|
|
168
|
+
return true;
|
|
169
|
+
} catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check if ollama server is running
|
|
175
|
+
function isOllamaRunning() {
|
|
176
|
+
try {
|
|
177
|
+
execSync('curl -s http://localhost:11434/api/tags', { stdio: 'ignore', timeout: 2000 });
|
|
178
|
+
return true;
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Get list of ollama models
|
|
185
|
+
function getOllamaModels() {
|
|
186
|
+
try {
|
|
187
|
+
const result = execSync('ollama list 2>/dev/null', { encoding: 'utf8' });
|
|
188
|
+
const lines = result.trim().split('\n').slice(1); // skip header
|
|
189
|
+
return lines.map(l => l.split(/\s+/)[0]).filter(Boolean);
|
|
190
|
+
} catch {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ==================== SKILL ====================
|
|
196
|
+
|
|
197
|
+
// View the agx skill
|
|
198
|
+
function showSkill() {
|
|
199
|
+
console.log(AGX_SKILL);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Check if skill is installed for a provider
|
|
203
|
+
function isSkillInstalled(provider) {
|
|
204
|
+
const skillDir = path.join(
|
|
205
|
+
process.env.HOME || process.env.USERPROFILE,
|
|
206
|
+
provider === 'claude' ? '.claude' : '.gemini',
|
|
207
|
+
'skills',
|
|
208
|
+
'agx'
|
|
209
|
+
);
|
|
210
|
+
return fs.existsSync(path.join(skillDir, 'SKILL.md'));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Install agx skill to a provider's skills directory
|
|
214
|
+
function installSkillTo(provider) {
|
|
215
|
+
const baseDir = path.join(
|
|
216
|
+
process.env.HOME || process.env.USERPROFILE,
|
|
217
|
+
provider === 'claude' ? '.claude' : '.gemini',
|
|
218
|
+
'skills',
|
|
219
|
+
'agx'
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (!fs.existsSync(baseDir)) {
|
|
223
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
fs.writeFileSync(path.join(baseDir, 'SKILL.md'), AGX_SKILL);
|
|
227
|
+
return baseDir;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Handle skill command
|
|
231
|
+
async function handleSkillCommand(args) {
|
|
232
|
+
const subCmd = args[1];
|
|
233
|
+
|
|
234
|
+
if (!subCmd || subCmd === 'view' || subCmd === 'show') {
|
|
235
|
+
// Show skill content
|
|
236
|
+
console.log(`\n${c.bold}${c.cyan}/agx${c.reset} - ${c.dim}LLM instructions for using agx${c.reset}\n`);
|
|
237
|
+
|
|
238
|
+
// Check installation status
|
|
239
|
+
const claudeInstalled = isSkillInstalled('claude');
|
|
240
|
+
const geminiInstalled = isSkillInstalled('gemini');
|
|
241
|
+
|
|
242
|
+
if (claudeInstalled || geminiInstalled) {
|
|
243
|
+
console.log(`${c.green}Installed:${c.reset}`);
|
|
244
|
+
if (claudeInstalled) console.log(` ${c.dim}~/.claude/skills/agx/SKILL.md${c.reset}`);
|
|
245
|
+
if (geminiInstalled) console.log(` ${c.dim}~/.gemini/skills/agx/SKILL.md${c.reset}`);
|
|
246
|
+
console.log('');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log(c.dim + '─'.repeat(60) + c.reset);
|
|
250
|
+
console.log(AGX_SKILL);
|
|
251
|
+
console.log(c.dim + '─'.repeat(60) + c.reset);
|
|
252
|
+
|
|
253
|
+
if (!claudeInstalled && !geminiInstalled) {
|
|
254
|
+
console.log(`\n${c.dim}Install with: ${c.reset}agx skill install`);
|
|
255
|
+
}
|
|
256
|
+
console.log('');
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (subCmd === 'install' || subCmd === 'add') {
|
|
261
|
+
const target = args[2]; // optional: claude, gemini, or all
|
|
262
|
+
|
|
263
|
+
console.log(`\n${c.bold}Install agx skill${c.reset}\n`);
|
|
264
|
+
|
|
265
|
+
if (!target || target === 'all') {
|
|
266
|
+
// Install to all available
|
|
267
|
+
const providers = detectProviders();
|
|
268
|
+
let installed = 0;
|
|
269
|
+
|
|
270
|
+
if (providers.claude) {
|
|
271
|
+
const dest = installSkillTo('claude');
|
|
272
|
+
console.log(`${c.green}✓${c.reset} Installed to ${c.dim}${dest}${c.reset}`);
|
|
273
|
+
installed++;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (providers.gemini) {
|
|
277
|
+
const dest = installSkillTo('gemini');
|
|
278
|
+
console.log(`${c.green}✓${c.reset} Installed to ${c.dim}${dest}${c.reset}`);
|
|
279
|
+
installed++;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (installed === 0) {
|
|
283
|
+
console.log(`${c.yellow}No providers installed.${c.reset} Run ${c.cyan}agx init${c.reset} first.`);
|
|
284
|
+
} else {
|
|
285
|
+
console.log(`\n${c.dim}LLMs can now use /agx to learn how to run agx commands.${c.reset}\n`);
|
|
286
|
+
}
|
|
287
|
+
} else if (target === 'claude' || target === 'gemini') {
|
|
288
|
+
const dest = installSkillTo(target);
|
|
289
|
+
console.log(`${c.green}✓${c.reset} Installed to ${c.dim}${dest}${c.reset}`);
|
|
290
|
+
console.log(`\n${c.dim}LLMs can now use /agx to learn how to run agx commands.${c.reset}\n`);
|
|
291
|
+
} else {
|
|
292
|
+
console.log(`${c.yellow}Unknown target:${c.reset} ${target}`);
|
|
293
|
+
console.log(`${c.dim}Usage: agx skill install [claude|gemini|all]${c.reset}\n`);
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Unknown subcommand
|
|
299
|
+
console.log(`${c.bold}agx skill${c.reset} - Manage the agx skill for LLMs\n`);
|
|
300
|
+
console.log(`${c.dim}Commands:${c.reset}`);
|
|
301
|
+
console.log(` ${c.cyan}agx skill${c.reset} View the skill content`);
|
|
302
|
+
console.log(` ${c.cyan}agx skill install${c.reset} Install to all providers`);
|
|
303
|
+
console.log(` ${c.cyan}agx skill install claude${c.reset} Install to Claude only`);
|
|
304
|
+
console.log('');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ==================== PROVIDERS ====================
|
|
308
|
+
|
|
309
|
+
// Provider installation info
|
|
310
|
+
const PROVIDERS = {
|
|
311
|
+
claude: {
|
|
312
|
+
name: 'Claude Code',
|
|
313
|
+
installCmd: 'npm install -g @anthropic-ai/claude-code',
|
|
314
|
+
description: 'Anthropic Claude AI assistant'
|
|
315
|
+
},
|
|
316
|
+
gemini: {
|
|
317
|
+
name: 'Gemini CLI',
|
|
318
|
+
installCmd: 'npm install -g @google/gemini-cli',
|
|
319
|
+
description: 'Google Gemini AI assistant'
|
|
320
|
+
},
|
|
321
|
+
ollama: {
|
|
322
|
+
name: 'Ollama',
|
|
323
|
+
installCmd: process.platform === 'darwin'
|
|
324
|
+
? 'brew install ollama'
|
|
325
|
+
: 'curl -fsSL https://ollama.ai/install.sh | sh',
|
|
326
|
+
description: 'Local AI models'
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// Install a provider
|
|
331
|
+
async function installProvider(provider) {
|
|
332
|
+
const info = PROVIDERS[provider];
|
|
333
|
+
if (!info) return false;
|
|
334
|
+
|
|
335
|
+
console.log(`\n${c.cyan}Installing ${info.name}...${c.reset}\n`);
|
|
336
|
+
console.log(`${c.dim}$ ${info.installCmd}${c.reset}\n`);
|
|
337
|
+
|
|
338
|
+
const success = await runInteractive(info.installCmd);
|
|
339
|
+
|
|
340
|
+
if (success && commandExists(provider)) {
|
|
341
|
+
console.log(`\n${c.green}✓${c.reset} ${info.name} installed successfully!`);
|
|
342
|
+
return true;
|
|
343
|
+
} else {
|
|
344
|
+
console.log(`\n${c.red}✗${c.reset} Installation failed. Try manually:`);
|
|
345
|
+
console.log(` ${c.dim}${info.installCmd}${c.reset}`);
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Login/authenticate a provider
|
|
351
|
+
async function loginProvider(provider) {
|
|
352
|
+
console.log('');
|
|
353
|
+
|
|
354
|
+
if (provider === 'claude') {
|
|
355
|
+
console.log(`${c.cyan}Launching Claude Code for authentication...${c.reset}`);
|
|
356
|
+
console.log(`${c.dim}This will open a browser to log in with your Anthropic account.${c.reset}\n`);
|
|
357
|
+
await runInteractive('claude');
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (provider === 'gemini') {
|
|
362
|
+
console.log(`${c.cyan}Launching Gemini CLI for authentication...${c.reset}`);
|
|
363
|
+
console.log(`${c.dim}This will open a browser to log in with your Google account.${c.reset}\n`);
|
|
364
|
+
await runInteractive('gemini');
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (provider === 'ollama') {
|
|
369
|
+
// Check if server is running
|
|
370
|
+
if (!isOllamaRunning()) {
|
|
371
|
+
console.log(`${c.yellow}Ollama server is not running.${c.reset}`);
|
|
372
|
+
const startIt = await prompt(`Start it now? [Y/n]: `);
|
|
373
|
+
if (startIt.toLowerCase() !== 'n') {
|
|
374
|
+
console.log(`\n${c.cyan}Starting Ollama server in background...${c.reset}`);
|
|
375
|
+
spawn('ollama', ['serve'], {
|
|
376
|
+
detached: true,
|
|
377
|
+
stdio: 'ignore'
|
|
378
|
+
}).unref();
|
|
379
|
+
// Wait a moment for startup
|
|
380
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
381
|
+
if (isOllamaRunning()) {
|
|
382
|
+
console.log(`${c.green}✓${c.reset} Ollama server started!`);
|
|
383
|
+
} else {
|
|
384
|
+
console.log(`${c.yellow}Server may still be starting. Run ${c.reset}ollama serve${c.yellow} manually if needed.${c.reset}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
console.log(`${c.green}✓${c.reset} Ollama server is running`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Check for models
|
|
392
|
+
const models = getOllamaModels();
|
|
393
|
+
if (models.length === 0) {
|
|
394
|
+
console.log(`\n${c.yellow}No models installed.${c.reset}`);
|
|
395
|
+
console.log(`\n${c.bold}Popular models:${c.reset}`);
|
|
396
|
+
console.log(` ${c.cyan}1${c.reset}) qwen3:8b ${c.dim}(4.9 GB) - Great all-rounder${c.reset}`);
|
|
397
|
+
console.log(` ${c.cyan}2${c.reset}) llama3.2:3b ${c.dim}(2.0 GB) - Fast & lightweight${c.reset}`);
|
|
398
|
+
console.log(` ${c.cyan}3${c.reset}) codellama:7b ${c.dim}(3.8 GB) - Code specialist${c.reset}`);
|
|
399
|
+
console.log(` ${c.cyan}4${c.reset}) mistral:7b ${c.dim}(4.1 GB) - Good general model${c.reset}`);
|
|
400
|
+
console.log(` ${c.cyan}5${c.reset}) Skip for now`);
|
|
401
|
+
|
|
402
|
+
const choice = await prompt(`\nWhich model to pull? [1]: `);
|
|
403
|
+
const modelMap = {
|
|
404
|
+
'1': 'qwen3:8b',
|
|
405
|
+
'2': 'llama3.2:3b',
|
|
406
|
+
'3': 'codellama:7b',
|
|
407
|
+
'4': 'mistral:7b',
|
|
408
|
+
'': 'qwen3:8b'
|
|
409
|
+
};
|
|
410
|
+
const model = modelMap[choice];
|
|
411
|
+
if (model) {
|
|
412
|
+
console.log(`\n${c.cyan}Pulling ${model}...${c.reset}`);
|
|
413
|
+
console.log(`${c.dim}This may take a few minutes depending on your connection.${c.reset}\n`);
|
|
414
|
+
await runInteractive(`ollama pull ${model}`);
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
console.log(`${c.green}✓${c.reset} Found ${models.length} model(s): ${c.dim}${models.slice(0, 3).join(', ')}${models.length > 3 ? '...' : ''}${c.reset}`);
|
|
418
|
+
}
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Run onboarding
|
|
426
|
+
async function runOnboarding() {
|
|
427
|
+
console.log(`
|
|
428
|
+
${c.bold}${c.cyan}╭─────────────────────────────────────────╮${c.reset}
|
|
429
|
+
${c.bold}${c.cyan}│${c.reset} ${c.bold}Welcome to agx${c.reset} ${c.cyan}│${c.reset}
|
|
430
|
+
${c.bold}${c.cyan}│${c.reset} ${c.dim}Unified AI Agent CLI${c.reset} ${c.cyan}│${c.reset}
|
|
431
|
+
${c.bold}${c.cyan}╰─────────────────────────────────────────╯${c.reset}
|
|
432
|
+
`);
|
|
433
|
+
|
|
434
|
+
let providers = detectProviders();
|
|
435
|
+
printProviderStatus(providers);
|
|
436
|
+
|
|
437
|
+
const missing = Object.entries(providers)
|
|
438
|
+
.filter(([_, installed]) => !installed)
|
|
439
|
+
.map(([name]) => name);
|
|
440
|
+
|
|
441
|
+
let available = Object.entries(providers)
|
|
442
|
+
.filter(([_, installed]) => installed)
|
|
443
|
+
.map(([name]) => name);
|
|
444
|
+
|
|
445
|
+
// Offer to install missing providers
|
|
446
|
+
if (missing.length > 0) {
|
|
447
|
+
console.log(`\n${c.bold}Would you like to install any providers?${c.reset}\n`);
|
|
448
|
+
|
|
449
|
+
for (const provider of missing) {
|
|
450
|
+
const info = PROVIDERS[provider];
|
|
451
|
+
const answer = await prompt(` Install ${c.cyan}${provider}${c.reset} (${info.description})? [y/N]: `);
|
|
452
|
+
|
|
453
|
+
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
454
|
+
const success = await installProvider(provider);
|
|
455
|
+
if (success) {
|
|
456
|
+
providers[provider] = true;
|
|
457
|
+
available.push(provider);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Re-detect after installations
|
|
463
|
+
providers = detectProviders();
|
|
464
|
+
available = Object.entries(providers)
|
|
465
|
+
.filter(([_, installed]) => installed)
|
|
466
|
+
.map(([name]) => name);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// No providers available
|
|
470
|
+
if (available.length === 0) {
|
|
471
|
+
console.log(`\n${c.yellow}⚠${c.reset} No AI providers installed.\n`);
|
|
472
|
+
console.log(`${c.dim}Run ${c.reset}agx init${c.dim} again to install providers.${c.reset}\n`);
|
|
473
|
+
process.exit(0);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
console.log(`\n${c.green}✓${c.reset} Available providers: ${c.bold}${available.join(', ')}${c.reset}`);
|
|
477
|
+
|
|
478
|
+
// Ask for default provider
|
|
479
|
+
let defaultProvider = available[0];
|
|
480
|
+
|
|
481
|
+
if (available.length > 1) {
|
|
482
|
+
console.log(`\n${c.bold}Choose your default provider:${c.reset}`);
|
|
483
|
+
available.forEach((p, i) => {
|
|
484
|
+
console.log(` ${c.cyan}${i + 1}${c.reset}) ${p}`);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
const choice = await prompt(`\nEnter number [${c.dim}1${c.reset}]: `);
|
|
488
|
+
const idx = parseInt(choice) - 1;
|
|
489
|
+
if (idx >= 0 && idx < available.length) {
|
|
490
|
+
defaultProvider = available[idx];
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Save config
|
|
495
|
+
const config = {
|
|
496
|
+
version: 1,
|
|
497
|
+
defaultProvider,
|
|
498
|
+
initialized: true,
|
|
499
|
+
providers: providers
|
|
500
|
+
};
|
|
501
|
+
saveConfig(config);
|
|
502
|
+
|
|
503
|
+
console.log(`\n${c.green}✓${c.reset} Configuration saved to ${c.dim}~/.agx/config.json${c.reset}`);
|
|
504
|
+
console.log(`${c.green}✓${c.reset} Default provider: ${c.bold}${c.cyan}${defaultProvider}${c.reset}`);
|
|
505
|
+
|
|
506
|
+
// Show quick start
|
|
507
|
+
console.log(`
|
|
508
|
+
${c.bold}Quick Start:${c.reset}
|
|
509
|
+
|
|
510
|
+
${c.dim}# Use default provider (${defaultProvider})${c.reset}
|
|
511
|
+
${c.cyan}agx --prompt "hello world"${c.reset}
|
|
512
|
+
|
|
513
|
+
${c.dim}# Or specify a provider${c.reset}
|
|
514
|
+
${c.cyan}agx ${defaultProvider} --prompt "explain this code"${c.reset}
|
|
515
|
+
|
|
516
|
+
${c.dim}# Interactive mode${c.reset}
|
|
517
|
+
${c.cyan}agx ${defaultProvider} -i --prompt "let's chat"${c.reset}
|
|
518
|
+
|
|
519
|
+
${c.dim}# Show help${c.reset}
|
|
520
|
+
${c.cyan}agx --help${c.reset}
|
|
521
|
+
|
|
522
|
+
${c.dim}Run ${c.reset}agx init${c.dim} anytime to reconfigure.${c.reset}
|
|
523
|
+
`);
|
|
524
|
+
|
|
525
|
+
process.exit(0);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Show current config status
|
|
529
|
+
async function showConfigStatus() {
|
|
530
|
+
const config = loadConfig();
|
|
531
|
+
const providers = detectProviders();
|
|
532
|
+
|
|
533
|
+
console.log(`\n${c.bold}agx Configuration${c.reset}\n`);
|
|
534
|
+
|
|
535
|
+
if (config) {
|
|
536
|
+
console.log(` Config file: ${c.dim}~/.agx/config.json${c.reset}`);
|
|
537
|
+
console.log(` Default provider: ${c.cyan}${config.defaultProvider}${c.reset}`);
|
|
538
|
+
} else {
|
|
539
|
+
console.log(` ${c.yellow}Not configured${c.reset} - run ${c.cyan}agx init${c.reset}`);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
printProviderStatus(providers);
|
|
543
|
+
console.log('');
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Run config menu
|
|
547
|
+
async function runConfigMenu() {
|
|
548
|
+
const config = loadConfig();
|
|
549
|
+
const providers = detectProviders();
|
|
550
|
+
|
|
551
|
+
console.log(`\n${c.bold}agx Configuration${c.reset}\n`);
|
|
552
|
+
|
|
553
|
+
console.log(`${c.bold}What would you like to do?${c.reset}\n`);
|
|
554
|
+
console.log(` ${c.cyan}1${c.reset}) Install a new provider`);
|
|
555
|
+
console.log(` ${c.cyan}2${c.reset}) Login to a provider`);
|
|
556
|
+
console.log(` ${c.cyan}3${c.reset}) Change default provider`);
|
|
557
|
+
console.log(` ${c.cyan}4${c.reset}) Show status`);
|
|
558
|
+
console.log(` ${c.cyan}5${c.reset}) Run full setup wizard`);
|
|
559
|
+
console.log(` ${c.cyan}q${c.reset}) Quit`);
|
|
560
|
+
|
|
561
|
+
const choice = await prompt(`\nChoice: `);
|
|
562
|
+
|
|
563
|
+
switch (choice) {
|
|
564
|
+
case '1': {
|
|
565
|
+
// Install a provider
|
|
566
|
+
const missing = ['claude', 'gemini', 'ollama'].filter(p => !providers[p]);
|
|
567
|
+
if (missing.length === 0) {
|
|
568
|
+
console.log(`\n${c.green}✓${c.reset} All providers are already installed!`);
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
console.log(`\n${c.bold}Available to install:${c.reset}\n`);
|
|
572
|
+
missing.forEach((p, i) => {
|
|
573
|
+
console.log(` ${c.cyan}${i + 1}${c.reset}) ${p} - ${PROVIDERS[p].description}`);
|
|
574
|
+
});
|
|
575
|
+
const pChoice = await prompt(`\nChoice: `);
|
|
576
|
+
const idx = parseInt(pChoice) - 1;
|
|
577
|
+
if (idx >= 0 && idx < missing.length) {
|
|
578
|
+
await installProvider(missing[idx]);
|
|
579
|
+
}
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
case '2': {
|
|
583
|
+
// Login to a provider
|
|
584
|
+
const installed = Object.keys(providers).filter(p => providers[p]);
|
|
585
|
+
if (installed.length === 0) {
|
|
586
|
+
console.log(`\n${c.yellow}No providers installed.${c.reset} Install one first.`);
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
console.log(`\n${c.bold}Login to:${c.reset}\n`);
|
|
590
|
+
installed.forEach((p, i) => {
|
|
591
|
+
console.log(` ${c.cyan}${i + 1}${c.reset}) ${p}`);
|
|
592
|
+
});
|
|
593
|
+
const pChoice = await prompt(`\nChoice: `);
|
|
594
|
+
const idx = parseInt(pChoice) - 1;
|
|
595
|
+
if (idx >= 0 && idx < installed.length) {
|
|
596
|
+
await loginProvider(installed[idx]);
|
|
597
|
+
}
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
case '3': {
|
|
601
|
+
// Change default provider
|
|
602
|
+
const installed = Object.keys(providers).filter(p => providers[p]);
|
|
603
|
+
if (installed.length === 0) {
|
|
604
|
+
console.log(`\n${c.yellow}No providers installed.${c.reset}`);
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
console.log(`\n${c.bold}Set default provider:${c.reset}\n`);
|
|
608
|
+
installed.forEach((p, i) => {
|
|
609
|
+
const current = config?.defaultProvider === p ? ` ${c.dim}(current)${c.reset}` : '';
|
|
610
|
+
console.log(` ${c.cyan}${i + 1}${c.reset}) ${p}${current}`);
|
|
611
|
+
});
|
|
612
|
+
const pChoice = await prompt(`\nChoice: `);
|
|
613
|
+
const idx = parseInt(pChoice) - 1;
|
|
614
|
+
if (idx >= 0 && idx < installed.length) {
|
|
615
|
+
const newConfig = { ...config, defaultProvider: installed[idx] };
|
|
616
|
+
saveConfig(newConfig);
|
|
617
|
+
console.log(`\n${c.green}✓${c.reset} Default provider set to ${c.cyan}${installed[idx]}${c.reset}`);
|
|
618
|
+
}
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
case '4':
|
|
622
|
+
await showConfigStatus();
|
|
623
|
+
break;
|
|
624
|
+
case '5':
|
|
625
|
+
await runOnboarding();
|
|
626
|
+
break;
|
|
627
|
+
case 'q':
|
|
628
|
+
case 'Q':
|
|
629
|
+
break;
|
|
630
|
+
default:
|
|
631
|
+
console.log(`${c.yellow}Invalid choice${c.reset}`);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
console.log('');
|
|
635
|
+
process.exit(0);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Check for commands or first run
|
|
639
|
+
async function checkOnboarding() {
|
|
640
|
+
const args = process.argv.slice(2);
|
|
641
|
+
const cmd = args[0];
|
|
642
|
+
|
|
643
|
+
// Init/setup command
|
|
644
|
+
if (cmd === 'init' || cmd === 'setup') {
|
|
645
|
+
await runOnboarding();
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Config menu
|
|
650
|
+
if (cmd === 'config') {
|
|
651
|
+
await runConfigMenu();
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Status command
|
|
656
|
+
if (cmd === 'status') {
|
|
657
|
+
await showConfigStatus();
|
|
658
|
+
process.exit(0);
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Skill command
|
|
663
|
+
if (cmd === 'skill') {
|
|
664
|
+
await handleSkillCommand(args);
|
|
665
|
+
process.exit(0);
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Login command
|
|
670
|
+
if (cmd === 'login') {
|
|
671
|
+
const provider = args[1];
|
|
672
|
+
if (!provider) {
|
|
673
|
+
console.log(`${c.yellow}Usage:${c.reset} agx login <provider>`);
|
|
674
|
+
console.log(`${c.dim}Providers: claude, gemini, ollama${c.reset}`);
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
if (!['claude', 'gemini', 'ollama'].includes(provider)) {
|
|
678
|
+
console.log(`${c.red}Unknown provider:${c.reset} ${provider}`);
|
|
679
|
+
process.exit(1);
|
|
680
|
+
}
|
|
681
|
+
if (!commandExists(provider)) {
|
|
682
|
+
console.log(`${c.yellow}${provider} is not installed.${c.reset}`);
|
|
683
|
+
const answer = await prompt(`Install it now? [Y/n]: `);
|
|
684
|
+
if (answer.toLowerCase() !== 'n') {
|
|
685
|
+
await installProvider(provider);
|
|
686
|
+
} else {
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
await loginProvider(provider);
|
|
691
|
+
process.exit(0);
|
|
692
|
+
return true;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Add/install command
|
|
696
|
+
if (cmd === 'add' || cmd === 'install') {
|
|
697
|
+
const provider = args[1];
|
|
698
|
+
if (!provider) {
|
|
699
|
+
console.log(`${c.yellow}Usage:${c.reset} agx add <provider>`);
|
|
700
|
+
console.log(`${c.dim}Providers: claude, gemini, ollama${c.reset}`);
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
if (!['claude', 'gemini', 'ollama'].includes(provider)) {
|
|
704
|
+
console.log(`${c.red}Unknown provider:${c.reset} ${provider}`);
|
|
705
|
+
process.exit(1);
|
|
706
|
+
}
|
|
707
|
+
if (commandExists(provider)) {
|
|
708
|
+
console.log(`${c.green}✓${c.reset} ${provider} is already installed!`);
|
|
709
|
+
const answer = await prompt(`Run login/setup? [Y/n]: `);
|
|
710
|
+
if (answer.toLowerCase() !== 'n') {
|
|
711
|
+
await loginProvider(provider);
|
|
712
|
+
}
|
|
713
|
+
} else {
|
|
714
|
+
const success = await installProvider(provider);
|
|
715
|
+
if (success) {
|
|
716
|
+
const answer = await prompt(`\nRun login/setup? [Y/n]: `);
|
|
717
|
+
if (answer.toLowerCase() !== 'n') {
|
|
718
|
+
await loginProvider(provider);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
process.exit(0);
|
|
723
|
+
return true;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// First run detection
|
|
727
|
+
const config = loadConfig();
|
|
728
|
+
if (!config && !args.includes('--help') && !args.includes('-h')) {
|
|
729
|
+
console.log(`${c.cyan}First time using agx? Let's get you set up!${c.reset}\n`);
|
|
730
|
+
await runOnboarding();
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Main execution
|
|
738
|
+
(async () => {
|
|
739
|
+
if (await checkOnboarding()) return;
|
|
4
740
|
|
|
5
741
|
const args = process.argv.slice(2);
|
|
6
742
|
let provider = args[0];
|
|
743
|
+
const config = loadConfig();
|
|
7
744
|
|
|
8
745
|
// Normalize provider aliases
|
|
9
746
|
const PROVIDER_ALIASES = {
|
|
@@ -20,18 +757,21 @@ const PROVIDER_ALIASES = {
|
|
|
20
757
|
|
|
21
758
|
const VALID_PROVIDERS = ['gemini', 'claude', 'ollama'];
|
|
22
759
|
|
|
23
|
-
// Handle help
|
|
24
|
-
if (args.includes('--help') || args.includes('-h')
|
|
760
|
+
// Handle help
|
|
761
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
762
|
+
const defaultNote = config?.defaultProvider
|
|
763
|
+
? `\n Default provider: ${config.defaultProvider} (from ~/.agx/config.json)`
|
|
764
|
+
: '';
|
|
25
765
|
console.log(`agx - Unified AI Agent CLI
|
|
26
766
|
|
|
27
767
|
SYNTAX:
|
|
28
|
-
agx
|
|
768
|
+
agx [options] --prompt "<prompt>" Use default provider
|
|
29
769
|
agx <provider> [options] --prompt "<prompt>"
|
|
30
770
|
|
|
31
771
|
PROVIDERS:
|
|
32
772
|
gemini, gem, g Google Gemini
|
|
33
773
|
claude, cl, c Anthropic Claude
|
|
34
|
-
ollama, ol, o Local Ollama (via Claude interface)
|
|
774
|
+
ollama, ol, o Local Ollama (via Claude interface)${defaultNote}
|
|
35
775
|
|
|
36
776
|
OPTIONS:
|
|
37
777
|
--prompt, -p <text> The prompt to send (recommended for clarity)
|
|
@@ -43,7 +783,17 @@ OPTIONS:
|
|
|
43
783
|
--debug, -d Enable debug output
|
|
44
784
|
--mcp <config> MCP config file (claude/ollama only)
|
|
45
785
|
|
|
786
|
+
COMMANDS:
|
|
787
|
+
init, setup Run setup wizard
|
|
788
|
+
config Configuration menu
|
|
789
|
+
add <provider> Install a provider (claude, gemini, ollama)
|
|
790
|
+
login <provider> Login/authenticate to a provider
|
|
791
|
+
status Show current configuration
|
|
792
|
+
skill View agx skill (LLM instructions)
|
|
793
|
+
skill install Install skill to Claude/Gemini
|
|
794
|
+
|
|
46
795
|
EXAMPLES:
|
|
796
|
+
agx --prompt "hello" Use default provider
|
|
47
797
|
agx claude --prompt "explain this code"
|
|
48
798
|
agx gemini -m gemini-2.0-flash --prompt "hello"
|
|
49
799
|
agx ollama --model qwen3:8b --prompt "write a poem"
|
|
@@ -57,15 +807,40 @@ NOTE: For predictable LLM usage, always use --prompt or -p flag.`);
|
|
|
57
807
|
process.exit(0);
|
|
58
808
|
}
|
|
59
809
|
|
|
810
|
+
// Detect if first arg is a provider or an option
|
|
811
|
+
const isProviderArg = provider && PROVIDER_ALIASES[provider.toLowerCase()];
|
|
812
|
+
|
|
813
|
+
// If no provider specified, use default from config
|
|
814
|
+
if (!provider || (!isProviderArg && provider.startsWith('-'))) {
|
|
815
|
+
if (config?.defaultProvider) {
|
|
816
|
+
// Shift: treat current args as options, use default provider
|
|
817
|
+
if (provider && provider.startsWith('-')) {
|
|
818
|
+
// First arg is an option, not a provider
|
|
819
|
+
provider = config.defaultProvider;
|
|
820
|
+
} else if (!provider) {
|
|
821
|
+
provider = config.defaultProvider;
|
|
822
|
+
}
|
|
823
|
+
} else {
|
|
824
|
+
console.log(`${c.yellow}No provider specified and no default configured.${c.reset}`);
|
|
825
|
+
console.log(`\nRun ${c.cyan}agx init${c.reset} to set up, or specify a provider:\n`);
|
|
826
|
+
console.log(` ${c.dim}agx claude --prompt "hello"${c.reset}`);
|
|
827
|
+
console.log(` ${c.dim}agx gemini --prompt "hello"${c.reset}`);
|
|
828
|
+
console.log(` ${c.dim}agx ollama --prompt "hello"${c.reset}\n`);
|
|
829
|
+
process.exit(1);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
60
833
|
// Resolve provider
|
|
61
|
-
|
|
62
|
-
if (!
|
|
63
|
-
console.error(
|
|
834
|
+
const resolvedProvider = PROVIDER_ALIASES[provider.toLowerCase()];
|
|
835
|
+
if (!resolvedProvider) {
|
|
836
|
+
console.error(`${c.red}Error:${c.reset} Unknown provider "${provider}"`);
|
|
64
837
|
console.error(`Valid providers: ${VALID_PROVIDERS.join(', ')}`);
|
|
65
838
|
process.exit(1);
|
|
66
839
|
}
|
|
840
|
+
provider = resolvedProvider;
|
|
67
841
|
|
|
68
|
-
|
|
842
|
+
// Determine remaining args - if first arg wasn't a provider, include it
|
|
843
|
+
const remainingArgs = isProviderArg ? args.slice(1) : args;
|
|
69
844
|
const translatedArgs = [];
|
|
70
845
|
const rawArgs = [];
|
|
71
846
|
let env = { ...process.env };
|
|
@@ -223,9 +998,17 @@ child.on('exit', (code) => {
|
|
|
223
998
|
|
|
224
999
|
child.on('error', (err) => {
|
|
225
1000
|
if (err.code === 'ENOENT') {
|
|
226
|
-
console.error(
|
|
1001
|
+
console.error(`${c.red}Error:${c.reset} "${command}" command not found.`);
|
|
1002
|
+
console.error(`\n${c.dim}Install it first:${c.reset}`);
|
|
1003
|
+
if (command === 'claude') {
|
|
1004
|
+
console.error(` npm install -g @anthropic-ai/claude-code`);
|
|
1005
|
+
} else if (command === 'gemini') {
|
|
1006
|
+
console.error(` npm install -g @anthropic-ai/gemini-cli`);
|
|
1007
|
+
}
|
|
227
1008
|
} else {
|
|
228
|
-
console.error(
|
|
1009
|
+
console.error(`${c.red}Failed to start ${command}:${c.reset}`, err.message);
|
|
229
1010
|
}
|
|
230
1011
|
process.exit(1);
|
|
231
1012
|
});
|
|
1013
|
+
|
|
1014
|
+
})();
|