@dinoxx/dinox-cli 2.9.0 → 2.11.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/README.md +30 -32
- package/dist/auth/devicePair.d.ts +62 -0
- package/dist/auth/devicePair.js +377 -0
- package/dist/commands/auth/index.js +90 -8
- package/dist/commands/gateway/index.js +16 -10
- package/dist/commands/graph/index.js +2 -0
- package/dist/commands/schema/renderSkills.js +2 -2
- package/dist/commands/update.d.ts +16 -0
- package/dist/commands/update.js +61 -11
- package/dist/config/types.d.ts +4 -0
- package/dist/daemon/ensure.js +7 -0
- package/dist/daemon/route.js +2 -0
- package/dist/gateway/config.d.ts +1 -5
- package/dist/gateway/config.js +23 -2
- package/dist/gateway/detect.d.ts +26 -3
- package/dist/gateway/detect.js +126 -13
- package/dist/utils/errorCodes.d.ts +12 -0
- package/dist/utils/errorCodes.js +3 -0
- package/package.json +3 -1
package/dist/gateway/detect.js
CHANGED
|
@@ -17,13 +17,13 @@ export const KNOWN_ACP_AGENTS = [
|
|
|
17
17
|
id: 'claude',
|
|
18
18
|
name: 'Claude Code',
|
|
19
19
|
candidates: [['claude-agent-acp'], ['claude-code-acp']],
|
|
20
|
-
adapter: { baseBinary: 'claude',
|
|
20
|
+
adapter: { baseBinary: 'claude', packageName: '@agentclientprotocol/claude-agent-acp' },
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
id: 'codex',
|
|
24
24
|
name: 'Codex',
|
|
25
25
|
candidates: [['codex-acp']],
|
|
26
|
-
adapter: { baseBinary: 'codex',
|
|
26
|
+
adapter: { baseBinary: 'codex', packageName: '@agentclientprotocol/codex-acp' },
|
|
27
27
|
},
|
|
28
28
|
{ id: 'gemini', name: 'Gemini CLI', candidates: [['gemini', '--acp'], ['gemini', '--experimental-acp']] },
|
|
29
29
|
{ id: 'copilot', name: 'GitHub Copilot', candidates: [['copilot', '--acp']] },
|
|
@@ -31,8 +31,8 @@ export const KNOWN_ACP_AGENTS = [
|
|
|
31
31
|
{ id: 'opencode', name: 'OpenCode', candidates: [['opencode', 'acp']] },
|
|
32
32
|
{ id: 'pi', name: 'pi', candidates: [['pi-acp']] },
|
|
33
33
|
{ id: 'goose', name: 'Goose', candidates: [['goose', 'acp']] },
|
|
34
|
-
{ id: 'qwen', name: 'Qwen Code', candidates: [['qwen', '--acp'], ['qwen', '--experimental-acp']] },
|
|
35
|
-
{ id: 'kimi', name: 'Kimi CLI', candidates: [['kimi', 'acp'], ['kimi', '--acp']] },
|
|
34
|
+
{ id: 'qwen', name: 'Qwen Code', candidates: [['qwen', '--acp'], ['qwen', '--acp', '--experimental-skills'], ['qwen', '--experimental-acp']] },
|
|
35
|
+
{ id: 'kimi', name: 'Kimi Code CLI', candidates: [['kimi', 'acp'], ['kimi', '--acp']] },
|
|
36
36
|
// DeepSeek Harness: ACP is a dsh profile; the model key comes from DEEPSEEK_API_KEY.
|
|
37
37
|
{ id: 'deepseek', name: 'DeepSeek Harness', candidates: [['dsh', '--profile', 'acp']] },
|
|
38
38
|
{ id: 'qoder', name: 'Qoder CLI', candidates: [['qodercli', '--acp'], ['qoderclicn', '--acp']] },
|
|
@@ -40,7 +40,56 @@ export const KNOWN_ACP_AGENTS = [
|
|
|
40
40
|
id: 'zcode',
|
|
41
41
|
name: 'ZCode',
|
|
42
42
|
candidates: [['zcode-acp']],
|
|
43
|
-
adapter: { baseBinary: 'zcode',
|
|
43
|
+
adapter: { baseBinary: 'zcode', packageName: 'zcode-acp' },
|
|
44
|
+
},
|
|
45
|
+
// Launch lines follow Paseo's ACP catalog (packages/app/src/data/acp-provider-catalog.ts).
|
|
46
|
+
// Local binaries only: detect must not run `npx -y`, which would download a package just to probe it.
|
|
47
|
+
{ id: 'amp', name: 'Amp', candidates: [['amp-acp']] },
|
|
48
|
+
{
|
|
49
|
+
id: 'auggie',
|
|
50
|
+
name: 'Auggie CLI',
|
|
51
|
+
candidates: [['auggie', '--acp']],
|
|
52
|
+
env: { AUGMENT_DISABLE_AUTO_UPDATE: '1' },
|
|
53
|
+
},
|
|
54
|
+
{ id: 'autohand', name: 'Autohand Code', candidates: [['autohand-acp']] },
|
|
55
|
+
{ id: 'cline', name: 'Cline', candidates: [['cline', '--acp']] },
|
|
56
|
+
{ id: 'codebuddy', name: 'Codebuddy Code', candidates: [['codebuddy', '--acp']] },
|
|
57
|
+
{ id: 'codewhale', name: 'CodeWhale', candidates: [['codewhale', 'serve', '--acp']] },
|
|
58
|
+
{ id: 'cortex', name: 'Cortex Code', candidates: [['cortex', 'acp', 'serve']] },
|
|
59
|
+
{ id: 'corust', name: 'Corust Agent', candidates: [['corust-agent-acp']] },
|
|
60
|
+
{ id: 'crow', name: 'crow-cli', candidates: [['crow-cli', 'acp']] },
|
|
61
|
+
{ id: 'deepagents', name: 'DeepAgents', candidates: [['deepagents-acp']] },
|
|
62
|
+
{ id: 'devin', name: 'Devin CLI', candidates: [['devin', 'acp']] },
|
|
63
|
+
{ id: 'dimcode', name: 'DimCode', candidates: [['dimcode', 'acp'], ['dim', 'acp']] },
|
|
64
|
+
{ id: 'dirac', name: 'Dirac', candidates: [['dirac', '--acp']] },
|
|
65
|
+
{
|
|
66
|
+
id: 'droid',
|
|
67
|
+
name: 'Factory Droid',
|
|
68
|
+
candidates: [['droid', 'exec', '--output-format', 'acp-daemon']],
|
|
69
|
+
env: { DROID_DISABLE_AUTO_UPDATE: 'true', FACTORY_DROID_AUTO_UPDATE_ENABLED: 'false' },
|
|
70
|
+
},
|
|
71
|
+
{ id: 'fast-agent', name: 'fast-agent', candidates: [['fast-agent-acp', '-x']] },
|
|
72
|
+
{ id: 'gjc', name: 'Gajae Code', candidates: [['gjc', 'acp']], env: { GJC_ACP_PERMISSION_MODE: 'prompt' } },
|
|
73
|
+
{ id: 'glm', name: 'GLM Agent', candidates: [['glm-acp-agent']] },
|
|
74
|
+
{ id: 'grok', name: 'Grok', candidates: [['grok', 'agent', 'stdio']] },
|
|
75
|
+
{ id: 'hoplite', name: 'Hoplite', candidates: [['hoplite', 'acp']] },
|
|
76
|
+
{ id: 'junie', name: 'Junie', candidates: [['junie', '--acp', 'true']] },
|
|
77
|
+
{ id: 'kilo', name: 'Kilo Code', candidates: [['kilo', 'acp']] },
|
|
78
|
+
{ id: 'kiro', name: 'Kiro CLI', candidates: [['kiro-cli', 'acp']] },
|
|
79
|
+
{ id: 'minimax', name: 'MiniMax Code', candidates: [['mcode', 'acp']] },
|
|
80
|
+
{ id: 'minion', name: 'Minion Code', candidates: [['minion-code', 'acp']] },
|
|
81
|
+
{ id: 'vibe', name: 'Mistral Vibe', candidates: [['vibe-acp']] },
|
|
82
|
+
{ id: 'nova', name: 'Nova', candidates: [['nova', 'acp']] },
|
|
83
|
+
{ id: 'poolside', name: 'Poolside', candidates: [['pool', 'acp']] },
|
|
84
|
+
{ id: 'agoragentic', name: 'Agoragentic', candidates: [['agoragentic-mcp', '--acp']] },
|
|
85
|
+
{ id: 'sigit', name: 'siGit Code', candidates: [['sigit']] },
|
|
86
|
+
{ id: 'stakpak', name: 'Stakpak', candidates: [['stakpak', 'acp']] },
|
|
87
|
+
{ id: 'trae', name: 'TRAE CLI', candidates: [['traecli', 'acp', 'serve']] },
|
|
88
|
+
{
|
|
89
|
+
id: 'vtcode',
|
|
90
|
+
name: 'VT Code',
|
|
91
|
+
candidates: [['vtcode', 'acp']],
|
|
92
|
+
env: { VT_ACP_ENABLED: '1', VT_ACP_ZED_ENABLED: '1' },
|
|
44
93
|
},
|
|
45
94
|
];
|
|
46
95
|
/** The known agent a manual launch command starts, by executable name and arguments. */
|
|
@@ -52,6 +101,17 @@ export function inferAgentId(command, args = []) {
|
|
|
52
101
|
}
|
|
53
102
|
// Some agents cold-start slowly (OpenCode boots a local server: ~40s on first run).
|
|
54
103
|
const PROBE_TIMEOUT_MS = 45_000;
|
|
104
|
+
/** A launch stayed up but never completed the ACP handshake. Further flags of that agent are not tried. */
|
|
105
|
+
export class AcpProbeTimeoutError extends Error {
|
|
106
|
+
constructor() {
|
|
107
|
+
super('no ACP handshake within timeout');
|
|
108
|
+
this.name = 'AcpProbeTimeoutError';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** A quick failure can be a wrong flag. A timeout means that agent is not going to answer. */
|
|
112
|
+
export function shouldTryNextAcpCandidate(error) {
|
|
113
|
+
return !(error instanceof AcpProbeTimeoutError);
|
|
114
|
+
}
|
|
55
115
|
export async function findExecutable(name, envPath = process.env.PATH ?? '') {
|
|
56
116
|
const extensions = process.platform === 'win32' ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';') : [''];
|
|
57
117
|
for (const dir of envPath.split(path.delimiter).filter(Boolean)) {
|
|
@@ -70,8 +130,12 @@ export async function findExecutable(name, envPath = process.env.PATH ?? '') {
|
|
|
70
130
|
return null;
|
|
71
131
|
}
|
|
72
132
|
/** Starts the agent, runs `initialize`, and stops it. Never leaves the process running. */
|
|
73
|
-
export async function probeAcpAgent(command, args, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
74
|
-
const launch = agentLaunch(command, args, {
|
|
133
|
+
export async function probeAcpAgent(command, args, timeoutMs = PROBE_TIMEOUT_MS, env) {
|
|
134
|
+
const launch = agentLaunch(command, args, {
|
|
135
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
136
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
137
|
+
windowsHide: true,
|
|
138
|
+
});
|
|
75
139
|
const child = spawn(launch.command, launch.args, launch.options);
|
|
76
140
|
if (!child.stdin || !child.stdout)
|
|
77
141
|
throw new Error('ACP probe stdio was not piped');
|
|
@@ -82,7 +146,7 @@ export async function probeAcpAgent(command, args, timeoutMs = PROBE_TIMEOUT_MS)
|
|
|
82
146
|
child.once('exit', (code) => reject(new Error(`exited before handshake (code ${code})`)));
|
|
83
147
|
});
|
|
84
148
|
const timedOut = new Promise((_, reject) => {
|
|
85
|
-
timer = setTimeout(() => reject(new
|
|
149
|
+
timer = setTimeout(() => reject(new AcpProbeTimeoutError()), timeoutMs);
|
|
86
150
|
});
|
|
87
151
|
const connection = new ClientSideConnection(() => ({
|
|
88
152
|
sessionUpdate: async () => { },
|
|
@@ -118,17 +182,29 @@ export async function detectAcpAgents(options = {}) {
|
|
|
118
182
|
return { id: agent.id, name: agent.name, command, args, verified: false, agentName: null, agentVersion: null };
|
|
119
183
|
}
|
|
120
184
|
try {
|
|
121
|
-
const info = await probeAcpAgent(command, args);
|
|
122
|
-
|
|
185
|
+
const info = await probeAcpAgent(command, args, options.timeoutMs, agent.env);
|
|
186
|
+
const detected = {
|
|
187
|
+
id: agent.id,
|
|
188
|
+
name: agent.name,
|
|
189
|
+
command,
|
|
190
|
+
args,
|
|
191
|
+
verified: true,
|
|
192
|
+
...info,
|
|
193
|
+
...(agent.env ? { env: agent.env } : {}),
|
|
194
|
+
};
|
|
195
|
+
options.onDetected?.(detected);
|
|
196
|
+
return detected;
|
|
123
197
|
}
|
|
124
198
|
catch (error) {
|
|
125
199
|
lastError = error instanceof Error ? error.message : String(error);
|
|
200
|
+
if (!shouldTryNextAcpCandidate(error))
|
|
201
|
+
break;
|
|
126
202
|
}
|
|
127
203
|
}
|
|
128
204
|
if (!lastError) {
|
|
129
205
|
if (agent.adapter && (await findExecutable(agent.adapter.baseBinary))) {
|
|
130
206
|
const [binary, ...args] = agent.candidates[0];
|
|
131
|
-
|
|
207
|
+
const detected = {
|
|
132
208
|
id: agent.id,
|
|
133
209
|
name: agent.name,
|
|
134
210
|
command: binary,
|
|
@@ -136,14 +212,51 @@ export async function detectAcpAgents(options = {}) {
|
|
|
136
212
|
verified: false,
|
|
137
213
|
agentName: null,
|
|
138
214
|
agentVersion: null,
|
|
139
|
-
installHint: agent.adapter.
|
|
215
|
+
installHint: `npm install -g ${agent.adapter.packageName}`,
|
|
140
216
|
};
|
|
217
|
+
options.onDetected?.(detected);
|
|
218
|
+
return detected;
|
|
141
219
|
}
|
|
142
220
|
return null;
|
|
143
221
|
}
|
|
144
222
|
const [binary, ...args] = agent.candidates[0];
|
|
145
223
|
const command = (await findExecutable(binary)) ?? binary;
|
|
146
|
-
|
|
224
|
+
const detected = {
|
|
225
|
+
id: agent.id,
|
|
226
|
+
name: agent.name,
|
|
227
|
+
command,
|
|
228
|
+
args,
|
|
229
|
+
verified: false,
|
|
230
|
+
agentName: null,
|
|
231
|
+
agentVersion: null,
|
|
232
|
+
error: lastError,
|
|
233
|
+
};
|
|
234
|
+
options.onDetected?.(detected);
|
|
235
|
+
return detected;
|
|
147
236
|
}));
|
|
148
237
|
return results.filter((agent) => agent !== null);
|
|
149
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Agents whose own CLI is on PATH but none of the ACP launch commands are.
|
|
241
|
+
* Those need a global adapter package before a gateway can start them.
|
|
242
|
+
*/
|
|
243
|
+
export async function findMissingAcpAdapters(envPath = process.env.PATH ?? '') {
|
|
244
|
+
const missing = [];
|
|
245
|
+
for (const agent of KNOWN_ACP_AGENTS) {
|
|
246
|
+
if (!agent.adapter)
|
|
247
|
+
continue;
|
|
248
|
+
if (!(await findExecutable(agent.adapter.baseBinary, envPath)))
|
|
249
|
+
continue;
|
|
250
|
+
let adapterPresent = false;
|
|
251
|
+
for (const [binary] of agent.candidates) {
|
|
252
|
+
if (await findExecutable(binary, envPath)) {
|
|
253
|
+
adapterPresent = true;
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (!adapterPresent) {
|
|
258
|
+
missing.push({ id: agent.id, name: agent.name, packageName: agent.adapter.packageName });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return missing;
|
|
262
|
+
}
|
|
@@ -11,6 +11,18 @@ export declare const ERROR_CODES: {
|
|
|
11
11
|
readonly exitCode: 3;
|
|
12
12
|
readonly description: "The configured credentials were rejected.";
|
|
13
13
|
};
|
|
14
|
+
readonly AUTH_PAIRING_FAILED: {
|
|
15
|
+
readonly exitCode: 3;
|
|
16
|
+
readonly description: "Device QR login failed because the server rejected the pairing request.";
|
|
17
|
+
};
|
|
18
|
+
readonly AUTH_PAIRING_TIMEOUT: {
|
|
19
|
+
readonly exitCode: 3;
|
|
20
|
+
readonly description: "Device QR login timed out before the phone approved this computer.";
|
|
21
|
+
};
|
|
22
|
+
readonly AUTH_PAIRING_UNSUPPORTED: {
|
|
23
|
+
readonly exitCode: 3;
|
|
24
|
+
readonly description: "This server does not accept the CLI device type for QR login yet.";
|
|
25
|
+
};
|
|
14
26
|
readonly AUTH_REQUIRED: {
|
|
15
27
|
readonly exitCode: 3;
|
|
16
28
|
readonly description: "The command needs an authenticated Dinox account.";
|
package/dist/utils/errorCodes.js
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
export const ERROR_CODES = {
|
|
9
9
|
ALREADY_EXISTS: { exitCode: 5, description: 'The target resource already exists.' },
|
|
10
10
|
AUTH_INVALID: { exitCode: 3, description: 'The configured credentials were rejected.' },
|
|
11
|
+
AUTH_PAIRING_FAILED: { exitCode: 3, description: 'Device QR login failed because the server rejected the pairing request.' },
|
|
12
|
+
AUTH_PAIRING_TIMEOUT: { exitCode: 3, description: 'Device QR login timed out before the phone approved this computer.' },
|
|
13
|
+
AUTH_PAIRING_UNSUPPORTED: { exitCode: 3, description: 'This server does not accept the CLI device type for QR login yet.' },
|
|
11
14
|
AUTH_REQUIRED: { exitCode: 3, description: 'The command needs an authenticated Dinox account.' },
|
|
12
15
|
AUTH_STATE_CHANGED: { exitCode: 3, description: 'Authentication changed while the command was running.' },
|
|
13
16
|
CACHE_CLEANUP_FAILED: { exitCode: 1, description: 'Local cache cleanup after logout did not complete.' },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dinoxx/dinox-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Dinox CLI",
|
|
5
5
|
"main": "dist/dinox.js",
|
|
6
6
|
"scripts": {
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
"@tiptap/markdown": "^3.20.0",
|
|
79
79
|
"better-sqlite3": "^12.6.2",
|
|
80
80
|
"commander": "^14.0.3",
|
|
81
|
+
"qrcode": "1.5.4",
|
|
81
82
|
"remark-gfm": "^4.0.1",
|
|
82
83
|
"remark-parse": "^11.0.0",
|
|
83
84
|
"remark-stringify": "^11.0.0",
|
|
@@ -93,6 +94,7 @@
|
|
|
93
94
|
"devDependencies": {
|
|
94
95
|
"@types/better-sqlite3": "^7.6.13",
|
|
95
96
|
"@types/node": "^20.19.0",
|
|
97
|
+
"@types/qrcode": "1.5.6",
|
|
96
98
|
"eslint": "^10.12.0",
|
|
97
99
|
"tsx": "^4.20.3",
|
|
98
100
|
"typescript": "^5.9.3",
|