agentpick 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +130 -0
- package/bin/cli.js +57 -0
- package/lib/detect.js +35 -0
- package/lib/menuActions.js +25 -0
- package/lib/modal.js +102 -0
- package/lib/picker.js +35 -0
- package/lib/probe.js +24 -0
- package/lib/registry.js +30 -0
- package/package.json +30 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 herliansyah
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# 🎯 agentpick
|
|
2
|
+
|
|
3
|
+
Detect installed AI coding-agent CLIs on your machine and pick one to launch.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx agentpick
|
|
9
|
+
npx agentpick "fix the failing test" # args are passed through to the chosen agent
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
- 0 agents detected: prints supported list and custom config instructions
|
|
13
|
+
- 1 agent detected: launches it immediately without menu prompt
|
|
14
|
+
- 2+ agents detected: interactive terminal picker
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
Add or override agents in `~/.config/agentpick/config.json`:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"agents": [
|
|
23
|
+
{ "id": "my-agent", "label": "My Agent", "bin": "my-agent", "args": [] }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
<!-- cli-end -->
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 🌟 Features
|
|
33
|
+
|
|
34
|
+
- ⚡ **Zero Setup**: Run instantly via `npx agentpick` without pre-installing.
|
|
35
|
+
- 🔍 **Auto-Detection**: Probes your system's `PATH` for installed AI agent CLI binaries.
|
|
36
|
+
- 🚀 **Smart Launching**:
|
|
37
|
+
- Automatically launches the agent if only one is installed.
|
|
38
|
+
- Opens a clean interactive terminal menu if multiple agents are detected.
|
|
39
|
+
- Helpful suggestions if no agents are found.
|
|
40
|
+
- 💬 **Argument Passthrough**: Any trailing arguments or prompts are passed directly to the chosen agent (e.g. `npx agentpick "explain this function"`).
|
|
41
|
+
- 🛠️ **Extensible Registry**: Add custom binaries, proprietary agents, or default CLI arguments via a simple JSON config file.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 📦 Supported Agents (Bundled Registry)
|
|
46
|
+
|
|
47
|
+
`agentpick` checks for these CLI tools out of the box:
|
|
48
|
+
|
|
49
|
+
| Agent | Binary (`bin`) |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| **Claude Code** | `claude` |
|
|
52
|
+
| **Codex CLI** | `codex` |
|
|
53
|
+
| **opencode** | `opencode` |
|
|
54
|
+
| **omp** | `omp` |
|
|
55
|
+
| **pi** | `pi` |
|
|
56
|
+
| **Prime Agent** | `prime-agent` |
|
|
57
|
+
| **Aider** | `aider` |
|
|
58
|
+
| **Gemini CLI** | `gemini` |
|
|
59
|
+
| **Continue** | `cn` |
|
|
60
|
+
| **Goose** | `goose` |
|
|
61
|
+
| **Cursor Agent** | `cursor-agent` |
|
|
62
|
+
| **GitHub Copilot CLI** | `copilot` |
|
|
63
|
+
| **Amazon Q Developer** | `q` |
|
|
64
|
+
| **Sourcegraph Cody** | `cody` |
|
|
65
|
+
| **Hermes** | `hermes` |
|
|
66
|
+
| **Antigravity** | `agy` |
|
|
67
|
+
| **Poolside** | `pool` |
|
|
68
|
+
| **Forge** | `forge` |
|
|
69
|
+
| **CodeWhale** | `codewhale` |
|
|
70
|
+
| **Command Code** | `cmdc` |
|
|
71
|
+
| **Freebuff** | `freebuff` |
|
|
72
|
+
| **Reasonix** | `reasonix` |
|
|
73
|
+
| **Kilo** | `kilo` |
|
|
74
|
+
| **Crush** | `crush` |
|
|
75
|
+
| **Droid** | `droid` |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 🇮🇩 Bahasa Indonesia
|
|
80
|
+
|
|
81
|
+
### Tentang agentpick
|
|
82
|
+
|
|
83
|
+
**agentpick** adalah CLI launcher ringan untuk mendeteksi *AI coding-agent* yang sudah terinstall di komputer Anda dan memilih salah satunya untuk dijalankan.
|
|
84
|
+
|
|
85
|
+
Jika Anda menggunakan beberapa coding assistant sekaligus (misalnya Claude Code untuk arsitektur, Aider untuk refactor cepat, atau Cursor Agent di terminal), `agentpick` memudahkan Anda berganti agent tanpa harus mengingat nama binary masing-masing.
|
|
86
|
+
|
|
87
|
+
### Cara Penggunaan
|
|
88
|
+
|
|
89
|
+
Cukup jalankan via `npx`:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
# Buka menu pemilihan agent interaktif
|
|
93
|
+
npx agentpick
|
|
94
|
+
|
|
95
|
+
# Jalankan agent terpilih dengan prompt langsung
|
|
96
|
+
npx agentpick "perbaiki unit test yang gagal"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Logika Deteksi
|
|
100
|
+
|
|
101
|
+
- **0 Agent Terdeteksi**: Menampilkan daftar agent yang didukung serta panduan konfigurasi agent kustom.
|
|
102
|
+
- **1 Agent Terdeteksi**: Langsung meluncurkan agent tersebut tanpa menampilkan menu.
|
|
103
|
+
- **2+ Agent Terdeteksi**: Menampilkan menu pemilihan (*interactive picker*) terminal.
|
|
104
|
+
|
|
105
|
+
### Menambahkan Agent Kustom
|
|
106
|
+
|
|
107
|
+
Jika agent yang Anda pakai belum ada di daftar bawaan, Anda dapat menambahkannya di `~/.config/agentpick/config.json`:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"agents": [
|
|
112
|
+
{ "id": "nama-agent", "label": "Nama Agent", "bin": "nama-binary", "args": [] }
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Development & Test
|
|
120
|
+
|
|
121
|
+
Run the test suite:
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
node test/detect.test.js
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
130
|
+
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { detectAgents, CONFIG_PATH } from '../lib/detect.js';
|
|
4
|
+
import { REGISTRY } from '../lib/registry.js';
|
|
5
|
+
import { MENU_ACTIONS } from '../lib/menuActions.js';
|
|
6
|
+
import { selectWithSeparators, isCancel } from '../lib/picker.js';
|
|
7
|
+
|
|
8
|
+
const passthroughArgs = process.argv.slice(2);
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
const detected = detectAgents();
|
|
12
|
+
|
|
13
|
+
if (detected.length === 0) {
|
|
14
|
+
console.error('No AI coding-agent CLI detected on PATH.\n');
|
|
15
|
+
console.error('Supported agents:');
|
|
16
|
+
for (const agent of REGISTRY) console.error(` - ${agent.label} (${agent.bin})`);
|
|
17
|
+
console.error(`\nInstall one of the above, or add a custom agent to ${CONFIG_PATH}`);
|
|
18
|
+
process.exitCode = 1;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const chosen = detected.length === 1 ? detected[0] : await pick(detected);
|
|
23
|
+
if (!chosen) return; // user cancelled
|
|
24
|
+
|
|
25
|
+
run(chosen);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function pick(detected, initialValue) {
|
|
29
|
+
console.clear();
|
|
30
|
+
const id = await selectWithSeparators({
|
|
31
|
+
message: '🤖 Pick an agent to launch',
|
|
32
|
+
initialValue,
|
|
33
|
+
options: [
|
|
34
|
+
...detected.map((agent) => ({ value: agent.id, label: agent.label })),
|
|
35
|
+
{ value: '__sep__', label: '──────────', separator: true, disabled: true },
|
|
36
|
+
...MENU_ACTIONS.map((action) => ({ value: action.id, label: action.label })),
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
if (isCancel(id)) return null;
|
|
40
|
+
|
|
41
|
+
const action = MENU_ACTIONS.find((a) => a.id === id);
|
|
42
|
+
if (action) {
|
|
43
|
+
const result = await action.run();
|
|
44
|
+
if (result.type === 'again') return pick(detected, id); // stay on this action after going back
|
|
45
|
+
return null; // exit
|
|
46
|
+
}
|
|
47
|
+
return detected.find((agent) => agent.id === id);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function run(agent) {
|
|
51
|
+
const child = spawn(agent.path, [...agent.args, ...passthroughArgs], { stdio: 'inherit' });
|
|
52
|
+
child.on('exit', (code) => {
|
|
53
|
+
process.exitCode = code ?? 1;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
main();
|
package/lib/detect.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { REGISTRY } from './registry.js';
|
|
5
|
+
import { probe } from './probe.js';
|
|
6
|
+
|
|
7
|
+
const CONFIG_PATH = join(homedir(), '.config', 'agentpick', 'config.json');
|
|
8
|
+
|
|
9
|
+
// User Registry Override: entries from ~/.config/agentpick/config.json, merged over the bundled Registry by id.
|
|
10
|
+
function loadUserAgents() {
|
|
11
|
+
try {
|
|
12
|
+
const raw = readFileSync(CONFIG_PATH, 'utf8');
|
|
13
|
+
const parsed = JSON.parse(raw);
|
|
14
|
+
return Array.isArray(parsed.agents) ? parsed.agents : [];
|
|
15
|
+
} catch {
|
|
16
|
+
return []; // no config, bad JSON, or missing agents field: fall back to bundled registry only
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function mergeRegistry() {
|
|
21
|
+
const byId = new Map(REGISTRY.map((a) => [a.id, a]));
|
|
22
|
+
for (const override of loadUserAgents()) {
|
|
23
|
+
if (override && override.id) byId.set(override.id, { args: [], ...override });
|
|
24
|
+
}
|
|
25
|
+
return [...byId.values()];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Detect: probe every Registry entry (bundled + user overrides), return the ones found on PATH.
|
|
29
|
+
export function detectAgents() {
|
|
30
|
+
return mergeRegistry()
|
|
31
|
+
.map((agent) => ({ ...agent, path: probe(agent.bin) }))
|
|
32
|
+
.filter((agent) => agent.path);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { CONFIG_PATH };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { showReadmeModal } from './modal.js';
|
|
3
|
+
|
|
4
|
+
// Menu Action: a non-agent entry in the picker. run() executes the action and returns
|
|
5
|
+
// a result telling pick()'s loop what to do next:
|
|
6
|
+
// { type: 'again' } -> redraw the menu, cursor back on this action
|
|
7
|
+
// { type: 'exit' } -> stop picking, no agent chosen
|
|
8
|
+
export const MENU_ACTIONS = [
|
|
9
|
+
{
|
|
10
|
+
id: '__help__',
|
|
11
|
+
label: 'Help',
|
|
12
|
+
async run() {
|
|
13
|
+
await showReadmeModal();
|
|
14
|
+
return { type: 'again' };
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: '__exit__',
|
|
19
|
+
label: 'Exit',
|
|
20
|
+
async run() {
|
|
21
|
+
console.log(pc.dim('Exited.'));
|
|
22
|
+
return { type: 'exit' };
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
];
|
package/lib/modal.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
|
|
4
|
+
// Word-wrap a plain string to `width` columns, splitting on spaces.
|
|
5
|
+
function wrap(text, width) {
|
|
6
|
+
const words = text.split(' ');
|
|
7
|
+
const lines = [];
|
|
8
|
+
let line = '';
|
|
9
|
+
for (const word of words) {
|
|
10
|
+
if (line && (line + ' ' + word).length > width) {
|
|
11
|
+
lines.push(line);
|
|
12
|
+
line = word;
|
|
13
|
+
} else {
|
|
14
|
+
line = line ? line + ' ' + word : word;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (line) lines.push(line);
|
|
18
|
+
return lines;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Minimal Markdown -> styled terminal lines. Handles #/##/### headings,
|
|
22
|
+
// fenced code blocks, and bullet lists — enough for a README, not a full parser.
|
|
23
|
+
// ponytail: covers only the Markdown subset agentpick's own README uses; upgrade to a real
|
|
24
|
+
// Markdown-to-ANSI renderer (e.g. marked-terminal) if richer docs need rendering later.
|
|
25
|
+
function renderMarkdown(md, width) {
|
|
26
|
+
const out = [];
|
|
27
|
+
let inCode = false;
|
|
28
|
+
for (const raw of md.split('\n')) {
|
|
29
|
+
if (raw.startsWith('```')) {
|
|
30
|
+
inCode = !inCode;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (inCode) {
|
|
34
|
+
out.push(pc.white(raw.slice(0, width)));
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const heading = raw.match(/^(#{1,3})\s+(.*)/);
|
|
38
|
+
if (heading) {
|
|
39
|
+
const text = heading[1].length === 1 ? pc.bold(pc.cyan(heading[2])) : pc.bold(pc.magenta(heading[2]));
|
|
40
|
+
out.push('', text);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (raw.trim().startsWith('- ')) {
|
|
44
|
+
for (const line of wrap(raw.trim().slice(2), width - 2)) out.push(pc.green('•') + ' ' + line);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (raw.trim() === '') {
|
|
48
|
+
out.push('');
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
for (const line of wrap(raw, width)) out.push(line);
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function waitForKey(message) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
process.stdout.write(pc.dim(message));
|
|
59
|
+
const { stdin } = process;
|
|
60
|
+
const wasRaw = stdin.isRaw;
|
|
61
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
62
|
+
stdin.resume();
|
|
63
|
+
stdin.once('data', () => {
|
|
64
|
+
if (stdin.isTTY) stdin.setRawMode(wasRaw);
|
|
65
|
+
stdin.pause();
|
|
66
|
+
resolve();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Show README.md as a full-screen modal: clear screen, draw a bordered box, wait for a
|
|
72
|
+
// keypress, then clear again so the caller can redraw whatever comes next.
|
|
73
|
+
export async function showReadmeModal() {
|
|
74
|
+
const width = Math.min((process.stdout.columns || 80) - 4, 100);
|
|
75
|
+
let text = readFileSync(new URL('../README.md', import.meta.url), 'utf8').trim();
|
|
76
|
+
const cut = text.indexOf('<!-- cli-end -->');
|
|
77
|
+
if (cut !== -1) text = text.slice(0, cut).trim();
|
|
78
|
+
const lines = renderMarkdown(text, width);
|
|
79
|
+
|
|
80
|
+
// Body lines are `│ ` + content(width) + ` │`, so the full box width is `width + 4`.
|
|
81
|
+
// Build the title bar to that same total width so its corners line up with the sides.
|
|
82
|
+
const boxWidth = width + 4;
|
|
83
|
+
const title = ' agentpick help ';
|
|
84
|
+
const dashes = '─'.repeat(Math.max(0, boxWidth - 2 - title.length - 1));
|
|
85
|
+
const top = pc.cyan('╭─') + pc.bold(title) + pc.cyan(dashes + '╮');
|
|
86
|
+
const bottom = pc.cyan('╰' + '─'.repeat(boxWidth - 2) + '╯');
|
|
87
|
+
|
|
88
|
+
console.clear();
|
|
89
|
+
console.log(top);
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
const pad = ' '.repeat(Math.max(0, width - stripLen(line)));
|
|
92
|
+
console.log(pc.cyan('│ ') + line + pad + pc.cyan(' │'));
|
|
93
|
+
}
|
|
94
|
+
console.log(bottom);
|
|
95
|
+
await waitForKey('\n Press any key to go back…');
|
|
96
|
+
console.clear();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Length of a string ignoring ANSI escape codes (picocolors output), for padding.
|
|
100
|
+
function stripLen(s) {
|
|
101
|
+
return s.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
102
|
+
}
|
package/lib/picker.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { SelectPrompt, isCancel } from '@clack/core';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
|
|
4
|
+
const RADIO_ACTIVE = '●';
|
|
5
|
+
const RADIO_INACTIVE = '○';
|
|
6
|
+
const BAR = '│';
|
|
7
|
+
|
|
8
|
+
// Thin wrapper around @clack/core's SelectPrompt with our own row renderer, so a
|
|
9
|
+
// `separator: true` option renders as plain dim text with no radio bullet — unlike
|
|
10
|
+
// @clack/prompts' built-in select(), which always draws a bullet even when disabled.
|
|
11
|
+
export async function selectWithSeparators({ message, options, initialValue }) {
|
|
12
|
+
const prompt = new SelectPrompt({
|
|
13
|
+
options,
|
|
14
|
+
initialValue,
|
|
15
|
+
render() {
|
|
16
|
+
const lines = [pc.cyan('┌ ') + pc.bold(message), pc.cyan(BAR)];
|
|
17
|
+
for (let i = 0; i < this.options.length; i++) {
|
|
18
|
+
const opt = this.options[i];
|
|
19
|
+
const label = opt.label ?? String(opt.value);
|
|
20
|
+
if (opt.separator) {
|
|
21
|
+
lines.push(pc.cyan(BAR) + ' ' + pc.dim(label));
|
|
22
|
+
} else if (i === this.cursor) {
|
|
23
|
+
lines.push(pc.cyan(BAR) + ' ' + pc.green(RADIO_ACTIVE) + ' ' + label);
|
|
24
|
+
} else {
|
|
25
|
+
lines.push(pc.cyan(BAR) + ' ' + pc.dim(RADIO_INACTIVE) + ' ' + pc.dim(label));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
lines.push(pc.cyan('└'));
|
|
29
|
+
return lines.join('\n');
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
return prompt.prompt();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { isCancel };
|
package/lib/probe.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { accessSync, constants } from 'node:fs';
|
|
2
|
+
import { delimiter, join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const isWindows = process.platform === 'win32';
|
|
5
|
+
// ponytail: fixed common Windows executable extensions; upgrade to reading %PATHEXT% if a bin needs one not listed here.
|
|
6
|
+
const WIN_EXTS = ['.exe', '.cmd', '.bat', ''];
|
|
7
|
+
|
|
8
|
+
// Probe: check whether `bin` resolves to an executable file on PATH. Returns the resolved path or null.
|
|
9
|
+
export function probe(bin) {
|
|
10
|
+
const dirs = (process.env.PATH || '').split(delimiter).filter(Boolean);
|
|
11
|
+
const exts = isWindows ? WIN_EXTS : [''];
|
|
12
|
+
for (const dir of dirs) {
|
|
13
|
+
for (const ext of exts) {
|
|
14
|
+
const candidate = join(dir, bin + ext);
|
|
15
|
+
try {
|
|
16
|
+
accessSync(candidate, constants.X_OK);
|
|
17
|
+
return candidate;
|
|
18
|
+
} catch {
|
|
19
|
+
// not here, keep looking
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
package/lib/registry.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Bundled Registry: known AI coding-agent CLIs.
|
|
2
|
+
// id: unique key, label: display name, bin: binary to probe on PATH, args: default invoke args.
|
|
3
|
+
export const REGISTRY = [
|
|
4
|
+
{ id: 'claude', label: 'Claude Code', bin: 'claude', args: [] },
|
|
5
|
+
{ id: 'codex', label: 'Codex CLI', bin: 'codex', args: [] },
|
|
6
|
+
{ id: 'opencode', label: 'opencode', bin: 'opencode', args: [] },
|
|
7
|
+
{ id: 'omp', label: 'omp', bin: 'omp', args: [] },
|
|
8
|
+
{ id: 'pi', label: 'pi', bin: 'pi', args: [] },
|
|
9
|
+
{ id: 'prime-agent', label: 'Prime Agent', bin: 'prime-agent', args: [] },
|
|
10
|
+
{ id: 'aider', label: 'Aider', bin: 'aider', args: [] },
|
|
11
|
+
{ id: 'gemini', label: 'Gemini CLI', bin: 'gemini', args: [] },
|
|
12
|
+
{ id: 'continue', label: 'Continue', bin: 'cn', args: [] },
|
|
13
|
+
{ id: 'goose', label: 'Goose', bin: 'goose', args: [] },
|
|
14
|
+
{ id: 'cursor-agent', label: 'Cursor Agent', bin: 'cursor-agent', args: [] },
|
|
15
|
+
{ id: 'copilot', label: 'GitHub Copilot CLI', bin: 'copilot', args: [] },
|
|
16
|
+
{ id: 'q', label: 'Amazon Q Developer', bin: 'q', args: [] },
|
|
17
|
+
{ id: 'cody', label: 'Sourcegraph Cody', bin: 'cody', args: [] },
|
|
18
|
+
{ id: 'hermes', label: 'Hermes', bin: 'hermes', args: [] },
|
|
19
|
+
{ id: 'antigravity', label: 'Antigravity', bin: 'agy', args: [] },
|
|
20
|
+
{ id: 'poolside', label: 'Poolside', bin: 'pool', args: [] },
|
|
21
|
+
{ id: 'forge', label: 'Forge', bin: 'forge', args: [] },
|
|
22
|
+
{ id: 'codewhale', label: 'CodeWhale', bin: 'codewhale', args: [] },
|
|
23
|
+
{ id: 'command-code', label: 'Command Code', bin: 'cmdc', args: [] },
|
|
24
|
+
{ id: 'freebuff', label: 'Freebuff', bin: 'freebuff', args: [] },
|
|
25
|
+
{ id: 'reasonix', label: 'Reasonix', bin: 'reasonix', args: [] },
|
|
26
|
+
{ id: 'kilo', label: 'Kilo', bin: 'kilo', args: [] },
|
|
27
|
+
{ id: 'crush', label: 'Crush', bin: 'crush', args: [] },
|
|
28
|
+
{ id: 'droid', label: 'Droid', bin: 'droid', args: [] },
|
|
29
|
+
];
|
|
30
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentpick",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Detect installed AI coding-agent CLIs and pick one to launch",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agentpick": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"lib"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"author": "herliansyah <herliansyah@gmail.com>",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/herliansyah/agentpick.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/herliansyah/agentpick/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/herliansyah/agentpick#readme",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@clack/prompts": "^1.7.0",
|
|
28
|
+
"picocolors": "^1.1.1"
|
|
29
|
+
}
|
|
30
|
+
}
|