linkshell-cli 0.10.65 → 0.10.67
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/desktop.d.ts +6 -0
- package/dist/cli/src/commands/desktop.js +27 -1
- package/dist/cli/src/commands/desktop.js.map +1 -1
- package/dist/cli/src/commands/password.d.ts +4 -0
- package/dist/cli/src/commands/password.js +12 -3
- package/dist/cli/src/commands/password.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/commands/desktop.ts +21 -1
- package/src/commands/password.ts +12 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linkshell-cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.67",
|
|
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/cli/src/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"ws": "^8.21.0",
|
|
37
37
|
"@linkshell/gateway": "0.6.37",
|
|
38
38
|
"@linkshell/wire": "0.2.20",
|
|
39
|
-
"@linkshell/host": "0.4.
|
|
39
|
+
"@linkshell/host": "0.4.64"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/ws": "^8.5.14",
|
package/src/commands/desktop.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { sep, join } from "node:path";
|
|
|
12
12
|
import { ensureHostRunning, silenceSqliteWarning, withRunningHost } from "./host.js";
|
|
13
13
|
import { linkshellDir, readHostLaunch, readHostOwner, readPid, stopDaemonAndWait } from "../utils/daemon.js";
|
|
14
14
|
import { cachedMembership, redeemDesktopPro, refreshMembership } from "../pro-membership.js";
|
|
15
|
+
import { accessPasswordEnabled, applyAccessPassword } from "./password.js";
|
|
15
16
|
|
|
16
17
|
export function installationSource(path: string): "homebrew" | "npm" {
|
|
17
18
|
return path.includes(`${sep}Cellar${sep}linkshell${sep}`) ? "homebrew" : "npm";
|
|
@@ -27,7 +28,8 @@ export async function desktopSnapshot(version: string) {
|
|
|
27
28
|
let executable = process.argv[1]!;
|
|
28
29
|
try { executable = realpathSync(executable); } catch { /* Retain the launch path for diagnosis. */ }
|
|
29
30
|
const account = desktopAccount();
|
|
30
|
-
const
|
|
31
|
+
const access = { password: await accessPasswordEnabled().catch(() => false) };
|
|
32
|
+
const base = { account, access, schema: 1, cliVersion: version, home: linkshellDir(), source: standaloneBundle() ? "standalone" : installationSource(executable), executable, hostOwner: readPid("host") ? readHostOwner(readPid("host")!) ?? "external" : "none", appBundle: standaloneBundle() };
|
|
31
33
|
const result = await withRunningHost(async (client) => {
|
|
32
34
|
const machine = await client.call("machine.info", {}, 5000);
|
|
33
35
|
const [gateway, sessions] = await Promise.all([
|
|
@@ -40,6 +42,22 @@ export async function desktopSnapshot(version: string) {
|
|
|
40
42
|
return result ? { ...base, status: "running", ...result } : { ...base, status: readPid("host") ? "unreachable" : "stopped" };
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
/** The new password, as JSON on stdin (never on the command line, where `ps` shows it). */
|
|
46
|
+
async function passwordInput(input: AsyncIterable<string | Buffer>): Promise<string> {
|
|
47
|
+
const chunks: Buffer[] = [];
|
|
48
|
+
let length = 0;
|
|
49
|
+
for await (const chunk of input) {
|
|
50
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
51
|
+
length += buffer.length;
|
|
52
|
+
if (length > 4096) throw new Error("密码过长,请输入 4 到 128 位");
|
|
53
|
+
chunks.push(buffer);
|
|
54
|
+
}
|
|
55
|
+
let password: unknown;
|
|
56
|
+
try { password = JSON.parse(Buffer.concat(chunks).toString("utf8"))?.password; } catch { /* Never echo the input. */ }
|
|
57
|
+
if (typeof password !== "string" || password.length < 4 || password.length > 128) throw new Error("密码需要 4 到 128 位");
|
|
58
|
+
return password;
|
|
59
|
+
}
|
|
60
|
+
|
|
43
61
|
async function redemptionInput(input: AsyncIterable<string | Buffer>): Promise<{ code: string; userId: string }> {
|
|
44
62
|
const chunks: Buffer[] = [];
|
|
45
63
|
let length = 0;
|
|
@@ -122,6 +140,8 @@ export async function runDesktop(version: string, action: string, input: AsyncIt
|
|
|
122
140
|
await ensureHostRunning();
|
|
123
141
|
await refreshGateway(15_000);
|
|
124
142
|
}
|
|
143
|
+
else if (action === "set-password") await applyAccessPassword(await passwordInput(input));
|
|
144
|
+
else if (action === "remove-password") await applyAccessPassword(undefined);
|
|
125
145
|
else if (action === "prepare-update") { await prepareDesktopUpdate(); }
|
|
126
146
|
else if (action === "recover-update") { await recoverDesktopUpdate(version); }
|
|
127
147
|
else if (action === "stop-update-host") { await stopDesktopUpdateHost(); }
|
package/src/commands/password.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RpcError } from "@linkshell/wire";
|
|
2
2
|
import { linkshellDir } from "../utils/daemon.js";
|
|
3
|
-
import { assertHostRuntime, withRunningHost } from "./host.js";
|
|
3
|
+
import { assertHostRuntime, silenceSqliteWarning, withRunningHost } from "./host.js";
|
|
4
4
|
|
|
5
5
|
/** Reads a line without echoing it; from a pipe, just the first line. */
|
|
6
6
|
function readHidden(prompt: string): Promise<string> {
|
|
@@ -44,7 +44,7 @@ function unknownMethod(error: unknown): boolean {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Applies the change through the running host, or writes it for the next start. */
|
|
47
|
-
async function
|
|
47
|
+
export async function applyAccessPassword(password: string | undefined): Promise<"live" | "restart" | "file"> {
|
|
48
48
|
try {
|
|
49
49
|
const result = await withRunningHost((client) => client.call("access.set", password ? { password } : {}));
|
|
50
50
|
if (result) return "live";
|
|
@@ -60,8 +60,17 @@ async function apply(password: string | undefined): Promise<"live" | "restart" |
|
|
|
60
60
|
return "file";
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/** Whether this computer has an access password: the running host's answer, else the file. */
|
|
64
|
+
export async function accessPasswordEnabled(): Promise<boolean> {
|
|
65
|
+
const live = await withRunningHost((client) => client.call("access.status", {})).catch(() => undefined);
|
|
66
|
+
if (live) return live.password;
|
|
67
|
+
const host = await import("@linkshell/host");
|
|
68
|
+
return new host.AccessControl(linkshellDir()).enabled;
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
export async function runPassword(action: "status" | "set" | "remove"): Promise<void> {
|
|
64
72
|
assertHostRuntime();
|
|
73
|
+
silenceSqliteWarning();
|
|
65
74
|
const host = await import("@linkshell/host");
|
|
66
75
|
if (action === "status") {
|
|
67
76
|
const live = await withRunningHost((client) => client.call("access.status", {})).catch(() => undefined);
|
|
@@ -84,7 +93,7 @@ export async function runPassword(action: "status" | "set" | "remove"): Promise<
|
|
|
84
93
|
throw error;
|
|
85
94
|
}
|
|
86
95
|
}
|
|
87
|
-
const how = await
|
|
96
|
+
const how = await applyAccessPassword(password);
|
|
88
97
|
const what = password ? "Access password set." : "Access password removed.";
|
|
89
98
|
const when =
|
|
90
99
|
how === "live"
|