@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.
@@ -0,0 +1,10 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(node --check:*)",
5
+ "Bash(node index.js:*)",
6
+ "Bash(python:*)",
7
+ "Bash(npm publish:*)"
8
+ ]
9
+ }
10
+ }
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
- From the `agx` directory:
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
- Now you can use `agx` globally.
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 <provider> [--model <name>] [--yolo] [--print] --prompt "<prompt>"
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/version before provider check
24
- if (args.includes('--help') || args.includes('-h') || !provider) {
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 <provider> [options] "<prompt>"
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
- provider = PROVIDER_ALIASES[provider.toLowerCase()];
62
- if (!provider) {
63
- console.error(`Error: Unknown provider "${args[0]}"`);
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
- const remainingArgs = args.slice(1);
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(`Error: "${command}" command not found.`);
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(`Failed to start ${command}:`, err);
1009
+ console.error(`${c.red}Failed to start ${command}:${c.reset}`, err.message);
229
1010
  }
230
1011
  process.exit(1);
231
1012
  });
1013
+
1014
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mndrk/agx",
3
- "version": "1.0.2",
3
+ "version": "1.3.0",
4
4
  "description": "Unified AI Agent Wrapper for Gemini, Claude, and Ollama",
5
5
  "main": "index.js",
6
6
  "bin": {