linkshell-cli 0.8.2 → 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 +4 -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/screen.d.ts +10 -0
- package/dist/cli/src/commands/screen.js +82 -74
- package/dist/cli/src/commands/screen.js.map +1 -1
- package/dist/cli/src/commands/setup.d.ts +11 -1
- package/dist/cli/src/commands/setup.js +102 -38
- package/dist/cli/src/commands/setup.js.map +1 -1
- package/dist/cli/src/index.js +28 -338
- 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 +112 -27
- package/dist/host/src/input.js +214 -144
- package/dist/host/src/input.js.map +1 -1
- package/dist/host/src/rpc/server.js +1 -1
- package/dist/host/src/rpc/server.js.map +1 -1
- package/dist/host/src/screen-pacer.d.ts +25 -11
- package/dist/host/src/screen-pacer.js +28 -6
- package/dist/host/src/screen-pacer.js.map +1 -1
- package/dist/host/src/screen-viewer.d.ts +5 -0
- package/dist/host/src/screen-viewer.js +1231 -55
- package/dist/host/src/screen-viewer.js.map +1 -1
- package/dist/host/src/screen.d.ts +19 -10
- package/dist/host/src/screen.js +314 -108
- package/dist/host/src/screen.js.map +1 -1
- package/dist/host/src/terminals.d.ts +1 -1
- package/dist/host/src/terminals.js +1 -0
- package/dist/host/src/terminals.js.map +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 +6 -15
- package/src/commands/gateway.ts +31 -0
- package/src/commands/host.ts +15 -2
- package/src/commands/screen.ts +78 -72
- package/src/commands/setup.ts +102 -60
- package/src/index.ts +29 -433
- 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
|
@@ -312,7 +312,7 @@ export declare const sessionConfigOptionSchema: z.ZodObject<{
|
|
|
312
312
|
}[];
|
|
313
313
|
id: string;
|
|
314
314
|
name: string;
|
|
315
|
-
category: "
|
|
315
|
+
category: "mode" | "other" | "model" | "effort";
|
|
316
316
|
current: string;
|
|
317
317
|
}, {
|
|
318
318
|
values: {
|
|
@@ -323,7 +323,7 @@ export declare const sessionConfigOptionSchema: z.ZodObject<{
|
|
|
323
323
|
id: string;
|
|
324
324
|
name: string;
|
|
325
325
|
current: string;
|
|
326
|
-
category?: "
|
|
326
|
+
category?: "mode" | "other" | "model" | "effort" | undefined;
|
|
327
327
|
}>;
|
|
328
328
|
export type SessionConfigOption = z.infer<typeof sessionConfigOptionSchema>;
|
|
329
329
|
export declare const sessionUpdateSchema: z.ZodDiscriminatedUnion<"sessionUpdate", [z.ZodObject<{
|
|
@@ -795,7 +795,7 @@ export declare const sessionUpdateSchema: z.ZodDiscriminatedUnion<"sessionUpdate
|
|
|
795
795
|
}>]>>;
|
|
796
796
|
}, "strip", z.ZodTypeAny, {
|
|
797
797
|
status: "failed" | "pending" | "in_progress" | "completed";
|
|
798
|
-
kind: "
|
|
798
|
+
kind: "search" | "read" | "edit" | "delete" | "move" | "execute" | "think" | "fetch" | "other";
|
|
799
799
|
title: string;
|
|
800
800
|
toolCallId: string;
|
|
801
801
|
sessionUpdate: "tool_call";
|
|
@@ -868,7 +868,7 @@ export declare const sessionUpdateSchema: z.ZodDiscriminatedUnion<"sessionUpdate
|
|
|
868
868
|
}[] | undefined;
|
|
869
869
|
}, {
|
|
870
870
|
status: "failed" | "pending" | "in_progress" | "completed";
|
|
871
|
-
kind: "
|
|
871
|
+
kind: "search" | "read" | "edit" | "delete" | "move" | "execute" | "think" | "fetch" | "other";
|
|
872
872
|
title: string;
|
|
873
873
|
toolCallId: string;
|
|
874
874
|
sessionUpdate: "tool_call";
|
|
@@ -1430,7 +1430,7 @@ export declare const sessionUpdateSchema: z.ZodDiscriminatedUnion<"sessionUpdate
|
|
|
1430
1430
|
}[];
|
|
1431
1431
|
id: string;
|
|
1432
1432
|
name: string;
|
|
1433
|
-
category: "
|
|
1433
|
+
category: "mode" | "other" | "model" | "effort";
|
|
1434
1434
|
current: string;
|
|
1435
1435
|
}, {
|
|
1436
1436
|
values: {
|
|
@@ -1441,7 +1441,7 @@ export declare const sessionUpdateSchema: z.ZodDiscriminatedUnion<"sessionUpdate
|
|
|
1441
1441
|
id: string;
|
|
1442
1442
|
name: string;
|
|
1443
1443
|
current: string;
|
|
1444
|
-
category?: "
|
|
1444
|
+
category?: "mode" | "other" | "model" | "effort" | undefined;
|
|
1445
1445
|
}>, "many">;
|
|
1446
1446
|
}, "strip", z.ZodTypeAny, {
|
|
1447
1447
|
options: {
|
|
@@ -1452,7 +1452,7 @@ export declare const sessionUpdateSchema: z.ZodDiscriminatedUnion<"sessionUpdate
|
|
|
1452
1452
|
}[];
|
|
1453
1453
|
id: string;
|
|
1454
1454
|
name: string;
|
|
1455
|
-
category: "
|
|
1455
|
+
category: "mode" | "other" | "model" | "effort";
|
|
1456
1456
|
current: string;
|
|
1457
1457
|
}[];
|
|
1458
1458
|
sessionUpdate: "ls_config";
|
|
@@ -1466,7 +1466,7 @@ export declare const sessionUpdateSchema: z.ZodDiscriminatedUnion<"sessionUpdate
|
|
|
1466
1466
|
id: string;
|
|
1467
1467
|
name: string;
|
|
1468
1468
|
current: string;
|
|
1469
|
-
category?: "
|
|
1469
|
+
category?: "mode" | "other" | "model" | "effort" | undefined;
|
|
1470
1470
|
}[];
|
|
1471
1471
|
sessionUpdate: "ls_config";
|
|
1472
1472
|
}>, z.ZodObject<{
|
|
@@ -2174,7 +2174,7 @@ export declare const sessionEventSchema: z.ZodObject<{
|
|
|
2174
2174
|
}>]>>;
|
|
2175
2175
|
}, "strip", z.ZodTypeAny, {
|
|
2176
2176
|
status: "failed" | "pending" | "in_progress" | "completed";
|
|
2177
|
-
kind: "
|
|
2177
|
+
kind: "search" | "read" | "edit" | "delete" | "move" | "execute" | "think" | "fetch" | "other";
|
|
2178
2178
|
title: string;
|
|
2179
2179
|
toolCallId: string;
|
|
2180
2180
|
sessionUpdate: "tool_call";
|
|
@@ -2247,7 +2247,7 @@ export declare const sessionEventSchema: z.ZodObject<{
|
|
|
2247
2247
|
}[] | undefined;
|
|
2248
2248
|
}, {
|
|
2249
2249
|
status: "failed" | "pending" | "in_progress" | "completed";
|
|
2250
|
-
kind: "
|
|
2250
|
+
kind: "search" | "read" | "edit" | "delete" | "move" | "execute" | "think" | "fetch" | "other";
|
|
2251
2251
|
title: string;
|
|
2252
2252
|
toolCallId: string;
|
|
2253
2253
|
sessionUpdate: "tool_call";
|
|
@@ -2809,7 +2809,7 @@ export declare const sessionEventSchema: z.ZodObject<{
|
|
|
2809
2809
|
}[];
|
|
2810
2810
|
id: string;
|
|
2811
2811
|
name: string;
|
|
2812
|
-
category: "
|
|
2812
|
+
category: "mode" | "other" | "model" | "effort";
|
|
2813
2813
|
current: string;
|
|
2814
2814
|
}, {
|
|
2815
2815
|
values: {
|
|
@@ -2820,7 +2820,7 @@ export declare const sessionEventSchema: z.ZodObject<{
|
|
|
2820
2820
|
id: string;
|
|
2821
2821
|
name: string;
|
|
2822
2822
|
current: string;
|
|
2823
|
-
category?: "
|
|
2823
|
+
category?: "mode" | "other" | "model" | "effort" | undefined;
|
|
2824
2824
|
}>, "many">;
|
|
2825
2825
|
}, "strip", z.ZodTypeAny, {
|
|
2826
2826
|
options: {
|
|
@@ -2831,7 +2831,7 @@ export declare const sessionEventSchema: z.ZodObject<{
|
|
|
2831
2831
|
}[];
|
|
2832
2832
|
id: string;
|
|
2833
2833
|
name: string;
|
|
2834
|
-
category: "
|
|
2834
|
+
category: "mode" | "other" | "model" | "effort";
|
|
2835
2835
|
current: string;
|
|
2836
2836
|
}[];
|
|
2837
2837
|
sessionUpdate: "ls_config";
|
|
@@ -2845,7 +2845,7 @@ export declare const sessionEventSchema: z.ZodObject<{
|
|
|
2845
2845
|
id: string;
|
|
2846
2846
|
name: string;
|
|
2847
2847
|
current: string;
|
|
2848
|
-
category?: "
|
|
2848
|
+
category?: "mode" | "other" | "model" | "effort" | undefined;
|
|
2849
2849
|
}[];
|
|
2850
2850
|
sessionUpdate: "ls_config";
|
|
2851
2851
|
}>, z.ZodObject<{
|
|
@@ -3133,7 +3133,7 @@ export declare const sessionEventSchema: z.ZodObject<{
|
|
|
3133
3133
|
parentToolCallId?: string | undefined;
|
|
3134
3134
|
} | {
|
|
3135
3135
|
status: "failed" | "pending" | "in_progress" | "completed";
|
|
3136
|
-
kind: "
|
|
3136
|
+
kind: "search" | "read" | "edit" | "delete" | "move" | "execute" | "think" | "fetch" | "other";
|
|
3137
3137
|
title: string;
|
|
3138
3138
|
toolCallId: string;
|
|
3139
3139
|
sessionUpdate: "tool_call";
|
|
@@ -3312,7 +3312,7 @@ export declare const sessionEventSchema: z.ZodObject<{
|
|
|
3312
3312
|
}[];
|
|
3313
3313
|
id: string;
|
|
3314
3314
|
name: string;
|
|
3315
|
-
category: "
|
|
3315
|
+
category: "mode" | "other" | "model" | "effort";
|
|
3316
3316
|
current: string;
|
|
3317
3317
|
}[];
|
|
3318
3318
|
sessionUpdate: "ls_config";
|
|
@@ -3433,7 +3433,7 @@ export declare const sessionEventSchema: z.ZodObject<{
|
|
|
3433
3433
|
parentToolCallId?: string | undefined;
|
|
3434
3434
|
} | {
|
|
3435
3435
|
status: "failed" | "pending" | "in_progress" | "completed";
|
|
3436
|
-
kind: "
|
|
3436
|
+
kind: "search" | "read" | "edit" | "delete" | "move" | "execute" | "think" | "fetch" | "other";
|
|
3437
3437
|
title: string;
|
|
3438
3438
|
toolCallId: string;
|
|
3439
3439
|
sessionUpdate: "tool_call";
|
|
@@ -3613,7 +3613,7 @@ export declare const sessionEventSchema: z.ZodObject<{
|
|
|
3613
3613
|
id: string;
|
|
3614
3614
|
name: string;
|
|
3615
3615
|
current: string;
|
|
3616
|
-
category?: "
|
|
3616
|
+
category?: "mode" | "other" | "model" | "effort" | undefined;
|
|
3617
3617
|
}[];
|
|
3618
3618
|
sessionUpdate: "ls_config";
|
|
3619
3619
|
} | {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linkshell-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Follow, steer and approve the coding agents on your computer (Claude Code, Codex, Gemini, Copilot and more) from your phone",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"dist",
|
|
12
12
|
"src",
|
|
13
13
|
"scripts",
|
|
14
|
-
"web",
|
|
15
14
|
"README.md",
|
|
16
15
|
"LICENSE"
|
|
17
16
|
],
|
|
@@ -32,21 +31,13 @@
|
|
|
32
31
|
},
|
|
33
32
|
"dependencies": {
|
|
34
33
|
"commander": "^13.1.0",
|
|
35
|
-
"node-pty": "^1.
|
|
34
|
+
"node-pty": "^1.1.0",
|
|
36
35
|
"qrcode-terminal": "^0.12.0",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"@linkshell/
|
|
40
|
-
"@linkshell/protocol": "0.4.10",
|
|
41
|
-
"@linkshell/wire": "0.2.1",
|
|
42
|
-
"@linkshell/host": "0.3.2"
|
|
43
|
-
},
|
|
44
|
-
"optionalDependencies": {
|
|
45
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.128",
|
|
46
|
-
"werift": "^0.22.9"
|
|
36
|
+
"@linkshell/gateway": "0.6.0",
|
|
37
|
+
"@linkshell/host": "0.4.0",
|
|
38
|
+
"@linkshell/wire": "0.2.1"
|
|
47
39
|
},
|
|
48
40
|
"devDependencies": {
|
|
49
|
-
"@types/ws": "^8.5.14",
|
|
50
41
|
"tsx": "^4.19.3",
|
|
51
42
|
"vitest": "^3.2.1"
|
|
52
43
|
},
|
|
@@ -54,7 +45,7 @@
|
|
|
54
45
|
"node": ">=22.13"
|
|
55
46
|
},
|
|
56
47
|
"scripts": {
|
|
57
|
-
"build": "
|
|
48
|
+
"build": "rm -rf dist *.tsbuildinfo && tsc -p tsconfig.json",
|
|
58
49
|
"postinstall": "node scripts/postinstall.mjs",
|
|
59
50
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
60
51
|
"dev": "tsx src/index.ts",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import * as daemon from "../utils/daemon.js";
|
|
3
|
+
import { assertHostRuntime, silenceSqliteWarning } from "./host.js";
|
|
4
|
+
|
|
5
|
+
/** Runs a gateway in this process until SIGINT/SIGTERM. */
|
|
6
|
+
export async function runGatewayForeground(version: string, port: number): Promise<void> {
|
|
7
|
+
// The relay keeps its pairings in node:sqlite, like the host.
|
|
8
|
+
assertHostRuntime();
|
|
9
|
+
silenceSqliteWarning();
|
|
10
|
+
const { startGateway } = await import("@linkshell/gateway");
|
|
11
|
+
// Pairings live here; keeping this file keeps phones paired.
|
|
12
|
+
const databasePath = join(daemon.linkshellDir(), "relay.db");
|
|
13
|
+
const gateway = await startGateway({ port, databasePath });
|
|
14
|
+
daemon.savePid("gateway", process.pid);
|
|
15
|
+
|
|
16
|
+
process.stderr.write(`\n LinkShell Gateway ${version}\n`);
|
|
17
|
+
process.stderr.write(` Listening on http://0.0.0.0:${gateway.port}\n`);
|
|
18
|
+
process.stderr.write(` PID: ${process.pid}\n`);
|
|
19
|
+
process.stderr.write(` Pairings: ${databasePath}\n\n`);
|
|
20
|
+
process.stderr.write(` Computers: linkshell host --gateway ws://this-server:${gateway.port}\n`);
|
|
21
|
+
process.stderr.write(` Health check: curl http://this-server:${gateway.port}/healthz\n\n`);
|
|
22
|
+
|
|
23
|
+
const shutdown = async () => {
|
|
24
|
+
process.stderr.write("[gateway] shutting down...\n");
|
|
25
|
+
daemon.removePid("gateway");
|
|
26
|
+
await gateway.close();
|
|
27
|
+
process.exit(0);
|
|
28
|
+
};
|
|
29
|
+
process.on("SIGINT", shutdown);
|
|
30
|
+
process.on("SIGTERM", shutdown);
|
|
31
|
+
}
|
package/src/commands/host.ts
CHANGED
|
@@ -19,11 +19,19 @@ export function silenceSqliteWarning(): void {
|
|
|
19
19
|
interface HostConfig {
|
|
20
20
|
/** v2 gateway base URL (ws:// or wss://), or "off". Unset: the official gateway once logged in. */
|
|
21
21
|
gateway?: string;
|
|
22
|
+
/** Set once `linkshell setup` has run here (by itself on the first start, or by hand). */
|
|
23
|
+
setup?: "done";
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/** LinkShell's own gateway: a Pro account's computers are reachable there. */
|
|
25
27
|
export const OFFICIAL_GATEWAY = "wss://gateway.itool.tech";
|
|
26
28
|
|
|
29
|
+
/** Whether two gateway addresses name the same one, however they were typed (scheme, trailing slash). */
|
|
30
|
+
export function sameGateway(a: string | undefined, b: string): boolean {
|
|
31
|
+
const key = (url: string) => url.trim().replace(/^http/i, "ws").replace(/\/+$/, "").toLowerCase();
|
|
32
|
+
return a !== undefined && key(a) === key(b);
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
/**
|
|
28
36
|
* Where devices reach this computer from outside the LAN: LINKSHELL_GATEWAY,
|
|
29
37
|
* else the saved choice, else the official gateway when logged in.
|
|
@@ -31,7 +39,7 @@ export const OFFICIAL_GATEWAY = "wss://gateway.itool.tech";
|
|
|
31
39
|
export function resolveGateway(home: string): string | undefined {
|
|
32
40
|
const chosen = process.env.LINKSHELL_GATEWAY || readHostConfig(home).gateway;
|
|
33
41
|
if (chosen === "off") return undefined;
|
|
34
|
-
// config.json
|
|
42
|
+
// A config.json written by 1.x holds a gateway URL ending in /ws.
|
|
35
43
|
if (chosen) return chosen.replace(/\/ws\/?$/, "");
|
|
36
44
|
return isLoggedIn() ? OFFICIAL_GATEWAY : undefined;
|
|
37
45
|
}
|
|
@@ -95,7 +103,12 @@ export async function runHostForeground(version: string): Promise<void> {
|
|
|
95
103
|
version,
|
|
96
104
|
env,
|
|
97
105
|
log,
|
|
98
|
-
gateway: {
|
|
106
|
+
gateway: {
|
|
107
|
+
url: () => resolveGateway(home),
|
|
108
|
+
// The account is LinkShell's, so its token goes to LinkShell's gateway and to no other: a
|
|
109
|
+
// gateway of one's own has no accounts, and would refuse a computer that offered one.
|
|
110
|
+
token: async () => (sameGateway(resolveGateway(home), OFFICIAL_GATEWAY) ? ((await getValidToken()) ?? undefined) : undefined),
|
|
111
|
+
},
|
|
99
112
|
tcpPort: devPort,
|
|
100
113
|
claudeCommand: process.env.LINKSHELL_CLAUDE_COMMAND || undefined,
|
|
101
114
|
claudeAdapter: adapter ? { command: adapter, args: [] } : undefined,
|
package/src/commands/screen.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { ensureHostRunning, hostRuntimeOk, withRunningHost } from "./host.js";
|
|
|
7
7
|
|
|
8
8
|
export interface ScreenAccess {
|
|
9
9
|
supported: boolean;
|
|
10
|
+
/** Whether what captures the screen is there: LinkShell.app on a Mac (installed with the CLI), ffmpeg on Linux. */
|
|
10
11
|
ffmpeg: boolean;
|
|
11
12
|
recording: boolean | null;
|
|
12
13
|
control: boolean | null;
|
|
@@ -39,8 +40,8 @@ export function screenReady(access: ScreenAccess): boolean {
|
|
|
39
40
|
export function describeScreen(access: ScreenAccess | "old" | undefined): string | undefined {
|
|
40
41
|
if (!access) return undefined;
|
|
41
42
|
if (access === "old") return "restart the host to check (linkshell host stop && linkshell host --daemon)";
|
|
42
|
-
if (!access.supported) return "not available on this system";
|
|
43
|
-
if (!access.ffmpeg) return
|
|
43
|
+
if (!access.supported) return process.platform === "darwin" ? "needs a Mac with Apple silicon" : "not available on this system";
|
|
44
|
+
if (!access.ffmpeg) return process.platform === "darwin" ? MISSING_APP : "needs ffmpeg (install it with your package manager)";
|
|
44
45
|
if (process.platform !== "darwin") return "can be watched (controlling it needs macOS)";
|
|
45
46
|
if (access.recording === null || access.control === null) return `can't tell${access.problem ? `: ${access.problem}` : ""}`;
|
|
46
47
|
if (access.recording && access.control) return "can be watched and controlled from the phone";
|
|
@@ -48,96 +49,101 @@ export function describeScreen(access: ScreenAccess | "old" | undefined): string
|
|
|
48
49
|
return `${missing} isn't allowed yet — set it up with: linkshell screen`;
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
/** A Mac without LinkShell.app: the optional package that carries it wasn't installed (npm run with --omit=optional, or a failed download). */
|
|
53
|
+
const MISSING_APP = "LinkShell.app is missing — reinstall with: npm install -g linkshell-cli";
|
|
54
|
+
|
|
51
55
|
const PERMISSIONS = {
|
|
52
|
-
recording:
|
|
53
|
-
|
|
54
|
-
where: "Screen & System Audio Recording (录屏与系统录音)",
|
|
55
|
-
},
|
|
56
|
-
control: {
|
|
57
|
-
label: "Controlling it",
|
|
58
|
-
where: "Accessibility (辅助功能)",
|
|
59
|
-
},
|
|
56
|
+
recording: "Watching the screen",
|
|
57
|
+
control: "Controlling it",
|
|
60
58
|
} as const;
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
60
|
+
type Print = (line?: string) => void;
|
|
61
|
+
const row = (out: Print, ok: boolean, label: string, detail: string) => out(` ${ok ? green("✓") : red("✗")} ${label.padEnd(22)} ${detail}`);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Gets this computer's screen ready for the phone, as far as it can be, saying
|
|
65
|
+
* what it finds. On a Mac the two permissions are LinkShell.app's: its own
|
|
66
|
+
* window comes up on this computer, takes the user to each switch and ticks
|
|
67
|
+
* them off, and this waits beside it. True when everything is in place.
|
|
68
|
+
*/
|
|
69
|
+
export async function setUpScreen(out: Print, interactive: boolean): Promise<boolean> {
|
|
72
70
|
let access = await screenAccess();
|
|
73
71
|
if (!access) {
|
|
74
|
-
row(false, "Host", "not running — start it with: linkshell host --daemon");
|
|
75
|
-
|
|
76
|
-
return;
|
|
72
|
+
row(out, false, "Host", "not running — start it with: linkshell host --daemon");
|
|
73
|
+
return false;
|
|
77
74
|
}
|
|
78
75
|
if (access === "old") {
|
|
79
|
-
row(false, "Host", "is older than this CLI — restart it (stops running agent turns):");
|
|
80
|
-
out(" linkshell host stop && linkshell host --daemon
|
|
81
|
-
|
|
82
|
-
return;
|
|
76
|
+
row(out, false, "Host", "is older than this CLI — restart it (stops running agent turns):");
|
|
77
|
+
out(" linkshell host stop && linkshell host --daemon");
|
|
78
|
+
return false;
|
|
83
79
|
}
|
|
84
80
|
if (!access.supported) {
|
|
85
|
-
row(false, "Screen", "not available on this system");
|
|
86
|
-
|
|
87
|
-
return;
|
|
81
|
+
row(out, false, "Screen", process.platform === "darwin" ? "needs a Mac with Apple silicon" : "not available on this system");
|
|
82
|
+
return false;
|
|
88
83
|
}
|
|
89
|
-
row(access.ffmpeg, "ffmpeg", access.ffmpeg ? "found" : `not installed — ${process.platform === "darwin" ? "brew install ffmpeg" : "install it with your package manager"}, then run this again`);
|
|
90
84
|
if (process.platform !== "darwin") {
|
|
91
|
-
out
|
|
92
|
-
if (
|
|
93
|
-
return;
|
|
85
|
+
row(out, access.ffmpeg, "ffmpeg", access.ffmpeg ? "found" : "not installed — install it with your package manager, then run this again");
|
|
86
|
+
if (access.ffmpeg) out(dim(" The screen can be watched from the phone; controlling it needs macOS."));
|
|
87
|
+
return access.ffmpeg;
|
|
88
|
+
}
|
|
89
|
+
if (!access.ffmpeg) {
|
|
90
|
+
row(out, false, "LinkShell.app", "missing from this installation — reinstall with: npm install -g linkshell-cli");
|
|
91
|
+
return false;
|
|
94
92
|
}
|
|
95
93
|
if (access.recording === null || access.control === null) {
|
|
96
|
-
row(false, "Permissions", access.problem ?? "could not be checked");
|
|
97
|
-
|
|
98
|
-
return;
|
|
94
|
+
row(out, false, "Permissions", access.problem ?? "could not be checked");
|
|
95
|
+
return false;
|
|
99
96
|
}
|
|
100
97
|
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
if (!interactive) {
|
|
110
|
-
row(false, permission.label, "not allowed yet");
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
// The system's own question comes up on this computer, and its settings open at the switch.
|
|
114
|
-
await screenAccess(true);
|
|
115
|
-
out(` ${red("✗")} ${permission.label.padEnd(22)} not allowed yet`);
|
|
116
|
-
out(` System Settings has opened at Privacy & Security › ${permission.where}.`);
|
|
117
|
-
out(` Turn on the switch for "${app}" there.${kind === "recording" ? " If macOS offers to quit and reopen it, later is fine." : ""}`);
|
|
118
|
-
out(dim(" Waiting for the switch… (Ctrl+C to stop; run this again any time)"));
|
|
119
|
-
const deadline = Date.now() + 10 * 60_000;
|
|
120
|
-
let allowed = false;
|
|
121
|
-
while (!allowed && Date.now() < deadline) {
|
|
122
|
-
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
123
|
-
const now = await screenAccess().catch(() => undefined);
|
|
124
|
-
if (now && now !== "old") {
|
|
125
|
-
access = now;
|
|
126
|
-
allowed = now[kind] === true;
|
|
98
|
+
const kinds = ["recording", "control"] as const;
|
|
99
|
+
const said = new Set<string>();
|
|
100
|
+
const tell = (now: ScreenAccess) => {
|
|
101
|
+
for (const kind of kinds) {
|
|
102
|
+
if (now[kind] && !said.has(kind)) {
|
|
103
|
+
said.add(kind);
|
|
104
|
+
row(out, true, PERMISSIONS[kind], "allowed");
|
|
127
105
|
}
|
|
128
106
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
107
|
+
};
|
|
108
|
+
tell(access);
|
|
109
|
+
if (screenReady(access)) return true;
|
|
110
|
+
if (!interactive) {
|
|
111
|
+
for (const kind of kinds) if (!access[kind]) row(out, false, PERMISSIONS[kind], "not allowed yet");
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// The app's own window: it has the system ask, opens the settings at each switch, and shows what is done.
|
|
116
|
+
await screenAccess(true);
|
|
117
|
+
const missing = kinds.filter((kind) => !(access as ScreenAccess)[kind]).length;
|
|
118
|
+
out(` A LinkShell window has opened on this Mac: turn on the ${missing === 2 ? "two switches" : "switch"} it shows.`);
|
|
119
|
+
out(dim(" Waiting… (Ctrl+C to stop; `linkshell screen` picks up where this left off)"));
|
|
120
|
+
const deadline = Date.now() + 10 * 60_000;
|
|
121
|
+
while (Date.now() < deadline) {
|
|
122
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
123
|
+
const now = await screenAccess().catch(() => undefined);
|
|
124
|
+
if (!now || now === "old") continue;
|
|
125
|
+
access = now;
|
|
126
|
+
tell(now);
|
|
127
|
+
if (screenReady(now)) return true;
|
|
135
128
|
}
|
|
129
|
+
for (const kind of kinds) if (!access[kind]) row(out, false, PERMISSIONS[kind], "still not allowed");
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
136
132
|
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
export async function runScreenSetup(options: { check?: boolean }): Promise<void> {
|
|
134
|
+
const out: Print = (line = "") => process.stdout.write(`${line}\n`);
|
|
135
|
+
out("\n LinkShell screen\n");
|
|
136
|
+
if (!hostRuntimeOk()) {
|
|
137
|
+
row(out, false, "Host", `needs Node.js 22.13 or newer (this is ${process.version})`);
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (!options.check) await ensureHostRunning();
|
|
142
|
+
const interactive = !options.check && process.stdin.isTTY === true;
|
|
143
|
+
if (await setUpScreen(out, interactive)) {
|
|
144
|
+
out(process.platform === "darwin" ? `\n ${green("Ready.")} In the app: 电脑 › 屏幕. To take the pointer and keyboard, pick 触控板 or 点按 in the viewer's toolbar.\n` : "");
|
|
139
145
|
} else {
|
|
140
|
-
out(`\n Not ready yet${
|
|
146
|
+
out(`\n Not ready yet${interactive ? "" : "; run `linkshell screen` in a terminal to set it up"}.\n`);
|
|
141
147
|
process.exitCode = 1;
|
|
142
148
|
}
|
|
143
149
|
}
|