linkshell-cli 0.9.0 → 0.10.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 +11 -6
- package/dist/cli/src/commands/gateway.d.ts +2 -0
- package/dist/cli/src/commands/gateway.js +29 -0
- package/dist/cli/src/commands/gateway.js.map +1 -0
- package/dist/cli/src/commands/host.d.ts +2 -0
- package/dist/cli/src/commands/host.js +12 -2
- package/dist/cli/src/commands/host.js.map +1 -1
- package/dist/cli/src/commands/setup.js +4 -2
- package/dist/cli/src/commands/setup.js.map +1 -1
- package/dist/cli/src/index.js +12 -334
- package/dist/cli/src/index.js.map +1 -1
- package/dist/cli/src/utils/daemon.d.ts +0 -7
- package/dist/cli/src/utils/daemon.js +1 -18
- package/dist/cli/src/utils/daemon.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/gateway/src/accounts.d.ts +6 -0
- package/dist/gateway/src/accounts.js +32 -0
- package/dist/gateway/src/accounts.js.map +1 -0
- package/dist/gateway/src/index.d.ts +4 -0
- package/dist/gateway/src/index.js +5 -0
- package/dist/gateway/src/index.js.map +1 -0
- package/dist/gateway/src/main.d.ts +1 -0
- package/dist/gateway/src/main.js +57 -0
- package/dist/gateway/src/main.js.map +1 -0
- package/dist/gateway/src/rate-limit.d.ts +22 -0
- package/dist/gateway/src/rate-limit.js +66 -0
- package/dist/gateway/src/rate-limit.js.map +1 -0
- package/dist/gateway/src/relay.d.ts +84 -0
- package/dist/gateway/src/relay.js +434 -0
- package/dist/gateway/src/relay.js.map +1 -0
- package/dist/gateway/src/serve.d.ts +34 -0
- package/dist/gateway/src/serve.js +102 -0
- package/dist/gateway/src/serve.js.map +1 -0
- package/dist/gateway/src/store.d.ts +35 -0
- package/dist/gateway/src/store.js +109 -0
- package/dist/gateway/src/store.js.map +1 -0
- package/dist/gateway/src/subscription.d.ts +18 -0
- package/dist/gateway/src/subscription.js +46 -0
- package/dist/gateway/src/subscription.js.map +1 -0
- package/dist/host/src/drivers/claude/transcript.d.ts +23 -23
- package/dist/host/src/fs.d.ts +3 -3
- package/dist/host/src/input.d.ts +4 -4
- package/dist/host/src/terminals.d.ts +1 -1
- package/dist/wire/src/model.d.ts +6 -6
- package/dist/wire/src/relay.d.ts +8 -8
- package/dist/wire/src/rpc.d.ts +818 -818
- package/dist/wire/src/updates.d.ts +18 -18
- package/package.json +4 -13
- package/src/commands/gateway.ts +31 -0
- package/src/commands/host.ts +13 -2
- package/src/commands/setup.ts +4 -2
- package/src/index.ts +14 -429
- package/src/utils/daemon.ts +2 -27
- package/dist/cli/src/config.d.ts +0 -14
- package/dist/cli/src/config.js +0 -23
- package/dist/cli/src/config.js.map +0 -1
- package/dist/cli/src/machine-id.d.ts +0 -6
- package/dist/cli/src/machine-id.js +0 -50
- package/dist/cli/src/machine-id.js.map +0 -1
- package/dist/cli/src/providers.d.ts +0 -13
- package/dist/cli/src/providers.js +0 -77
- package/dist/cli/src/providers.js.map +0 -1
- package/dist/cli/src/runtime/acp/acp-client.d.ts +0 -128
- package/dist/cli/src/runtime/acp/acp-client.js +0 -430
- package/dist/cli/src/runtime/acp/acp-client.js.map +0 -1
- package/dist/cli/src/runtime/acp/agent-process-discovery.d.ts +0 -48
- package/dist/cli/src/runtime/acp/agent-process-discovery.js +0 -278
- package/dist/cli/src/runtime/acp/agent-process-discovery.js.map +0 -1
- package/dist/cli/src/runtime/acp/agent-session.d.ts +0 -62
- package/dist/cli/src/runtime/acp/agent-session.js +0 -1081
- package/dist/cli/src/runtime/acp/agent-session.js.map +0 -1
- package/dist/cli/src/runtime/acp/agent-workspace.d.ts +0 -311
- package/dist/cli/src/runtime/acp/agent-workspace.js +0 -6083
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +0 -1
- package/dist/cli/src/runtime/acp/claude-sdk-client.d.ts +0 -121
- package/dist/cli/src/runtime/acp/claude-sdk-client.js +0 -1147
- package/dist/cli/src/runtime/acp/claude-sdk-client.js.map +0 -1
- package/dist/cli/src/runtime/acp/claude-stream-json-client.d.ts +0 -53
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js +0 -552
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js.map +0 -1
- package/dist/cli/src/runtime/acp/json-rpc.d.ts +0 -22
- package/dist/cli/src/runtime/acp/json-rpc.js +0 -160
- package/dist/cli/src/runtime/acp/json-rpc.js.map +0 -1
- package/dist/cli/src/runtime/acp/provider-resolver.d.ts +0 -16
- package/dist/cli/src/runtime/acp/provider-resolver.js +0 -132
- package/dist/cli/src/runtime/acp/provider-resolver.js.map +0 -1
- package/dist/cli/src/runtime/bridge-session.d.ts +0 -97
- package/dist/cli/src/runtime/bridge-session.js +0 -1364
- package/dist/cli/src/runtime/bridge-session.js.map +0 -1
- package/dist/cli/src/runtime/hook-command.d.ts +0 -9
- package/dist/cli/src/runtime/hook-command.js +0 -80
- package/dist/cli/src/runtime/hook-command.js.map +0 -1
- package/dist/cli/src/runtime/screen-fallback.d.ts +0 -22
- package/dist/cli/src/runtime/screen-fallback.js +0 -202
- package/dist/cli/src/runtime/screen-fallback.js.map +0 -1
- package/dist/cli/src/runtime/screen-share.d.ts +0 -34
- package/dist/cli/src/runtime/screen-share.js +0 -376
- package/dist/cli/src/runtime/screen-share.js.map +0 -1
- package/dist/cli/src/runtime/scrollback.d.ts +0 -11
- package/dist/cli/src/runtime/scrollback.js +0 -33
- package/dist/cli/src/runtime/scrollback.js.map +0 -1
- package/dist/cli/src/runtime/usage-report.d.ts +0 -34
- package/dist/cli/src/runtime/usage-report.js +0 -332
- package/dist/cli/src/runtime/usage-report.js.map +0 -1
- package/dist/cli/src/utils/keep-awake.d.ts +0 -6
- package/dist/cli/src/utils/keep-awake.js +0 -48
- package/dist/cli/src/utils/keep-awake.js.map +0 -1
- package/dist/cli/src/utils/lan-ip.d.ts +0 -9
- package/dist/cli/src/utils/lan-ip.js +0 -61
- package/dist/cli/src/utils/lan-ip.js.map +0 -1
- package/dist/host/src/input-helper.d.ts +0 -1
- package/dist/host/src/input-helper.js +0 -564
- package/dist/host/src/input-helper.js.map +0 -1
- package/dist/shared-protocol/src/index.d.ts +0 -17029
- package/dist/shared-protocol/src/index.js +0 -1064
- package/dist/shared-protocol/src/index.js.map +0 -1
- package/scripts/copy-web.mjs +0 -34
- package/src/config.ts +0 -36
- package/src/machine-id.ts +0 -61
- package/src/providers.ts +0 -97
- package/src/runtime/acp/acp-client.ts +0 -521
- package/src/runtime/acp/agent-process-discovery.ts +0 -308
- package/src/runtime/acp/agent-session.ts +0 -1186
- package/src/runtime/acp/agent-workspace.ts +0 -6590
- package/src/runtime/acp/claude-sdk-client.ts +0 -1221
- package/src/runtime/acp/claude-stream-json-client.ts +0 -633
- package/src/runtime/acp/json-rpc.ts +0 -184
- package/src/runtime/acp/provider-resolver.ts +0 -154
- package/src/runtime/bridge-session.ts +0 -1549
- package/src/runtime/hook-command.ts +0 -73
- package/src/runtime/screen-fallback.ts +0 -235
- package/src/runtime/screen-share.ts +0 -426
- package/src/runtime/scrollback.ts +0 -43
- package/src/runtime/usage-report.ts +0 -342
- package/src/utils/keep-awake.ts +0 -61
- package/src/utils/lan-ip.ts +0 -61
- package/web/assets/FileBrowser-wOAfY-xj.js +0 -2
- package/web/assets/PortPreview-Cbvv7yzA.js +0 -2
- package/web/assets/TerminalPanel-CTSklkct.js +0 -1
- package/web/assets/UsageDashboard-CJ1AsNmd.js +0 -1
- package/web/assets/index-BsnDaeCt.css +0 -33
- package/web/assets/index-q85psYZ5.js +0 -36
- package/web/assets/vendor-highlight-NXWAAyIv.js +0 -5
- package/web/assets/vendor-react-M7xG3qiw.js +0 -49
- package/web/assets/vendor-xterm-D15THcI4.js +0 -9
- package/web/index.html +0 -37
- package/web/logos/claude.png +0 -0
- package/web/logos/codex.png +0 -0
- package/web/logos/copilot.png +0 -0
- package/web/logos/gemini.png +0 -0
- package/web/logos/linkshell-1024.png +0 -0
- package/web/logos/linkshell-splash.png +0 -0
- package/web/logos/linkshell.png +0 -0
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
|
-
import type { AgentFraming } from "./provider-resolver.js";
|
|
3
|
-
|
|
4
|
-
interface JsonRpcRequest {
|
|
5
|
-
jsonrpc: "2.0";
|
|
6
|
-
id: number | string;
|
|
7
|
-
method: string;
|
|
8
|
-
params?: unknown;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface JsonRpcResponse {
|
|
12
|
-
jsonrpc: "2.0";
|
|
13
|
-
id: number | string;
|
|
14
|
-
result?: unknown;
|
|
15
|
-
error?: { code: number; message: string; data?: unknown };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface JsonRpcNotification {
|
|
19
|
-
jsonrpc: "2.0";
|
|
20
|
-
method: string;
|
|
21
|
-
params?: unknown;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotification;
|
|
25
|
-
|
|
26
|
-
export class JsonRpcStdioTransport {
|
|
27
|
-
private child: ChildProcessWithoutNullStreams | undefined;
|
|
28
|
-
private nextId = 1;
|
|
29
|
-
private pending = new Map<
|
|
30
|
-
number | string,
|
|
31
|
-
{
|
|
32
|
-
resolve: (value: unknown) => void;
|
|
33
|
-
reject: (error: Error) => void;
|
|
34
|
-
timer?: ReturnType<typeof setTimeout>;
|
|
35
|
-
}
|
|
36
|
-
>();
|
|
37
|
-
private buffer = "";
|
|
38
|
-
private closed = false;
|
|
39
|
-
|
|
40
|
-
constructor(
|
|
41
|
-
private readonly command: string,
|
|
42
|
-
private readonly framing: AgentFraming,
|
|
43
|
-
private readonly onNotification: (method: string, params: unknown) => void,
|
|
44
|
-
private readonly onRequest: (method: string, params: unknown) => Promise<unknown> | unknown,
|
|
45
|
-
private readonly onExit: (message: string) => void,
|
|
46
|
-
) {}
|
|
47
|
-
|
|
48
|
-
start(cwd: string): void {
|
|
49
|
-
if (this.child) return;
|
|
50
|
-
this.closed = false;
|
|
51
|
-
this.child = spawn(this.command, {
|
|
52
|
-
cwd,
|
|
53
|
-
shell: true,
|
|
54
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
55
|
-
env: process.env,
|
|
56
|
-
});
|
|
57
|
-
this.child.stdout.setEncoding("utf8");
|
|
58
|
-
this.child.stderr.setEncoding("utf8");
|
|
59
|
-
this.child.stdout.on("data", (chunk: string) => this.read(chunk));
|
|
60
|
-
this.child.stderr.on("data", (chunk: string) => {
|
|
61
|
-
const trimmed = chunk.trim();
|
|
62
|
-
if (trimmed) process.stderr.write(`[agent:stderr] ${trimmed}\n`);
|
|
63
|
-
});
|
|
64
|
-
this.child.on("error", (error) => this.failAll(error.message));
|
|
65
|
-
this.child.on("exit", (code, signal) => {
|
|
66
|
-
this.failAll(`ACP agent exited (code=${code ?? "null"}, signal=${signal ?? "null"})`);
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
request(method: string, params?: unknown, timeoutMs: number | null = 30_000): Promise<unknown> {
|
|
71
|
-
if (!this.child || this.child.stdin.destroyed) {
|
|
72
|
-
return Promise.reject(new Error("ACP agent is not running"));
|
|
73
|
-
}
|
|
74
|
-
const id = this.nextId++;
|
|
75
|
-
const message: JsonRpcRequest = { jsonrpc: "2.0", id, method, params };
|
|
76
|
-
return new Promise((resolve, reject) => {
|
|
77
|
-
const timer = timeoutMs && timeoutMs > 0
|
|
78
|
-
? setTimeout(() => {
|
|
79
|
-
this.pending.delete(id);
|
|
80
|
-
reject(new Error(`ACP request timed out: ${method}`));
|
|
81
|
-
}, timeoutMs)
|
|
82
|
-
: undefined;
|
|
83
|
-
this.pending.set(id, { resolve, reject, timer });
|
|
84
|
-
this.write(message);
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
notify(method: string, params?: unknown): void {
|
|
89
|
-
this.write({ jsonrpc: "2.0", method, params });
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
stop(): void {
|
|
93
|
-
const child = this.child;
|
|
94
|
-
this.child = undefined;
|
|
95
|
-
if (child && !child.killed) child.kill("SIGTERM");
|
|
96
|
-
this.failAll("ACP transport stopped");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private write(message: JsonRpcMessage): void {
|
|
100
|
-
const raw = JSON.stringify(message);
|
|
101
|
-
if (this.framing === "newline") {
|
|
102
|
-
this.child?.stdin.write(`${raw}\n`);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
this.child?.stdin.write(`Content-Length: ${Buffer.byteLength(raw, "utf8")}\r\n\r\n${raw}`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private read(chunk: string): void {
|
|
109
|
-
this.buffer += chunk;
|
|
110
|
-
while (this.buffer.length > 0) {
|
|
111
|
-
const contentLengthMatch = this.buffer.match(/^Content-Length:\s*(\d+)\r?\n/i);
|
|
112
|
-
if (contentLengthMatch) {
|
|
113
|
-
const headerEnd = this.buffer.indexOf("\r\n\r\n");
|
|
114
|
-
const altHeaderEnd = this.buffer.indexOf("\n\n");
|
|
115
|
-
const end = headerEnd >= 0 ? headerEnd + 4 : altHeaderEnd >= 0 ? altHeaderEnd + 2 : -1;
|
|
116
|
-
if (end < 0) return;
|
|
117
|
-
const length = Number(contentLengthMatch[1]);
|
|
118
|
-
if (this.buffer.length < end + length) return;
|
|
119
|
-
const raw = this.buffer.slice(end, end + length);
|
|
120
|
-
this.buffer = this.buffer.slice(end + length);
|
|
121
|
-
this.dispatch(raw);
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const newline = this.buffer.indexOf("\n");
|
|
126
|
-
if (newline < 0) return;
|
|
127
|
-
const raw = this.buffer.slice(0, newline).trim();
|
|
128
|
-
this.buffer = this.buffer.slice(newline + 1);
|
|
129
|
-
if (raw) this.dispatch(raw);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
private dispatch(raw: string): void {
|
|
134
|
-
let message: JsonRpcMessage;
|
|
135
|
-
try {
|
|
136
|
-
message = JSON.parse(raw) as JsonRpcMessage;
|
|
137
|
-
} catch {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if ("id" in message && ("result" in message || "error" in message)) {
|
|
142
|
-
const pending = this.pending.get(message.id);
|
|
143
|
-
if (!pending) return;
|
|
144
|
-
this.pending.delete(message.id);
|
|
145
|
-
if (pending.timer) clearTimeout(pending.timer);
|
|
146
|
-
const response = message as JsonRpcResponse;
|
|
147
|
-
if (response.error) pending.reject(new Error(response.error.message));
|
|
148
|
-
else pending.resolve(response.result);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if ("method" in message && "id" in message) {
|
|
153
|
-
Promise.resolve(this.onRequest(message.method, message.params))
|
|
154
|
-
.then((result) => this.write({ jsonrpc: "2.0", id: message.id, result }))
|
|
155
|
-
.catch((error) => {
|
|
156
|
-
this.write({
|
|
157
|
-
jsonrpc: "2.0",
|
|
158
|
-
id: message.id,
|
|
159
|
-
error: {
|
|
160
|
-
code: -32000,
|
|
161
|
-
message: error instanceof Error ? error.message : String(error),
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if ("method" in message) {
|
|
169
|
-
this.onNotification(message.method, message.params);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private failAll(message: string): void {
|
|
174
|
-
if (this.closed) return;
|
|
175
|
-
this.closed = true;
|
|
176
|
-
this.child = undefined;
|
|
177
|
-
this.onExit(message);
|
|
178
|
-
for (const [, pending] of this.pending) {
|
|
179
|
-
if (pending.timer) clearTimeout(pending.timer);
|
|
180
|
-
pending.reject(new Error(message));
|
|
181
|
-
}
|
|
182
|
-
this.pending.clear();
|
|
183
|
-
}
|
|
184
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
5
|
-
|
|
6
|
-
import type { KnownAgentProvider } from "./agent-process-discovery.js";
|
|
7
|
-
|
|
8
|
-
export type AgentProvider = string;
|
|
9
|
-
export type { KnownAgentProvider };
|
|
10
|
-
export type AgentProtocol = "acp" | "codex-app-server" | "claude-agent-sdk" | "claude-stream-json";
|
|
11
|
-
export type AgentFraming = "content-length" | "newline";
|
|
12
|
-
|
|
13
|
-
const require = createRequire(import.meta.url);
|
|
14
|
-
|
|
15
|
-
export interface AgentCommandConfig {
|
|
16
|
-
command: string;
|
|
17
|
-
provider: AgentProvider;
|
|
18
|
-
protocol: AgentProtocol;
|
|
19
|
-
framing: AgentFraming;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function resolveAgentCommand(input: {
|
|
23
|
-
provider: AgentProvider;
|
|
24
|
-
command?: string;
|
|
25
|
-
}): AgentCommandConfig | null {
|
|
26
|
-
const explicit = input.command?.trim();
|
|
27
|
-
if (explicit) {
|
|
28
|
-
const isCodexAppServer = /\bcodex\b/.test(explicit) && /\bapp-server\b/.test(explicit);
|
|
29
|
-
const isClaudeCli = input.provider === "claude" && /\bclaude\b/.test(explicit);
|
|
30
|
-
return {
|
|
31
|
-
provider: input.provider,
|
|
32
|
-
command: explicit,
|
|
33
|
-
protocol: isCodexAppServer ? "codex-app-server" : isClaudeCli ? "claude-stream-json" : "acp",
|
|
34
|
-
// Gemini --acp, cursor-agent acp, and other ACP CLIs speak NDJSON, not LSP Content-Length.
|
|
35
|
-
framing: "newline",
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (input.provider === "codex") {
|
|
40
|
-
return {
|
|
41
|
-
provider: "codex",
|
|
42
|
-
command: "codex app-server --listen stdio://",
|
|
43
|
-
protocol: "codex-app-server",
|
|
44
|
-
framing: "newline",
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (input.provider === "claude") {
|
|
49
|
-
if (process.env.LINKSHELL_CLAUDE_PROVIDER !== "stream-json" && hasPackage("@anthropic-ai/claude-agent-sdk")) {
|
|
50
|
-
return {
|
|
51
|
-
provider: "claude",
|
|
52
|
-
command: "claude-agent-sdk",
|
|
53
|
-
protocol: "claude-agent-sdk",
|
|
54
|
-
framing: "newline",
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
provider: "claude",
|
|
59
|
-
command: "claude --print --output-format stream-json --input-format stream-json --verbose --permission-mode bypassPermissions",
|
|
60
|
-
protocol: "claude-stream-json",
|
|
61
|
-
framing: "newline",
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (input.provider === "gemini") {
|
|
66
|
-
return {
|
|
67
|
-
provider: "gemini",
|
|
68
|
-
command: "gemini --acp",
|
|
69
|
-
protocol: "acp",
|
|
70
|
-
framing: "newline",
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (input.provider === "cursor") {
|
|
75
|
-
return {
|
|
76
|
-
provider: "cursor",
|
|
77
|
-
command: "cursor-agent acp",
|
|
78
|
-
protocol: "acp",
|
|
79
|
-
framing: "newline",
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (input.provider === "grok") {
|
|
84
|
-
return {
|
|
85
|
-
provider: "grok",
|
|
86
|
-
command: "grok agent stdio",
|
|
87
|
-
protocol: "acp",
|
|
88
|
-
framing: "newline",
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// copilot / opencode / kimi / custom: PTY-only unless caller passes --agent-command
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function hasPackage(name: string): boolean {
|
|
97
|
-
try {
|
|
98
|
-
require.resolve(name);
|
|
99
|
-
return true;
|
|
100
|
-
} catch {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function resolveBinary(bin: string): string | null {
|
|
106
|
-
// 1. Try which (PATH lookup)
|
|
107
|
-
try {
|
|
108
|
-
const path = execSync(`which ${bin}`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
109
|
-
if (path && existsSync(path)) return path;
|
|
110
|
-
} catch { /* not in PATH */ }
|
|
111
|
-
|
|
112
|
-
// 2. Common install locations (daemon may have a stripped PATH)
|
|
113
|
-
const home = homedir();
|
|
114
|
-
const candidates = [
|
|
115
|
-
`${home}/.npm-global/bin/${bin}`,
|
|
116
|
-
`${home}/.local/bin/${bin}`,
|
|
117
|
-
`/opt/homebrew/bin/${bin}`,
|
|
118
|
-
`/usr/local/bin/${bin}`,
|
|
119
|
-
`${home}/.nvm/versions/node/*/bin/${bin}`,
|
|
120
|
-
];
|
|
121
|
-
for (const candidate of candidates) {
|
|
122
|
-
// expand glob if present
|
|
123
|
-
if (candidate.includes("*")) {
|
|
124
|
-
try {
|
|
125
|
-
const expanded = execSync(`ls ${candidate} 2>/dev/null`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim().split("\n")[0];
|
|
126
|
-
if (expanded && existsSync(expanded)) return expanded;
|
|
127
|
-
} catch { continue; }
|
|
128
|
-
} else if (existsSync(candidate)) {
|
|
129
|
-
return candidate;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const INSTALL_BINARIES: Array<{ provider: KnownAgentProvider; binaries: string[] }> = [
|
|
137
|
-
{ provider: "claude", binaries: ["claude"] },
|
|
138
|
-
{ provider: "codex", binaries: ["codex"] },
|
|
139
|
-
{ provider: "gemini", binaries: ["gemini"] },
|
|
140
|
-
{ provider: "copilot", binaries: ["copilot"] },
|
|
141
|
-
{ provider: "opencode", binaries: ["opencode", "opencode-ai"] },
|
|
142
|
-
{ provider: "cursor", binaries: ["cursor-agent"] },
|
|
143
|
-
{ provider: "grok", binaries: ["grok"] },
|
|
144
|
-
{ provider: "kimi", binaries: ["kimi"] },
|
|
145
|
-
];
|
|
146
|
-
|
|
147
|
-
export function detectAvailableProviders(): AgentProvider[] {
|
|
148
|
-
const available: AgentProvider[] = [];
|
|
149
|
-
for (const entry of INSTALL_BINARIES) {
|
|
150
|
-
if (entry.binaries.some((bin) => resolveBinary(bin))) available.push(entry.provider);
|
|
151
|
-
}
|
|
152
|
-
return available;
|
|
153
|
-
}
|
|
154
|
-
|