@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.
@@ -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', install: 'npm install -g @agentclientprotocol/claude-agent-acp' },
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', install: 'npm install -g @agentclientprotocol/codex-acp' },
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', install: 'npm install -g zcode-acp' },
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, { stdio: ['pipe', 'pipe', 'ignore'], env: process.env, windowsHide: true });
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 Error('no ACP handshake within timeout')), timeoutMs);
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
- return { id: agent.id, name: agent.name, command, args, verified: true, ...info };
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
- return {
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.install,
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
- return { id: agent.id, name: agent.name, command, args, verified: false, agentName: null, agentVersion: null, error: lastError };
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.";
@@ -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.9.0",
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",