linkshell-cli 0.1.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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -0
  3. package/dist/cli/src/commands/doctor.d.ts +1 -0
  4. package/dist/cli/src/commands/doctor.js +112 -0
  5. package/dist/cli/src/commands/doctor.js.map +1 -0
  6. package/dist/cli/src/commands/setup.d.ts +1 -0
  7. package/dist/cli/src/commands/setup.js +48 -0
  8. package/dist/cli/src/commands/setup.js.map +1 -0
  9. package/dist/cli/src/config.d.ts +12 -0
  10. package/dist/cli/src/config.js +23 -0
  11. package/dist/cli/src/config.js.map +1 -0
  12. package/dist/cli/src/index.d.ts +2 -0
  13. package/dist/cli/src/index.js +108 -0
  14. package/dist/cli/src/index.js.map +1 -0
  15. package/dist/cli/src/providers.d.ts +12 -0
  16. package/dist/cli/src/providers.js +61 -0
  17. package/dist/cli/src/providers.js.map +1 -0
  18. package/dist/cli/src/runtime/bridge-session.d.ts +40 -0
  19. package/dist/cli/src/runtime/bridge-session.js +317 -0
  20. package/dist/cli/src/runtime/bridge-session.js.map +1 -0
  21. package/dist/cli/src/runtime/scrollback.d.ts +11 -0
  22. package/dist/cli/src/runtime/scrollback.js +33 -0
  23. package/dist/cli/src/runtime/scrollback.js.map +1 -0
  24. package/dist/cli/src/utils/lan-ip.d.ts +5 -0
  25. package/dist/cli/src/utils/lan-ip.js +20 -0
  26. package/dist/cli/src/utils/lan-ip.js.map +1 -0
  27. package/dist/cli/tsconfig.tsbuildinfo +1 -0
  28. package/dist/shared-protocol/src/index.d.ts +380 -0
  29. package/dist/shared-protocol/src/index.js +158 -0
  30. package/dist/shared-protocol/src/index.js.map +1 -0
  31. package/package.json +49 -0
  32. package/src/commands/doctor.ts +119 -0
  33. package/src/commands/setup.ts +65 -0
  34. package/src/config.ts +34 -0
  35. package/src/index.ts +139 -0
  36. package/src/providers.ts +91 -0
  37. package/src/runtime/bridge-session.ts +407 -0
  38. package/src/runtime/scrollback.ts +43 -0
  39. package/src/types/qrcode-terminal.d.ts +7 -0
  40. package/src/utils/lan-ip.ts +19 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LinkShell Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # linkshell-cli
2
+
3
+ 在手机上远程查看和控制本地 Claude Code / Codex 终端会话。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install -g linkshell-cli
9
+ ```
10
+
11
+ ## 一条命令开始
12
+
13
+ ```bash
14
+ linkshell start --provider claude
15
+ ```
16
+
17
+ CLI 会自动:
18
+ 1. 启动内置 Gateway(端口 8787)
19
+ 2. 检测局域网 IP
20
+ 3. 创建配对并打印 QR 码
21
+ 4. 手机扫码即连
22
+
23
+ ```
24
+ Built-in gateway started on port 8787
25
+ LAN address: http://192.168.1.12:8787
26
+
27
+ Pairing code: 847293
28
+ Session: a1b2c3d4-...
29
+
30
+ Scan to connect:
31
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
32
+ ...
33
+ ```
34
+
35
+ ## 更多用法
36
+
37
+ ```bash
38
+ # 桥接 Claude Code
39
+ linkshell start --provider claude
40
+
41
+ # 桥接 Codex
42
+ linkshell start --provider codex
43
+
44
+ # 桥接任意命令
45
+ linkshell start --provider custom --command bash
46
+
47
+ # 指定端口
48
+ linkshell start --provider claude --port 9000
49
+
50
+ # 连接远程 Gateway(不启动内置 Gateway)
51
+ linkshell start --gateway wss://your-server.com:8787/ws --provider claude
52
+
53
+ # 指定 QR 码中的公网地址
54
+ linkshell start --gateway ws://127.0.0.1:8787/ws --pairing-gateway 203.0.113.10 --provider claude
55
+ ```
56
+
57
+ ## 命令
58
+
59
+ | 命令 | 说明 |
60
+ |------|------|
61
+ | `linkshell start` | 启动桥接会话(内置或远程 Gateway) |
62
+ | `linkshell setup` | 交互式配置向导 |
63
+ | `linkshell doctor` | 环境检查和连通性诊断 |
64
+
65
+ ## start 选项
66
+
67
+ ```
68
+ --gateway <url> 远程 Gateway 地址(省略则启动内置 Gateway)
69
+ --port <port> 内置 Gateway 端口(默认 8787)
70
+ --pairing-gateway <url> QR 码中给手机使用的地址
71
+ --provider <name> claude | codex | custom(默认 claude)
72
+ --command <cmd> 自定义命令(custom provider 必填)
73
+ --session-id <id> 手动指定 session ID
74
+ --client-name <name> 显示名称(默认 local-cli)
75
+ --cols <n> 终端列数(默认 120)
76
+ --rows <n> 终端行数(默认 36)
77
+ --verbose 详细日志
78
+ ```
79
+
80
+ ## 配置持久化
81
+
82
+ ```bash
83
+ linkshell setup
84
+ ```
85
+
86
+ 配置保存在 `~/.linkshell/config.json`,后续启动自动读取。
87
+
88
+ ## doctor 检查项
89
+
90
+ ```bash
91
+ linkshell doctor
92
+ ```
93
+
94
+ - Node.js 版本 >= 18
95
+ - node-pty 原生模块
96
+ - Claude / Codex CLI 是否安装
97
+ - 配置文件状态
98
+ - Gateway 连通性和延迟
99
+
100
+ ## 工作原理
101
+
102
+ ```
103
+ 你的电脑 (CLI + 内置 Gateway) ◄──WebSocket──► 手机 (App)
104
+ ```
105
+
106
+ 1. CLI 启动内置 Gateway(或连接远程 Gateway)
107
+ 2. 通过 PTY 启动目标进程(Claude/Codex/bash)
108
+ 3. 终端输出通过 WebSocket 转发给手机 App
109
+ 4. App 的输入回传给 CLI,写入 PTY
110
+
111
+ 支持断线自动重连、ACK 确认、scrollback 缓冲(1000 条)、心跳检测(15s)和协议版本协商。
112
+
113
+ ## License
114
+
115
+ MIT
@@ -0,0 +1 @@
1
+ export declare function runDoctor(gatewayUrl?: string): Promise<void>;
@@ -0,0 +1,112 @@
1
+ import { execSync } from "node:child_process";
2
+ import { loadConfig, getConfigPath } from "../config.js";
3
+ function check(name, fn) {
4
+ try {
5
+ return { name, ok: true, detail: fn() };
6
+ }
7
+ catch (e) {
8
+ return { name, ok: false, detail: e instanceof Error ? e.message : String(e) };
9
+ }
10
+ }
11
+ function which(bin) {
12
+ try {
13
+ return execSync(`which ${bin}`, { encoding: "utf8", timeout: 5000 }).trim() || undefined;
14
+ }
15
+ catch {
16
+ return undefined;
17
+ }
18
+ }
19
+ async function checkGateway(url) {
20
+ try {
21
+ const httpUrl = url.replace(/\/ws\/?$/, "").replace(/^wss:/, "https:").replace(/^ws:/, "http:");
22
+ const start = Date.now();
23
+ const res = await fetch(`${httpUrl}/healthz`, { signal: AbortSignal.timeout(5000) });
24
+ const latency = Date.now() - start;
25
+ if (!res.ok)
26
+ return { name: "Gateway", ok: false, detail: `HTTP ${res.status}` };
27
+ return { name: "Gateway", ok: true, detail: `reachable (${latency}ms)` };
28
+ }
29
+ catch (e) {
30
+ return { name: "Gateway", ok: false, detail: e instanceof Error ? e.message : "unreachable" };
31
+ }
32
+ }
33
+ export async function runDoctor(gatewayUrl) {
34
+ const config = loadConfig();
35
+ const gateway = gatewayUrl ?? config.gateway;
36
+ process.stdout.write("\n LinkShell Doctor\n\n");
37
+ const results = [];
38
+ // Node.js version
39
+ results.push(check("Node.js", () => {
40
+ const ver = process.versions.node;
41
+ const major = Number(ver.split(".")[0]);
42
+ if (major < 18)
43
+ throw new Error(`v${ver} (need >= 18)`);
44
+ return `v${ver}`;
45
+ }));
46
+ // node-pty
47
+ results.push(check("node-pty", () => {
48
+ try {
49
+ require("node-pty");
50
+ return "loaded";
51
+ }
52
+ catch {
53
+ // Try dynamic import path for ESM
54
+ try {
55
+ execSync("node -e \"require('node-pty')\"", { timeout: 5000, stdio: "pipe" });
56
+ return "loaded";
57
+ }
58
+ catch {
59
+ throw new Error("native module not built — run: pnpm approve-builds && pnpm install --force");
60
+ }
61
+ }
62
+ }));
63
+ // Claude CLI
64
+ const claudePath = which("claude");
65
+ if (claudePath) {
66
+ results.push(check("Claude CLI", () => {
67
+ const ver = execSync("claude --version 2>&1", { encoding: "utf8", timeout: 5000 }).trim();
68
+ return `${ver} (${claudePath})`;
69
+ }));
70
+ }
71
+ else {
72
+ results.push({ name: "Claude CLI", ok: false, detail: "not found — npm i -g @anthropic-ai/claude-code" });
73
+ }
74
+ // Codex CLI
75
+ const codexPath = which("codex");
76
+ if (codexPath) {
77
+ results.push(check("Codex CLI", () => `found (${codexPath})`));
78
+ }
79
+ else {
80
+ results.push({ name: "Codex CLI", ok: false, detail: "not found — npm i -g @openai/codex" });
81
+ }
82
+ // Config
83
+ results.push(check("Config", () => {
84
+ const path = getConfigPath();
85
+ const cfg = loadConfig();
86
+ const keys = Object.keys(cfg).filter((k) => cfg[k] !== undefined);
87
+ if (keys.length === 0)
88
+ return `${path} (empty — run: linkshell setup)`;
89
+ return `${path} (${keys.join(", ")})`;
90
+ }));
91
+ // Gateway
92
+ if (gateway) {
93
+ results.push(await checkGateway(gateway));
94
+ }
95
+ else {
96
+ results.push({ name: "Gateway", ok: false, detail: "no gateway configured — run: linkshell setup" });
97
+ }
98
+ // Print results
99
+ for (const r of results) {
100
+ const icon = r.ok ? "\x1b[32m✓\x1b[0m" : "\x1b[31m✗\x1b[0m";
101
+ process.stdout.write(` ${icon} ${r.name}: ${r.detail}\n`);
102
+ }
103
+ const failed = results.filter((r) => !r.ok);
104
+ process.stdout.write("\n");
105
+ if (failed.length === 0) {
106
+ process.stdout.write(" \x1b[32mAll checks passed.\x1b[0m\n\n");
107
+ }
108
+ else {
109
+ process.stdout.write(` \x1b[33m${failed.length} issue(s) found.\x1b[0m\n\n`);
110
+ }
111
+ }
112
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function runSetup(): Promise<void>;
@@ -0,0 +1,48 @@
1
+ import * as readline from "node:readline";
2
+ import { loadConfig, saveConfig, getConfigPath } from "../config.js";
3
+ function ask(rl, question, defaultValue) {
4
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
5
+ return new Promise((resolve) => {
6
+ rl.question(` ${question}${suffix}: `, (answer) => {
7
+ resolve(answer.trim() || defaultValue || "");
8
+ });
9
+ });
10
+ }
11
+ function choose(rl, question, options, defaultIdx = 0) {
12
+ return new Promise((resolve) => {
13
+ process.stdout.write(` ${question}\n`);
14
+ for (let i = 0; i < options.length; i++) {
15
+ const marker = i === defaultIdx ? "\x1b[36m>\x1b[0m" : " ";
16
+ process.stdout.write(` ${marker} ${i + 1}. ${options[i]}\n`);
17
+ }
18
+ rl.question(` Choice (${defaultIdx + 1}): `, (answer) => {
19
+ const idx = Number(answer.trim()) - 1;
20
+ resolve(options[idx >= 0 && idx < options.length ? idx : defaultIdx]);
21
+ });
22
+ });
23
+ }
24
+ export async function runSetup() {
25
+ const existing = loadConfig();
26
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
27
+ process.stdout.write("\n LinkShell Setup\n\n");
28
+ const gateway = await ask(rl, "Gateway URL", existing.gateway ?? "wss://localhost:8787/ws");
29
+ const provider = await choose(rl, "Default provider:", ["claude", "codex", "custom"], ["claude", "codex", "custom"].indexOf(existing.provider ?? "claude"));
30
+ let command;
31
+ if (provider === "custom") {
32
+ command = await ask(rl, "Custom command", existing.command ?? "bash");
33
+ }
34
+ const clientName = await ask(rl, "Client name", existing.clientName ?? require("os").hostname());
35
+ rl.close();
36
+ const config = {
37
+ gateway,
38
+ provider,
39
+ command,
40
+ clientName,
41
+ };
42
+ saveConfig(config);
43
+ process.stdout.write(`\n \x1b[32mConfig saved to ${getConfigPath()}\x1b[0m\n\n`);
44
+ process.stdout.write(" Next steps:\n");
45
+ process.stdout.write(" linkshell doctor — verify your setup\n");
46
+ process.stdout.write(" linkshell start — start a bridge session\n\n");
47
+ }
48
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../../src/commands/setup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface LinkShellConfig {
2
+ gateway?: string;
3
+ pairingGateway?: string;
4
+ provider?: "claude" | "codex" | "custom";
5
+ command?: string;
6
+ clientName?: string;
7
+ cols?: number;
8
+ rows?: number;
9
+ }
10
+ export declare function loadConfig(): LinkShellConfig;
11
+ export declare function saveConfig(config: LinkShellConfig): void;
12
+ export declare function getConfigPath(): string;
@@ -0,0 +1,23 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ const CONFIG_DIR = join(homedir(), ".linkshell");
5
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
6
+ export function loadConfig() {
7
+ try {
8
+ if (!existsSync(CONFIG_FILE))
9
+ return {};
10
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
11
+ }
12
+ catch {
13
+ return {};
14
+ }
15
+ }
16
+ export function saveConfig(config) {
17
+ mkdirSync(CONFIG_DIR, { recursive: true });
18
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf8");
19
+ }
20
+ export function getConfigPath() {
21
+ return CONFIG_FILE;
22
+ }
23
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAYlC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;AACjD,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEpD,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;YAAE,OAAO,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAoB,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAuB;IAChD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { BridgeSession } from "./runtime/bridge-session.js";
4
+ import { resolveProviderConfig } from "./providers.js";
5
+ import { loadConfig } from "./config.js";
6
+ import { runDoctor } from "./commands/doctor.js";
7
+ import { runSetup } from "./commands/setup.js";
8
+ import { getLanIp } from "./utils/lan-ip.js";
9
+ const config = loadConfig();
10
+ const program = new Command();
11
+ program
12
+ .name("linkshell")
13
+ .description("Bridge a local Claude/Codex terminal session to a remote gateway")
14
+ .version("0.1.0");
15
+ program
16
+ .command("start")
17
+ .description("Start a bridge session")
18
+ .option("--gateway <url>", "Gateway websocket URL (omit to start built-in gateway)", config.gateway)
19
+ .option("--pairing-gateway <url-or-host>", "Public HTTP gateway used in QR/deep link output", config.pairingGateway)
20
+ .option("--port <port>", "Port for built-in gateway (default: 8787)", "8787")
21
+ .option("--session-id <id>", "Session identifier (auto-created if omitted)")
22
+ .option("--provider <provider>", "claude | codex | custom", config.provider ?? "claude")
23
+ .option("--command <command>", "Override provider executable", config.command)
24
+ .option("--client-name <name>", "Display name for this CLI", config.clientName ?? "local-cli")
25
+ .option("--cols <cols>", "Initial terminal columns", String(config.cols ?? 120))
26
+ .option("--rows <rows>", "Initial terminal rows", String(config.rows ?? 36))
27
+ .option("--verbose", "Enable verbose logging")
28
+ .allowUnknownOption(true)
29
+ .action(async (options, command) => {
30
+ const passthroughArgs = command.args.filter((value) => value !== "--");
31
+ const providerConfig = resolveProviderConfig({
32
+ provider: options.provider,
33
+ command: options.command,
34
+ args: passthroughArgs,
35
+ });
36
+ let gatewayUrl = options.gateway;
37
+ let gatewayHttpUrl;
38
+ let pairingGateway = options.pairingGateway;
39
+ let embeddedGatewayHandle;
40
+ if (!gatewayUrl) {
41
+ // Start built-in gateway
42
+ const { startEmbeddedGateway } = await import("@linkshell/gateway/embedded");
43
+ const port = Number(options.port);
44
+ const gw = await startEmbeddedGateway({
45
+ port,
46
+ logLevel: options.verbose ? "debug" : "warn",
47
+ silent: false,
48
+ });
49
+ embeddedGatewayHandle = gw;
50
+ gatewayUrl = gw.wsUrl;
51
+ gatewayHttpUrl = gw.httpUrl;
52
+ // Auto-detect LAN IP for QR code
53
+ const lanIp = getLanIp();
54
+ if (!pairingGateway && lanIp !== "127.0.0.1") {
55
+ pairingGateway = `http://${lanIp}:${gw.port}`;
56
+ }
57
+ process.stderr.write(`\n Built-in gateway started on port ${gw.port}\n`);
58
+ if (pairingGateway) {
59
+ process.stderr.write(` LAN address: ${pairingGateway}\n`);
60
+ }
61
+ process.stderr.write("\n");
62
+ }
63
+ else {
64
+ gatewayHttpUrl = gatewayUrl
65
+ .replace(/\/ws\/?$/, "")
66
+ .replace(/^wss:/, "https:")
67
+ .replace(/^ws:/, "http:");
68
+ }
69
+ const session = new BridgeSession({
70
+ gatewayUrl,
71
+ gatewayHttpUrl,
72
+ pairingGateway,
73
+ sessionId: options.sessionId,
74
+ cols: Number(options.cols),
75
+ rows: Number(options.rows),
76
+ clientName: options.clientName,
77
+ verbose: Boolean(options.verbose),
78
+ providerConfig,
79
+ });
80
+ // Clean up embedded gateway on exit
81
+ if (embeddedGatewayHandle) {
82
+ const cleanup = async () => {
83
+ await embeddedGatewayHandle.close();
84
+ };
85
+ process.on("SIGINT", () => { cleanup().then(() => process.exit(0)); });
86
+ process.on("SIGTERM", () => { cleanup().then(() => process.exit(0)); });
87
+ }
88
+ await session.start();
89
+ });
90
+ program
91
+ .command("doctor")
92
+ .description("Check your environment and connectivity")
93
+ .option("--gateway <url>", "Gateway URL to test", config.gateway)
94
+ .action(async (options) => {
95
+ await runDoctor(options.gateway);
96
+ });
97
+ program
98
+ .command("setup")
99
+ .description("Interactive setup wizard")
100
+ .action(async () => {
101
+ await runSetup();
102
+ });
103
+ program.parseAsync(process.argv).catch((error) => {
104
+ const message = error instanceof Error ? error.message : String(error);
105
+ process.stderr.write(`${message}\n`);
106
+ process.exit(1);
107
+ });
108
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export type ProviderName = "claude" | "codex" | "custom";
2
+ export interface ProviderConfig {
3
+ provider: ProviderName;
4
+ command: string;
5
+ args: string[];
6
+ env: NodeJS.ProcessEnv;
7
+ }
8
+ export declare function resolveProviderConfig(input: {
9
+ provider: ProviderName;
10
+ command?: string;
11
+ args: string[];
12
+ }): ProviderConfig;
@@ -0,0 +1,61 @@
1
+ import { execSync } from "node:child_process";
2
+ function which(bin) {
3
+ try {
4
+ return execSync(`which ${bin}`, { encoding: "utf8" }).trim() || undefined;
5
+ }
6
+ catch {
7
+ return undefined;
8
+ }
9
+ }
10
+ function resolveClaudeProvider(input) {
11
+ const command = input.command ?? "claude";
12
+ const resolved = which(command);
13
+ if (!resolved) {
14
+ throw new Error(`Claude CLI not found ("${command}"). Install it with: npm install -g @anthropic-ai/claude-code`);
15
+ }
16
+ // Claude starts an interactive REPL by default — that's exactly what we want in the PTY.
17
+ // Pass through any extra args the user provided.
18
+ return {
19
+ provider: "claude",
20
+ command: resolved,
21
+ args: input.args,
22
+ env: { ...process.env },
23
+ };
24
+ }
25
+ function resolveCodexProvider(input) {
26
+ const command = input.command ?? "codex";
27
+ const resolved = which(command);
28
+ if (!resolved) {
29
+ throw new Error(`Codex CLI not found ("${command}"). Install it with: npm install -g @openai/codex`);
30
+ }
31
+ if (!process.env.OPENAI_API_KEY) {
32
+ process.stderr.write("[warn] OPENAI_API_KEY not set — Codex may fail to authenticate\n");
33
+ }
34
+ return {
35
+ provider: "codex",
36
+ command: resolved,
37
+ args: input.args,
38
+ env: { ...process.env },
39
+ };
40
+ }
41
+ export function resolveProviderConfig(input) {
42
+ switch (input.provider) {
43
+ case "claude":
44
+ return resolveClaudeProvider(input);
45
+ case "codex":
46
+ return resolveCodexProvider(input);
47
+ case "custom": {
48
+ if (!input.command) {
49
+ throw new Error("custom provider requires --command");
50
+ }
51
+ const resolved = which(input.command);
52
+ return {
53
+ provider: "custom",
54
+ command: resolved ?? input.command,
55
+ args: input.args,
56
+ env: { ...process.env },
57
+ };
58
+ }
59
+ }
60
+ }
61
+ //# sourceMappingURL=providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.js","sourceRoot":"","sources":["../../../src/providers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAW9C,SAAS,KAAK,CAAC,GAAW;IACxB,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,SAAS,GAAG,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAAC,KAG9B;IACC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,QAAQ,CAAC;IAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,0BAA0B,OAAO,+DAA+D,CACjG,CAAC;IACJ,CAAC;IAED,yFAAyF;IACzF,iDAAiD;IACjD,OAAO;QACL,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE,QAAQ;QACjB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE;KACxB,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAAC,KAG7B;IACC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,OAAO,CAAC;IACzC,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,yBAAyB,OAAO,mDAAmD,CACpF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;QAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kEAAkE,CACnE,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,OAAO;QACjB,OAAO,EAAE,QAAQ;QACjB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE;KACxB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAIrC;IACC,QAAQ,KAAK,CAAC,QAAQ,EAAE,CAAC;QACvB,KAAK,QAAQ;YACX,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC;QACtC,KAAK,OAAO;YACV,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC;QACrC,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;gBACnB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;YACxD,CAAC;YACD,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACtC,OAAO;gBACL,QAAQ,EAAE,QAAQ;gBAClB,OAAO,EAAE,QAAQ,IAAI,KAAK,CAAC,OAAO;gBAClC,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE;aACxB,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { ProviderConfig } from "../providers.js";
2
+ export interface BridgeSessionOptions {
3
+ gatewayUrl: string;
4
+ gatewayHttpUrl: string;
5
+ pairingGateway?: string;
6
+ sessionId?: string;
7
+ cols: number;
8
+ rows: number;
9
+ clientName: string;
10
+ verbose?: boolean;
11
+ providerConfig: ProviderConfig;
12
+ }
13
+ export declare class BridgeSession {
14
+ private readonly options;
15
+ private socket;
16
+ private terminal;
17
+ private outputSeq;
18
+ private lastAckedSeq;
19
+ private scrollback;
20
+ private heartbeatTimer;
21
+ private reconnectTimer;
22
+ private reconnectAttempts;
23
+ private reconnecting;
24
+ private sessionId;
25
+ private exited;
26
+ private stopped;
27
+ constructor(options: BridgeSessionOptions);
28
+ private log;
29
+ start(): Promise<void>;
30
+ private createPairing;
31
+ private connectGateway;
32
+ private handleMessage;
33
+ private replayFrom;
34
+ private spawnTerminal;
35
+ private send;
36
+ private startHeartbeat;
37
+ private stopHeartbeat;
38
+ private scheduleReconnect;
39
+ stop(exitCode?: number): void;
40
+ }