linkshell-cli 0.9.0 → 0.10.1
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 +2 -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/setup.js +4 -2
- package/dist/cli/src/commands/setup.js.map +1 -1
- package/dist/cli/src/index.js +12 -334
- 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/drivers/codex/app-server.d.ts +31 -6
- package/dist/host/src/drivers/codex/app-server.js +99 -24
- package/dist/host/src/drivers/codex/app-server.js.map +1 -1
- package/dist/host/src/drivers/codex/desktop-ipc.d.ts +8 -0
- package/dist/host/src/drivers/codex/desktop-ipc.js +124 -0
- package/dist/host/src/drivers/codex/desktop-ipc.js.map +1 -0
- package/dist/host/src/drivers/codex/driver.d.ts +111 -2
- package/dist/host/src/drivers/codex/driver.js +629 -63
- package/dist/host/src/drivers/codex/driver.js.map +1 -1
- package/dist/host/src/drivers/codex/mapper.d.ts +21 -0
- package/dist/host/src/drivers/codex/mapper.js +45 -6
- package/dist/host/src/drivers/codex/mapper.js.map +1 -1
- package/dist/host/src/drivers/types.d.ts +6 -0
- package/dist/host/src/fs.d.ts +3 -3
- package/dist/host/src/hub.js +8 -1
- package/dist/host/src/hub.js.map +1 -1
- package/dist/host/src/input.d.ts +4 -4
- package/dist/host/src/terminals.d.ts +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 +5 -14
- package/src/commands/gateway.ts +31 -0
- package/src/commands/host.ts +13 -2
- package/src/commands/setup.ts +4 -2
- package/src/index.ts +14 -429
- 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
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
|
|
4
|
-
/** Pre-marker CLI: `curl -X POST http://127.0.0.1:<port>/hook --data-binary @-`. */
|
|
5
|
-
const LEGACY_LOCAL_HOOK =
|
|
6
|
-
/https?:\/\/127\.0\.0\.1:\d+\/hook(?:\?|"|'|\s|$|\\)/;
|
|
7
|
-
|
|
8
|
-
function hookEntryText(entry: unknown): string {
|
|
9
|
-
try {
|
|
10
|
-
return JSON.stringify(entry);
|
|
11
|
-
} catch {
|
|
12
|
-
return String(entry);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** True for current (`/hook?m=lsh-…`) and legacy localhost `/hook` curl entries. */
|
|
17
|
-
export function isLinkShellHookEntry(entry: unknown, marker?: string): boolean {
|
|
18
|
-
const raw = hookEntryText(entry);
|
|
19
|
-
if (marker && raw.includes(`/hook?m=${marker}`)) return true;
|
|
20
|
-
if (raw.includes("/hook?m=lsh-")) return true;
|
|
21
|
-
if (raw.includes("/hook?m=") && raw.includes("LINKSHELL_ID")) return true;
|
|
22
|
-
return LEGACY_LOCAL_HOOK.test(raw) && raw.includes("--data-binary @-");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function sweepHooksObject(hooks: Record<string, unknown>): boolean {
|
|
26
|
-
let changed = false;
|
|
27
|
-
for (const [eventName, entries] of Object.entries(hooks)) {
|
|
28
|
-
if (!Array.isArray(entries)) continue;
|
|
29
|
-
const filtered = entries.filter((entry) => !isLinkShellHookEntry(entry));
|
|
30
|
-
if (filtered.length !== entries.length) {
|
|
31
|
-
changed = true;
|
|
32
|
-
if (filtered.length === 0) delete hooks[eventName];
|
|
33
|
-
else hooks[eventName] = filtered;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return changed;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Remove leftover LinkShell hook entries from one JSON settings/hooks file. */
|
|
40
|
-
export function sweepLinkShellHookEntries(configPath: string): boolean {
|
|
41
|
-
if (!existsSync(configPath)) return false;
|
|
42
|
-
try {
|
|
43
|
-
const raw = JSON.parse(readFileSync(configPath, "utf8")) as Record<string, unknown>;
|
|
44
|
-
const hooks = raw.hooks;
|
|
45
|
-
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return false;
|
|
46
|
-
const changed = sweepHooksObject(hooks as Record<string, unknown>);
|
|
47
|
-
if (!changed) return false;
|
|
48
|
-
if (Object.keys(hooks as object).length === 0) delete raw.hooks;
|
|
49
|
-
writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}\n`);
|
|
50
|
-
return true;
|
|
51
|
-
} catch {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* One-shot leftover sweep. LinkShell no longer writes hook configs; this only
|
|
58
|
-
* deletes historical curl-hook entries so they cannot fire after upgrade.
|
|
59
|
-
*/
|
|
60
|
-
export function sweepLinkShellHookConfigs(home: string, extraPaths: string[] = []): string[] {
|
|
61
|
-
const candidates = [
|
|
62
|
-
join(home, ".claude", "settings.json"),
|
|
63
|
-
join(home, ".claude", "settings.local.json"),
|
|
64
|
-
join(home, ".codex", "hooks.json"),
|
|
65
|
-
join(home, ".gemini", "settings.json"),
|
|
66
|
-
...extraPaths,
|
|
67
|
-
];
|
|
68
|
-
const swept: string[] = [];
|
|
69
|
-
for (const path of candidates) {
|
|
70
|
-
if (sweepLinkShellHookEntries(path)) swept.push(path);
|
|
71
|
-
}
|
|
72
|
-
return swept;
|
|
73
|
-
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { execSync, spawn } from "node:child_process";
|
|
2
|
-
import { readFileSync, unlinkSync, existsSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { platform } from "node:os";
|
|
6
|
-
import { createEnvelope, serializeEnvelope } from "@linkshell/protocol";
|
|
7
|
-
import type { Envelope } from "@linkshell/protocol";
|
|
8
|
-
|
|
9
|
-
export interface ScreenFallbackOptions {
|
|
10
|
-
fps: number;
|
|
11
|
-
quality: number;
|
|
12
|
-
scale: number;
|
|
13
|
-
sessionId: string;
|
|
14
|
-
onFrame: (envelope: Envelope) => void;
|
|
15
|
-
onStatus: (envelope: Envelope) => void;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const CHUNK_SIZE = 48 * 1024; // 48KB per chunk, leave room for envelope overhead
|
|
19
|
-
const TMP_FILE = join(tmpdir(), `linkshell-screen-${process.pid}.jpg`);
|
|
20
|
-
|
|
21
|
-
export class ScreenFallback {
|
|
22
|
-
private timer: ReturnType<typeof setInterval> | undefined;
|
|
23
|
-
private frameId = 0;
|
|
24
|
-
private active = false;
|
|
25
|
-
private readonly options: ScreenFallbackOptions;
|
|
26
|
-
|
|
27
|
-
constructor(options: ScreenFallbackOptions) {
|
|
28
|
-
this.options = options;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
start(): void {
|
|
32
|
-
if (this.active) return;
|
|
33
|
-
this.active = true;
|
|
34
|
-
this.frameId = 0;
|
|
35
|
-
|
|
36
|
-
const interval = Math.max(50, Math.floor(1000 / this.options.fps));
|
|
37
|
-
|
|
38
|
-
this.options.onStatus(
|
|
39
|
-
createEnvelope({
|
|
40
|
-
type: "screen.status",
|
|
41
|
-
sessionId: this.options.sessionId,
|
|
42
|
-
payload: { active: true, mode: "fallback" as const },
|
|
43
|
-
}),
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
// Capture first frame immediately
|
|
47
|
-
this.captureAndSend();
|
|
48
|
-
|
|
49
|
-
this.timer = setInterval(() => {
|
|
50
|
-
this.captureAndSend();
|
|
51
|
-
}, interval);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
stop(): void {
|
|
55
|
-
if (!this.active) return;
|
|
56
|
-
this.active = false;
|
|
57
|
-
if (this.timer) {
|
|
58
|
-
clearInterval(this.timer);
|
|
59
|
-
this.timer = undefined;
|
|
60
|
-
}
|
|
61
|
-
// Clean up temp file
|
|
62
|
-
try { if (existsSync(TMP_FILE)) unlinkSync(TMP_FILE); } catch {}
|
|
63
|
-
|
|
64
|
-
this.options.onStatus(
|
|
65
|
-
createEnvelope({
|
|
66
|
-
type: "screen.status",
|
|
67
|
-
sessionId: this.options.sessionId,
|
|
68
|
-
payload: { active: false, mode: "off" as const },
|
|
69
|
-
}),
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private captureAndSend(): void {
|
|
74
|
-
if (!this.active) return;
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
const imgBuffer = this.captureScreen();
|
|
78
|
-
if (!imgBuffer || imgBuffer.length === 0) return;
|
|
79
|
-
|
|
80
|
-
const base64 = imgBuffer.toString("base64");
|
|
81
|
-
const frameId = this.frameId++;
|
|
82
|
-
|
|
83
|
-
// Get dimensions (approximate from JPEG header or use defaults)
|
|
84
|
-
const dims = this.getJpegDimensions(imgBuffer);
|
|
85
|
-
|
|
86
|
-
if (base64.length <= CHUNK_SIZE) {
|
|
87
|
-
// Single chunk
|
|
88
|
-
this.options.onFrame(
|
|
89
|
-
createEnvelope({
|
|
90
|
-
type: "screen.frame",
|
|
91
|
-
sessionId: this.options.sessionId,
|
|
92
|
-
payload: {
|
|
93
|
-
data: base64,
|
|
94
|
-
width: dims.width,
|
|
95
|
-
height: dims.height,
|
|
96
|
-
frameId,
|
|
97
|
-
chunkIndex: 0,
|
|
98
|
-
chunkTotal: 1,
|
|
99
|
-
},
|
|
100
|
-
}),
|
|
101
|
-
);
|
|
102
|
-
} else {
|
|
103
|
-
// Split into chunks
|
|
104
|
-
const chunkTotal = Math.ceil(base64.length / CHUNK_SIZE);
|
|
105
|
-
for (let i = 0; i < chunkTotal; i++) {
|
|
106
|
-
if (!this.active) return;
|
|
107
|
-
const chunk = base64.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
|
|
108
|
-
this.options.onFrame(
|
|
109
|
-
createEnvelope({
|
|
110
|
-
type: "screen.frame",
|
|
111
|
-
sessionId: this.options.sessionId,
|
|
112
|
-
payload: {
|
|
113
|
-
data: chunk,
|
|
114
|
-
width: dims.width,
|
|
115
|
-
height: dims.height,
|
|
116
|
-
frameId,
|
|
117
|
-
chunkIndex: i,
|
|
118
|
-
chunkTotal,
|
|
119
|
-
},
|
|
120
|
-
}),
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
} catch {
|
|
125
|
-
// Silently skip failed frames
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
private captureScreen(): Buffer | null {
|
|
130
|
-
const os = platform();
|
|
131
|
-
const q = this.options.quality;
|
|
132
|
-
const s = this.options.scale;
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
if (os === "darwin") {
|
|
136
|
-
// macOS: screencapture
|
|
137
|
-
execSync(
|
|
138
|
-
`screencapture -x -t jpg -C "${TMP_FILE}"`,
|
|
139
|
-
{ timeout: 3000, stdio: "pipe" },
|
|
140
|
-
);
|
|
141
|
-
if (!existsSync(TMP_FILE)) return null;
|
|
142
|
-
let buf = readFileSync(TMP_FILE);
|
|
143
|
-
|
|
144
|
-
// Resize if scale < 1 and sips is available
|
|
145
|
-
if (s < 1) {
|
|
146
|
-
try {
|
|
147
|
-
const w = Math.floor(this.getJpegDimensions(buf).width * s);
|
|
148
|
-
execSync(
|
|
149
|
-
`sips --resampleWidth ${w} -s formatOptions ${q} "${TMP_FILE}" --out "${TMP_FILE}"`,
|
|
150
|
-
{ timeout: 3000, stdio: "pipe" },
|
|
151
|
-
);
|
|
152
|
-
buf = readFileSync(TMP_FILE);
|
|
153
|
-
} catch {
|
|
154
|
-
// Use original size if resize fails
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return buf;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (os === "linux") {
|
|
161
|
-
// Linux: try import (ImageMagick) or scrot
|
|
162
|
-
try {
|
|
163
|
-
const resizeArg = s < 1 ? `-resize ${Math.floor(s * 100)}%` : "";
|
|
164
|
-
execSync(
|
|
165
|
-
`import -window root -quality ${q} ${resizeArg} jpeg:"${TMP_FILE}"`,
|
|
166
|
-
{ timeout: 3000, stdio: "pipe" },
|
|
167
|
-
);
|
|
168
|
-
} catch {
|
|
169
|
-
// Fallback to scrot
|
|
170
|
-
execSync(
|
|
171
|
-
`scrot -q ${q} "${TMP_FILE}"`,
|
|
172
|
-
{ timeout: 3000, stdio: "pipe" },
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
if (!existsSync(TMP_FILE)) return null;
|
|
176
|
-
return readFileSync(TMP_FILE);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Windows: use PowerShell
|
|
180
|
-
if (os === "win32") {
|
|
181
|
-
execSync(
|
|
182
|
-
`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::PrimaryScreen | ForEach-Object { $bmp = New-Object System.Drawing.Bitmap($_.Bounds.Width, $_.Bounds.Height); $g = [System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen($_.Bounds.Location, [System.Drawing.Point]::Empty, $_.Bounds.Size); $bmp.Save('${TMP_FILE.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Jpeg) }"`,
|
|
183
|
-
{ timeout: 5000, stdio: "pipe" },
|
|
184
|
-
);
|
|
185
|
-
if (!existsSync(TMP_FILE)) return null;
|
|
186
|
-
return readFileSync(TMP_FILE);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return null;
|
|
190
|
-
} catch {
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
private getJpegDimensions(buf: Buffer): { width: number; height: number } {
|
|
196
|
-
// Parse JPEG SOF marker for dimensions
|
|
197
|
-
try {
|
|
198
|
-
let i = 2; // Skip SOI
|
|
199
|
-
while (i < buf.length - 8) {
|
|
200
|
-
if (buf[i] !== 0xff) break;
|
|
201
|
-
const marker = buf[i + 1]!;
|
|
202
|
-
// SOF0, SOF1, SOF2
|
|
203
|
-
if (marker >= 0xc0 && marker <= 0xc2) {
|
|
204
|
-
const height = buf.readUInt16BE(i + 5);
|
|
205
|
-
const width = buf.readUInt16BE(i + 7);
|
|
206
|
-
return { width, height };
|
|
207
|
-
}
|
|
208
|
-
const len = buf.readUInt16BE(i + 2);
|
|
209
|
-
i += 2 + len;
|
|
210
|
-
}
|
|
211
|
-
} catch {}
|
|
212
|
-
return { width: 1920, height: 1080 }; // Default fallback
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
static isAvailable(): boolean {
|
|
216
|
-
const os = platform();
|
|
217
|
-
try {
|
|
218
|
-
if (os === "darwin") {
|
|
219
|
-
execSync("which screencapture", { stdio: "pipe" });
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
222
|
-
if (os === "linux") {
|
|
223
|
-
try {
|
|
224
|
-
execSync("which import", { stdio: "pipe" });
|
|
225
|
-
return true;
|
|
226
|
-
} catch {
|
|
227
|
-
execSync("which scrot", { stdio: "pipe" });
|
|
228
|
-
return true;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (os === "win32") return true; // PowerShell always available
|
|
232
|
-
} catch {}
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
}
|