linkshell-cli 0.2.125 → 0.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.
- package/dist/cli/src/commands/setup.js +2 -20
- package/dist/cli/src/commands/setup.js.map +1 -1
- package/dist/cli/src/index.js +26 -28
- package/dist/cli/src/index.js.map +1 -1
- package/dist/cli/src/providers.d.ts +7 -3
- package/dist/cli/src/providers.js +19 -76
- package/dist/cli/src/providers.js.map +1 -1
- package/dist/cli/src/runtime/acp/agent-session.d.ts +1 -1
- package/dist/cli/src/runtime/acp/agent-session.js +4 -4
- package/dist/cli/src/runtime/acp/agent-session.js.map +1 -1
- package/dist/cli/src/runtime/acp/agent-workspace.d.ts +1 -1
- package/dist/cli/src/runtime/acp/agent-workspace.js +17 -62
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
- package/dist/cli/src/runtime/bridge-session.d.ts +1 -31
- package/dist/cli/src/runtime/bridge-session.js +57 -993
- package/dist/cli/src/runtime/bridge-session.js.map +1 -1
- package/dist/cli/src/runtime/screen-fallback.d.ts +1 -1
- package/dist/cli/src/runtime/screen-fallback.js +4 -4
- package/dist/cli/src/runtime/screen-fallback.js.map +1 -1
- package/dist/cli/src/runtime/screen-share.d.ts +1 -1
- package/dist/cli/src/runtime/screen-share.js +7 -7
- package/dist/cli/src/runtime/screen-share.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/dist/shared-protocol/src/index.d.ts +3743 -5570
- package/dist/shared-protocol/src/index.js +19 -84
- package/dist/shared-protocol/src/index.js.map +1 -1
- package/package.json +12 -12
- package/src/commands/setup.ts +5 -31
- package/src/index.ts +29 -34
- package/src/providers.ts +26 -108
- package/src/runtime/acp/agent-workspace.ts +18 -63
- package/src/runtime/bridge-session.ts +57 -1091
- package/src/runtime/screen-fallback.ts +5 -5
- package/src/runtime/screen-share.ts +8 -8
- package/src/types/linkshell-gateway.d.ts +18 -0
- package/dist/cli/src/runtime/acp-relay.d.ts +0 -23
- package/dist/cli/src/runtime/acp-relay.js +0 -73
- package/dist/cli/src/runtime/acp-relay.js.map +0 -1
- package/src/runtime/acp/agent-session.ts +0 -1180
package/src/providers.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { accessSync, constants, existsSync } from "node:fs";
|
|
2
|
-
import { delimiter, join } from "node:path";
|
|
3
|
-
import type { TerminalProvider } from "@linkshell/protocol";
|
|
2
|
+
import { basename, delimiter, join } from "node:path";
|
|
4
3
|
|
|
5
|
-
export type ProviderName =
|
|
4
|
+
export type ProviderName = "shell";
|
|
6
5
|
|
|
7
6
|
export interface ProviderConfig {
|
|
8
7
|
provider: ProviderName;
|
|
@@ -11,6 +10,8 @@ export interface ProviderConfig {
|
|
|
11
10
|
env: NodeJS.ProcessEnv;
|
|
12
11
|
}
|
|
13
12
|
|
|
13
|
+
export type ShellConfig = ProviderConfig;
|
|
14
|
+
|
|
14
15
|
function stripOuterQuotes(value: string): string {
|
|
15
16
|
const trimmed = value.trim();
|
|
16
17
|
if (
|
|
@@ -65,125 +66,42 @@ function which(bin: string): string | undefined {
|
|
|
65
66
|
return undefined;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
function
|
|
69
|
-
command
|
|
70
|
-
|
|
71
|
-
}): ProviderConfig {
|
|
72
|
-
const command = input.command ?? "claude";
|
|
73
|
-
const resolved = which(command);
|
|
74
|
-
if (!resolved) {
|
|
75
|
-
throw new Error(
|
|
76
|
-
`Claude CLI not found ("${command}"). Install it with: npm install -g @anthropic-ai/claude-code`,
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Claude starts an interactive REPL by default — that's exactly what we want in the PTY.
|
|
81
|
-
// Pass through any extra args the user provided.
|
|
82
|
-
return {
|
|
83
|
-
provider: "claude",
|
|
84
|
-
command: resolved,
|
|
85
|
-
args: input.args,
|
|
86
|
-
env: { ...process.env },
|
|
87
|
-
};
|
|
69
|
+
function shellSupportsLoginArg(command: string): boolean {
|
|
70
|
+
const name = basename(command).toLowerCase();
|
|
71
|
+
return ["bash", "zsh", "sh", "fish"].includes(name);
|
|
88
72
|
}
|
|
89
73
|
|
|
90
|
-
function
|
|
74
|
+
export function resolveShellConfig(input: {
|
|
91
75
|
command?: string;
|
|
92
|
-
args
|
|
93
|
-
}):
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
process.stderr.write(
|
|
104
|
-
"[warn] OPENAI_API_KEY not set — Codex may fail to authenticate\n",
|
|
105
|
-
);
|
|
106
|
-
}
|
|
76
|
+
args?: string[];
|
|
77
|
+
} = {}): ShellConfig {
|
|
78
|
+
const configuredShell = input.command ?? process.env.SHELL ?? (process.platform === "darwin" ? "/bin/zsh" : undefined);
|
|
79
|
+
const command = configuredShell ?? (process.platform === "win32" ? "cmd.exe" : "sh");
|
|
80
|
+
const resolved = which(command) ?? command;
|
|
81
|
+
const passthrough = input.args ?? [];
|
|
82
|
+
const args = passthrough.length > 0
|
|
83
|
+
? passthrough
|
|
84
|
+
: shellSupportsLoginArg(resolved)
|
|
85
|
+
? ["-l"]
|
|
86
|
+
: [];
|
|
107
87
|
|
|
108
88
|
return {
|
|
109
|
-
provider: "
|
|
89
|
+
provider: "shell",
|
|
110
90
|
command: resolved,
|
|
111
|
-
args
|
|
91
|
+
args,
|
|
112
92
|
env: { ...process.env },
|
|
113
93
|
};
|
|
114
94
|
}
|
|
115
95
|
|
|
116
|
-
function
|
|
96
|
+
export function resolveProviderConfig(input: {
|
|
97
|
+
provider?: string;
|
|
117
98
|
command?: string;
|
|
118
99
|
args: string[];
|
|
119
100
|
}): ProviderConfig {
|
|
120
|
-
|
|
121
|
-
const resolved = which(command);
|
|
122
|
-
if (!resolved) {
|
|
123
|
-
throw new Error(
|
|
124
|
-
`Gemini CLI not found ("${command}"). Install it with: npm install -g @anthropic-ai/gemini-cli or check https://github.com/anthropics/gemini-cli`,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (!process.env.GOOGLE_API_KEY && !process.env.GEMINI_API_KEY) {
|
|
101
|
+
if (input.provider && input.provider !== "shell") {
|
|
129
102
|
process.stderr.write(
|
|
130
|
-
|
|
103
|
+
`[warn] terminal provider "${input.provider}" is ignored in protocol v2; starting the system shell instead\n`,
|
|
131
104
|
);
|
|
132
105
|
}
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
provider: "gemini",
|
|
136
|
-
command: resolved,
|
|
137
|
-
args: input.args,
|
|
138
|
-
env: { ...process.env },
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function resolveCopilotProvider(input: {
|
|
143
|
-
command?: string;
|
|
144
|
-
args: string[];
|
|
145
|
-
}): ProviderConfig {
|
|
146
|
-
const command = input.command ?? "github-copilot";
|
|
147
|
-
const resolved = which(command);
|
|
148
|
-
if (!resolved) {
|
|
149
|
-
throw new Error(
|
|
150
|
-
`GitHub Copilot CLI not found ("${command}").`,
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
provider: "copilot",
|
|
156
|
-
command: resolved,
|
|
157
|
-
args: input.args,
|
|
158
|
-
env: { ...process.env },
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function resolveProviderConfig(input: {
|
|
163
|
-
provider: ProviderName;
|
|
164
|
-
command?: string;
|
|
165
|
-
args: string[];
|
|
166
|
-
}): ProviderConfig {
|
|
167
|
-
switch (input.provider) {
|
|
168
|
-
case "claude":
|
|
169
|
-
return resolveClaudeProvider(input);
|
|
170
|
-
case "codex":
|
|
171
|
-
return resolveCodexProvider(input);
|
|
172
|
-
case "gemini":
|
|
173
|
-
return resolveGeminiProvider(input);
|
|
174
|
-
case "copilot":
|
|
175
|
-
return resolveCopilotProvider(input);
|
|
176
|
-
case "custom": {
|
|
177
|
-
if (!input.command) {
|
|
178
|
-
throw new Error("custom provider requires --command");
|
|
179
|
-
}
|
|
180
|
-
const resolved = which(input.command);
|
|
181
|
-
return {
|
|
182
|
-
provider: "custom",
|
|
183
|
-
command: resolved ?? input.command,
|
|
184
|
-
args: input.args,
|
|
185
|
-
env: { ...process.env },
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
}
|
|
106
|
+
return resolveShellConfig({ command: input.command, args: input.args });
|
|
189
107
|
}
|
|
@@ -764,8 +764,6 @@ interface ProviderRuntimeCapabilities {
|
|
|
764
764
|
}
|
|
765
765
|
|
|
766
766
|
const ALL_REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh"] as const;
|
|
767
|
-
const ALL_PERMISSION_MODES = ["read_only", "workspace_write", "full_access"] as const;
|
|
768
|
-
const CODEX_COMMAND_NAMES = ["plan", "exit-plan", "compact", "clear", "status", "review", "subagents"] as const;
|
|
769
767
|
const CLAUDE_REMOTE_HIDDEN_COMMANDS = new Set([
|
|
770
768
|
"add-dir",
|
|
771
769
|
"agents",
|
|
@@ -1039,56 +1037,16 @@ function customClaudeCommands(cwd: string): AgentCommandDescriptor[] {
|
|
|
1039
1037
|
function defaultProviderCommands(provider: AgentProvider, cwd: string, enabled: boolean): AgentCommandDescriptor[] {
|
|
1040
1038
|
const disabledReason = enabled ? undefined : `${providerLabel(provider)} 未安装或启动失败`;
|
|
1041
1039
|
if (provider === "codex") {
|
|
1042
|
-
return
|
|
1043
|
-
provider,
|
|
1044
|
-
name,
|
|
1045
|
-
source: "linkshell",
|
|
1046
|
-
category: name === "plan" || name === "exit-plan" ? "Modes" : "Codex",
|
|
1047
|
-
description: {
|
|
1048
|
-
"plan": "Enter Codex plan mode",
|
|
1049
|
-
"exit-plan": "Exit Codex plan mode",
|
|
1050
|
-
compact: "Compact the current thread",
|
|
1051
|
-
clear: "Start a fresh Codex thread",
|
|
1052
|
-
status: "Show LinkShell agent status",
|
|
1053
|
-
review: "Ask Codex to review local changes",
|
|
1054
|
-
subagents: "Insert a delegation prompt",
|
|
1055
|
-
}[name],
|
|
1056
|
-
argsMode: name === "review" || name === "subagents" ? "optional" : "none",
|
|
1057
|
-
destructive: name === "clear",
|
|
1058
|
-
disabledReason,
|
|
1059
|
-
executionKind: name === "review" || name === "subagents" ? "prompt" : "native",
|
|
1060
|
-
}));
|
|
1040
|
+
return [];
|
|
1061
1041
|
}
|
|
1062
1042
|
if (provider === "claude") {
|
|
1063
|
-
const builtIns = CLAUDE_BUILT_IN_COMMANDS
|
|
1064
|
-
.filter((entry) => isClaudeRemoteFriendlyCommand(entry.name))
|
|
1065
|
-
.map((entry) => makeCommand({
|
|
1066
|
-
provider,
|
|
1067
|
-
name: entry.name,
|
|
1068
|
-
description: entry.description,
|
|
1069
|
-
argsMode: entry.argsMode,
|
|
1070
|
-
destructive: entry.destructive,
|
|
1071
|
-
disabledReason,
|
|
1072
|
-
executionKind: "prompt",
|
|
1073
|
-
}));
|
|
1074
1043
|
const custom = customClaudeCommands(cwd).map((command) => ({
|
|
1075
1044
|
...command,
|
|
1076
1045
|
disabledReason: command.disabledReason ?? disabledReason,
|
|
1077
1046
|
}));
|
|
1078
|
-
return
|
|
1047
|
+
return custom;
|
|
1079
1048
|
}
|
|
1080
|
-
return [
|
|
1081
|
-
makeCommand({
|
|
1082
|
-
provider,
|
|
1083
|
-
name: "status",
|
|
1084
|
-
source: "linkshell",
|
|
1085
|
-
category: "LinkShell",
|
|
1086
|
-
description: "Show LinkShell agent status",
|
|
1087
|
-
argsMode: "none",
|
|
1088
|
-
disabledReason,
|
|
1089
|
-
executionKind: "native",
|
|
1090
|
-
}),
|
|
1091
|
-
];
|
|
1049
|
+
return [];
|
|
1092
1050
|
}
|
|
1093
1051
|
|
|
1094
1052
|
function mergeCommands(...groups: Array<AgentCommandDescriptor[] | undefined>): AgentCommandDescriptor[] {
|
|
@@ -1265,7 +1223,7 @@ export class AgentWorkspaceProxy {
|
|
|
1265
1223
|
|
|
1266
1224
|
constructor(
|
|
1267
1225
|
private readonly input: {
|
|
1268
|
-
|
|
1226
|
+
hostDeviceId: string;
|
|
1269
1227
|
cwd: string;
|
|
1270
1228
|
availableProviders: AgentProvider[];
|
|
1271
1229
|
command?: string;
|
|
@@ -1293,7 +1251,7 @@ export class AgentWorkspaceProxy {
|
|
|
1293
1251
|
);
|
|
1294
1252
|
this.input.send(createEnvelope({
|
|
1295
1253
|
type: "agent.v2.conversation.list.result",
|
|
1296
|
-
|
|
1254
|
+
hostDeviceId: this.input.hostDeviceId,
|
|
1297
1255
|
payload: { conversations },
|
|
1298
1256
|
}));
|
|
1299
1257
|
break;
|
|
@@ -1541,17 +1499,14 @@ export class AgentWorkspaceProxy {
|
|
|
1541
1499
|
supportsPermission,
|
|
1542
1500
|
supportsPlan: enabled,
|
|
1543
1501
|
supportsCancel: enabled,
|
|
1544
|
-
models: runtimeCapabilities?.models
|
|
1545
|
-
defaultModel: runtimeCapabilities?.defaultModel
|
|
1502
|
+
models: runtimeCapabilities?.models,
|
|
1503
|
+
defaultModel: runtimeCapabilities?.defaultModel,
|
|
1546
1504
|
reasoningEfforts: supportsReasoningEffort
|
|
1547
|
-
? runtimeCapabilities?.reasoningEfforts ?? [
|
|
1505
|
+
? runtimeCapabilities?.reasoningEfforts ?? []
|
|
1548
1506
|
: [],
|
|
1549
|
-
permissionModes:
|
|
1507
|
+
permissionModes: [],
|
|
1550
1508
|
commands,
|
|
1551
|
-
modes: runtimeCapabilities?.modes ??
|
|
1552
|
-
{ id: "default", title: "Default", description: "Run normal implementation turns" },
|
|
1553
|
-
{ id: "plan", title: "Plan", description: "Discuss and produce an implementation plan first" },
|
|
1554
|
-
] : []),
|
|
1509
|
+
modes: runtimeCapabilities?.modes ?? [],
|
|
1555
1510
|
currentMode,
|
|
1556
1511
|
features: {
|
|
1557
1512
|
images: supportsImages,
|
|
@@ -1567,7 +1522,7 @@ export class AgentWorkspaceProxy {
|
|
|
1567
1522
|
const anyPermission = providers.some((p) => p.supportsPermission);
|
|
1568
1523
|
this.input.send(createEnvelope({
|
|
1569
1524
|
type: "agent.v2.capabilities",
|
|
1570
|
-
|
|
1525
|
+
hostDeviceId: this.input.hostDeviceId,
|
|
1571
1526
|
payload: {
|
|
1572
1527
|
enabled: anyEnabled,
|
|
1573
1528
|
provider: this.input.availableProviders[0] ?? "codex",
|
|
@@ -1629,7 +1584,7 @@ export class AgentWorkspaceProxy {
|
|
|
1629
1584
|
this.activeConversationId = existingConversation.id;
|
|
1630
1585
|
this.input.send(createEnvelope({
|
|
1631
1586
|
type: "agent.v2.conversation.opened",
|
|
1632
|
-
|
|
1587
|
+
hostDeviceId: this.input.hostDeviceId,
|
|
1633
1588
|
payload: {
|
|
1634
1589
|
conversation: existingConversation,
|
|
1635
1590
|
snapshot: this.timelines.get(existingConversation.id) ?? [],
|
|
@@ -1668,7 +1623,7 @@ export class AgentWorkspaceProxy {
|
|
|
1668
1623
|
this.timelines.set(conversation.id, this.timelines.get(conversation.id) ?? []);
|
|
1669
1624
|
this.input.send(createEnvelope({
|
|
1670
1625
|
type: "agent.v2.conversation.opened",
|
|
1671
|
-
|
|
1626
|
+
hostDeviceId: this.input.hostDeviceId,
|
|
1672
1627
|
payload: { conversation, snapshot: this.timelines.get(conversation.id) ?? [] },
|
|
1673
1628
|
}));
|
|
1674
1629
|
return conversation;
|
|
@@ -1720,7 +1675,7 @@ export class AgentWorkspaceProxy {
|
|
|
1720
1675
|
});
|
|
1721
1676
|
this.input.send(createEnvelope({
|
|
1722
1677
|
type: "agent.v2.conversation.opened",
|
|
1723
|
-
|
|
1678
|
+
hostDeviceId: this.input.hostDeviceId,
|
|
1724
1679
|
payload: { conversation, snapshot: this.timelines.get(conversation.id) ?? [] },
|
|
1725
1680
|
}));
|
|
1726
1681
|
return conversation;
|
|
@@ -2678,7 +2633,7 @@ export class AgentWorkspaceProxy {
|
|
|
2678
2633
|
this.upsertItem(conversationId, item);
|
|
2679
2634
|
this.input.send(createEnvelope({
|
|
2680
2635
|
type: "agent.v2.permission.request",
|
|
2681
|
-
|
|
2636
|
+
hostDeviceId: this.input.hostDeviceId,
|
|
2682
2637
|
payload: { conversationId, ...permission, item },
|
|
2683
2638
|
}));
|
|
2684
2639
|
|
|
@@ -2956,7 +2911,7 @@ export class AgentWorkspaceProxy {
|
|
|
2956
2911
|
const conversation = this.conversations.get(conversationId);
|
|
2957
2912
|
this.input.send(createEnvelope({
|
|
2958
2913
|
type: "agent.v2.event",
|
|
2959
|
-
|
|
2914
|
+
hostDeviceId: this.input.hostDeviceId,
|
|
2960
2915
|
payload: { conversationId, conversation, item },
|
|
2961
2916
|
}));
|
|
2962
2917
|
}
|
|
@@ -2964,7 +2919,7 @@ export class AgentWorkspaceProxy {
|
|
|
2964
2919
|
private emitConversation(conversation: AgentConversation): void {
|
|
2965
2920
|
this.input.send(createEnvelope({
|
|
2966
2921
|
type: "agent.v2.event",
|
|
2967
|
-
|
|
2922
|
+
hostDeviceId: this.input.hostDeviceId,
|
|
2968
2923
|
payload: { conversationId: conversation.id, conversation },
|
|
2969
2924
|
}));
|
|
2970
2925
|
}
|
|
@@ -3013,7 +2968,7 @@ export class AgentWorkspaceProxy {
|
|
|
3013
2968
|
: [...this.timelines.values()].flat();
|
|
3014
2969
|
this.input.send(createEnvelope({
|
|
3015
2970
|
type: "agent.v2.snapshot",
|
|
3016
|
-
|
|
2971
|
+
hostDeviceId: this.input.hostDeviceId,
|
|
3017
2972
|
payload: {
|
|
3018
2973
|
conversations,
|
|
3019
2974
|
activeConversationId: this.activeConversationId,
|