linkshell-cli 0.2.125 → 0.2.126

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/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 = TerminalProvider;
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 resolveClaudeProvider(input: {
69
- command?: string;
70
- args: string[];
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 resolveCodexProvider(input: {
74
+ export function resolveShellConfig(input: {
91
75
  command?: string;
92
- args: string[];
93
- }): ProviderConfig {
94
- const command = input.command ?? "codex";
95
- const resolved = which(command);
96
- if (!resolved) {
97
- throw new Error(
98
- `Codex CLI not found ("${command}"). Install it with: npm install -g @openai/codex`,
99
- );
100
- }
101
-
102
- if (!process.env.OPENAI_API_KEY) {
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: "codex",
89
+ provider: "shell",
110
90
  command: resolved,
111
- args: input.args,
91
+ args,
112
92
  env: { ...process.env },
113
93
  };
114
94
  }
115
95
 
116
- function resolveGeminiProvider(input: {
96
+ export function resolveProviderConfig(input: {
97
+ provider?: string;
117
98
  command?: string;
118
99
  args: string[];
119
100
  }): ProviderConfig {
120
- const command = input.command ?? "gemini";
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
- "[warn] GOOGLE_API_KEY / GEMINI_API_KEY not set Gemini may fail to authenticate\n",
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 CODEX_COMMAND_NAMES.map((name) => makeCommand({
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 [...builtIns, ...custom];
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[] {
@@ -1541,17 +1499,14 @@ export class AgentWorkspaceProxy {
1541
1499
  supportsPermission,
1542
1500
  supportsPlan: enabled,
1543
1501
  supportsCancel: enabled,
1544
- models: runtimeCapabilities?.models ?? [{ id: "default", label: "默认模型" }],
1545
- defaultModel: runtimeCapabilities?.defaultModel ?? "default",
1502
+ models: runtimeCapabilities?.models,
1503
+ defaultModel: runtimeCapabilities?.defaultModel,
1546
1504
  reasoningEfforts: supportsReasoningEffort
1547
- ? runtimeCapabilities?.reasoningEfforts ?? [...ALL_REASONING_EFFORTS]
1505
+ ? runtimeCapabilities?.reasoningEfforts ?? []
1548
1506
  : [],
1549
- permissionModes: supportsPermission ? [...ALL_PERMISSION_MODES] : [],
1507
+ permissionModes: [],
1550
1508
  commands,
1551
- modes: runtimeCapabilities?.modes ?? (provider === "codex" ? [
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,