linkshell-cli 0.3.24 → 0.3.26
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/upgrade.js +24 -9
- package/dist/cli/src/commands/upgrade.js.map +1 -1
- package/dist/cli/src/index.js +0 -0
- package/dist/cli/src/runtime/acp/agent-workspace.js +1 -0
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
- package/dist/cli/src/runtime/acp/codex-rpc-bridge.d.ts +5 -0
- package/dist/cli/src/runtime/acp/codex-rpc-bridge.js +54 -1
- package/dist/cli/src/runtime/acp/codex-rpc-bridge.js.map +1 -1
- package/dist/cli/src/runtime/acp/json-rpc.d.ts +1 -0
- package/dist/cli/src/runtime/acp/json-rpc.js +3 -1
- package/dist/cli/src/runtime/acp/json-rpc.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/src/commands/upgrade.ts +28 -8
- package/src/runtime/acp/agent-workspace.ts +1 -0
- package/src/runtime/acp/codex-rpc-bridge.ts +59 -1
- package/src/runtime/acp/json-rpc.ts +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linkshell-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.26",
|
|
4
4
|
"description": "Remote terminal bridge for Claude Code / Codex / Gemini / Copilot — control from your phone",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli/src/index.js",
|
|
@@ -29,14 +29,21 @@
|
|
|
29
29
|
"type": "git",
|
|
30
30
|
"url": "git+https://github.com/LiuTianjie/LinkShell.git"
|
|
31
31
|
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.json",
|
|
34
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
35
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
36
|
+
"dev": "tsx src/index.ts",
|
|
37
|
+
"test": "vitest run"
|
|
38
|
+
},
|
|
32
39
|
"dependencies": {
|
|
40
|
+
"@linkshell/gateway": "0.3.9",
|
|
41
|
+
"@linkshell/protocol": "0.3.11",
|
|
33
42
|
"commander": "^13.1.0",
|
|
34
43
|
"node-pty": "^1.0.0",
|
|
35
44
|
"qrcode-terminal": "^0.12.0",
|
|
36
45
|
"ws": "^8.18.1",
|
|
37
|
-
"zod": "^4.4.3"
|
|
38
|
-
"@linkshell/gateway": "0.3.9",
|
|
39
|
-
"@linkshell/protocol": "0.3.11"
|
|
46
|
+
"zod": "^4.4.3"
|
|
40
47
|
},
|
|
41
48
|
"optionalDependencies": {
|
|
42
49
|
"@anthropic-ai/claude-agent-sdk": "^0.2.128",
|
|
@@ -46,12 +53,5 @@
|
|
|
46
53
|
"@types/ws": "^8.5.14",
|
|
47
54
|
"tsx": "^4.19.3",
|
|
48
55
|
"vitest": "^3.2.1"
|
|
49
|
-
},
|
|
50
|
-
"scripts": {
|
|
51
|
-
"build": "tsc -p tsconfig.json",
|
|
52
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
53
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
54
|
-
"dev": "tsx src/index.ts",
|
|
55
|
-
"test": "vitest run"
|
|
56
56
|
}
|
|
57
|
-
}
|
|
57
|
+
}
|
package/src/commands/upgrade.ts
CHANGED
|
@@ -4,6 +4,9 @@ import { createRequire } from "node:module";
|
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
5
|
const pkg = require("../../../../package.json") as { version: string };
|
|
6
6
|
|
|
7
|
+
const NPM_UPGRADE_ATTEMPTS = 4;
|
|
8
|
+
const NPM_UPGRADE_RETRY_DELAY_MS = 8_000;
|
|
9
|
+
|
|
7
10
|
function isBrewInstall(): boolean {
|
|
8
11
|
try {
|
|
9
12
|
const out = execSync("brew list linkshell 2>/dev/null", {
|
|
@@ -31,6 +34,30 @@ async function fetchLatestVersion(): Promise<string | null> {
|
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
function sleep(ms: number): Promise<void> {
|
|
38
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function upgradeViaNpm(): Promise<void> {
|
|
42
|
+
for (let attempt = 1; attempt <= NPM_UPGRADE_ATTEMPTS; attempt++) {
|
|
43
|
+
try {
|
|
44
|
+
execSync("npm install -g linkshell-cli@latest", { stdio: "inherit", timeout: 120_000 });
|
|
45
|
+
process.stderr.write("\n \x1b[32m✓\x1b[0m Upgraded successfully.\n\n");
|
|
46
|
+
return;
|
|
47
|
+
} catch {
|
|
48
|
+
if (attempt >= NPM_UPGRADE_ATTEMPTS) break;
|
|
49
|
+
process.stderr.write(
|
|
50
|
+
`\n npm registry may still be syncing the new release. Retrying in ${NPM_UPGRADE_RETRY_DELAY_MS / 1000}s... (${attempt}/${NPM_UPGRADE_ATTEMPTS})\n`,
|
|
51
|
+
);
|
|
52
|
+
await sleep(NPM_UPGRADE_RETRY_DELAY_MS);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
process.stderr.write("\n \x1b[31m✗\x1b[0m npm upgrade failed. Try manually:\n");
|
|
57
|
+
process.stderr.write(" npm install -g linkshell-cli@latest\n\n");
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
34
61
|
export async function runUpgrade(): Promise<void> {
|
|
35
62
|
const current = pkg.version;
|
|
36
63
|
process.stderr.write(`\n Current version: ${current}\n`);
|
|
@@ -62,13 +89,6 @@ export async function runUpgrade(): Promise<void> {
|
|
|
62
89
|
}
|
|
63
90
|
} else {
|
|
64
91
|
process.stderr.write(" Upgrading via npm...\n");
|
|
65
|
-
|
|
66
|
-
execSync("npm install -g linkshell-cli@latest", { stdio: "inherit", timeout: 120_000 });
|
|
67
|
-
process.stderr.write("\n \x1b[32m✓\x1b[0m Upgraded successfully.\n\n");
|
|
68
|
-
} catch {
|
|
69
|
-
process.stderr.write("\n \x1b[31m✗\x1b[0m npm upgrade failed. Try manually:\n");
|
|
70
|
-
process.stderr.write(" npm install -g linkshell-cli@latest\n\n");
|
|
71
|
-
process.exitCode = 1;
|
|
72
|
-
}
|
|
92
|
+
await upgradeViaNpm();
|
|
73
93
|
}
|
|
74
94
|
}
|
|
@@ -2497,6 +2497,7 @@ export class AgentWorkspaceProxy {
|
|
|
2497
2497
|
if (
|
|
2498
2498
|
method.startsWith("account/") ||
|
|
2499
2499
|
method.startsWith("mcpServer/startupStatus/") ||
|
|
2500
|
+
method.startsWith("remoteControl/") ||
|
|
2500
2501
|
method === "thread/status/changed" ||
|
|
2501
2502
|
method === "thread/tokenUsage/updated" ||
|
|
2502
2503
|
method === "serverRequest/resolved" ||
|
|
@@ -5,6 +5,8 @@ import { resolveCodexAppServerCommands, type AgentCommandConfig } from "./provid
|
|
|
5
5
|
|
|
6
6
|
const CODEX_PROXY_STARTUP_GRACE_MS = 250;
|
|
7
7
|
|
|
8
|
+
type JsonRpcId = string | number;
|
|
9
|
+
|
|
8
10
|
export class CodexRpcBridge {
|
|
9
11
|
private transport: JsonRpcStdioTransport | undefined;
|
|
10
12
|
private transportReady = false;
|
|
@@ -13,6 +15,7 @@ export class CodexRpcBridge {
|
|
|
13
15
|
private candidateIndex = 0;
|
|
14
16
|
private startupTimer: ReturnType<typeof setTimeout> | undefined;
|
|
15
17
|
private queuedMessages: JsonRpcMessage[] = [];
|
|
18
|
+
private inFlightRequests = new Map<JsonRpcId, JsonRpcMessage>();
|
|
16
19
|
|
|
17
20
|
constructor(
|
|
18
21
|
private readonly input: {
|
|
@@ -32,6 +35,7 @@ export class CodexRpcBridge {
|
|
|
32
35
|
this.queuedMessages.push(jsonMessage);
|
|
33
36
|
return;
|
|
34
37
|
}
|
|
38
|
+
this.rememberRequest(jsonMessage);
|
|
35
39
|
this.transport?.send(jsonMessage);
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -46,6 +50,7 @@ export class CodexRpcBridge {
|
|
|
46
50
|
this.transportReady = false;
|
|
47
51
|
this.starting = false;
|
|
48
52
|
this.queuedMessages = [];
|
|
53
|
+
this.inFlightRequests.clear();
|
|
49
54
|
this.stopping = false;
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -80,6 +85,7 @@ export class CodexRpcBridge {
|
|
|
80
85
|
() => ({}),
|
|
81
86
|
(message) => this.handleTransportExit(message),
|
|
82
87
|
(message) => {
|
|
88
|
+
this.forgetRequest(message);
|
|
83
89
|
this.input.send(createEnvelope({
|
|
84
90
|
type: "agent.codex.rpc",
|
|
85
91
|
hostDeviceId: this.input.hostDeviceId,
|
|
@@ -87,7 +93,11 @@ export class CodexRpcBridge {
|
|
|
87
93
|
}));
|
|
88
94
|
return true;
|
|
89
95
|
},
|
|
90
|
-
{
|
|
96
|
+
{
|
|
97
|
+
includeJsonrpc: false,
|
|
98
|
+
verbose: this.input.verbose,
|
|
99
|
+
onStderrData: (message) => this.handleTransportStderr(message),
|
|
100
|
+
},
|
|
91
101
|
);
|
|
92
102
|
this.transport.start(this.input.cwd);
|
|
93
103
|
if (this.input.verbose) {
|
|
@@ -120,12 +130,14 @@ export class CodexRpcBridge {
|
|
|
120
130
|
return;
|
|
121
131
|
}
|
|
122
132
|
this.failQueued(message);
|
|
133
|
+
this.failInFlight(message);
|
|
123
134
|
this.emitError(message);
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
private flushQueued(): void {
|
|
127
138
|
const messages = this.queuedMessages.splice(0);
|
|
128
139
|
for (const message of messages) {
|
|
140
|
+
this.rememberRequest(message);
|
|
129
141
|
this.transport?.send(message);
|
|
130
142
|
}
|
|
131
143
|
}
|
|
@@ -160,4 +172,50 @@ export class CodexRpcBridge {
|
|
|
160
172
|
},
|
|
161
173
|
}));
|
|
162
174
|
}
|
|
175
|
+
|
|
176
|
+
private handleTransportStderr(message: string): void {
|
|
177
|
+
if (this.input.verbose) {
|
|
178
|
+
process.stderr.write(`[agent:codex-rpc] stderr ignored while app-server is running: ${message}\n`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private rememberRequest(message: JsonRpcMessage): void {
|
|
183
|
+
if (!isJsonRpcRequest(message)) return;
|
|
184
|
+
this.inFlightRequests.set(message.id, message);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private forgetRequest(message: JsonRpcMessage): void {
|
|
188
|
+
if (!isJsonRpcResponse(message)) return;
|
|
189
|
+
this.inFlightRequests.delete(message.id);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private failInFlight(message: string): void {
|
|
193
|
+
const requests = [...this.inFlightRequests.values()];
|
|
194
|
+
this.inFlightRequests.clear();
|
|
195
|
+
for (const item of requests) {
|
|
196
|
+
if (!isJsonRpcRequest(item)) continue;
|
|
197
|
+
this.input.send(createEnvelope({
|
|
198
|
+
type: "agent.codex.rpc",
|
|
199
|
+
hostDeviceId: this.input.hostDeviceId,
|
|
200
|
+
payload: {
|
|
201
|
+
jsonrpc: "2.0",
|
|
202
|
+
id: item.id,
|
|
203
|
+
error: { code: -32000, message },
|
|
204
|
+
},
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function isJsonRpcRequest(message: JsonRpcMessage): message is JsonRpcMessage & { id: JsonRpcId; method: string } {
|
|
211
|
+
return "id" in message &&
|
|
212
|
+
"method" in message &&
|
|
213
|
+
(typeof message.id === "string" || typeof message.id === "number") &&
|
|
214
|
+
typeof message.method === "string";
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function isJsonRpcResponse(message: JsonRpcMessage): message is JsonRpcMessage & { id: JsonRpcId } {
|
|
218
|
+
return "id" in message &&
|
|
219
|
+
(typeof message.id === "string" || typeof message.id === "number") &&
|
|
220
|
+
("result" in message || "error" in message);
|
|
163
221
|
}
|
|
@@ -26,6 +26,7 @@ export type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotificat
|
|
|
26
26
|
interface JsonRpcTransportOptions {
|
|
27
27
|
includeJsonrpc?: boolean;
|
|
28
28
|
verbose?: boolean;
|
|
29
|
+
onStderrData?: (message: string) => void;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export class JsonRpcStdioTransport {
|
|
@@ -64,7 +65,10 @@ export class JsonRpcStdioTransport {
|
|
|
64
65
|
this.child.stdout.on("data", (chunk: string) => this.read(chunk));
|
|
65
66
|
this.child.stderr.on("data", (chunk: string) => {
|
|
66
67
|
const trimmed = chunk.trim();
|
|
67
|
-
if (trimmed)
|
|
68
|
+
if (trimmed) {
|
|
69
|
+
process.stderr.write(`[agent:stderr] ${trimmed}\n`);
|
|
70
|
+
this.options.onStderrData?.(trimmed);
|
|
71
|
+
}
|
|
68
72
|
});
|
|
69
73
|
this.child.on("error", (error) => this.failAll(error.message));
|
|
70
74
|
this.child.on("exit", (code, signal) => {
|