@tcb-sandbox/cli 0.3.7

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.
@@ -0,0 +1,50 @@
1
+ import { TcbSandboxClient } from "@tcb-sandbox/sdk-js";
2
+ export { TcbSandboxClient };
3
+ export async function createSdkClient(clientClass, opts) {
4
+ const client = new clientClass({
5
+ baseUrl: opts.endpoint,
6
+ cloudbaseSessionId: opts.sessionId,
7
+ headers: opts.headers,
8
+ timeoutInSeconds: opts.timeoutMs ? Math.floor(opts.timeoutMs / 1000) : undefined,
9
+ });
10
+ return client;
11
+ }
12
+ export const TOOL_METHOD_MAP = {
13
+ bash: "execution.bash",
14
+ batch: "execution.batch",
15
+ read: "fileOperations.read",
16
+ write: "fileOperations.write",
17
+ edit: "fileOperations.edit",
18
+ filesUpload: "fileOperations.filesUpload",
19
+ filesDownload: "fileOperations.filesDownload",
20
+ grep: "search.grep",
21
+ glob: "search.glob",
22
+ ls: "search.ls",
23
+ ptyCreate: "ptyTerminal.ptyCreate",
24
+ ptySendInput: "ptyTerminal.ptySendInput",
25
+ ptyResize: "ptyTerminal.ptyResize",
26
+ ptyReadOutput: "ptyTerminal.ptyReadOutput",
27
+ ptyKill: "ptyTerminal.ptyKill",
28
+ set: "secrets.set",
29
+ get: "secrets.get",
30
+ list: "secrets.list",
31
+ delete: "secrets.delete",
32
+ ports: "preview.ports",
33
+ url: "preview.url",
34
+ push: "git.push",
35
+ capabilityList: "capabilitySystem.capabilityList",
36
+ capabilityInstall: "capabilitySystem.capabilityInstall",
37
+ capabilityRegister: "capabilitySystem.capabilityRegister",
38
+ capabilityRemove: "capabilitySystem.capabilityRemove",
39
+ capabilityDescribe: "capabilitySystem.capabilityDescribe",
40
+ capabilityInvoke: "capabilitySystem.capabilityInvoke",
41
+ listMcpServers: "mcpServerManagement.listMcpServers",
42
+ mcporterCli: "mcpServerManagement.mcporterCli",
43
+ addMcpServers: "mcpServerManagement.addMcpServers",
44
+ removeMcpServers: "mcpServerManagement.removeMcpServers",
45
+ workspaceSnapshot: "other.workspaceSnapshot",
46
+ workspaceSnapshotStatus: "other.workspaceSnapshotStatus",
47
+ workspaceRestore: "other.workspaceRestore",
48
+ health: "system.health",
49
+ apiDocs: "system.apiDocs",
50
+ };
@@ -0,0 +1,13 @@
1
+ export interface ServeOptions {
2
+ host: string;
3
+ port: number;
4
+ workspaceRoot: string;
5
+ }
6
+ export declare function resolveTrwMain(): string;
7
+ /** Default workspace root for local multi-session sandboxes. */
8
+ export declare function defaultWorkspaceRoot(): string;
9
+ export declare function printServeBanner(opts: ServeOptions, trwMain: string): void;
10
+ /**
11
+ * Spawn TRW with inherited stdio; resolve when the child exits.
12
+ */
13
+ export declare function runServe(opts: ServeOptions): Promise<number | null>;
package/dist/serve.js ADDED
@@ -0,0 +1,197 @@
1
+ import { execFileSync, spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ function readJsonVersion(pkgPath) {
8
+ try {
9
+ const raw = fs.readFileSync(pkgPath, "utf8");
10
+ const j = JSON.parse(raw);
11
+ return typeof j.version === "string" ? j.version : "?";
12
+ }
13
+ catch {
14
+ return "?";
15
+ }
16
+ }
17
+ function cliPackageVersion() {
18
+ return readJsonVersion(path.join(__dirname, "..", "package.json"));
19
+ }
20
+ const EMBEDDED_TRW = path.join(__dirname, "trw-embedded.js");
21
+ const RESOLVE_TRW_HINT = "Run `pnpm run build` in @tcb-sandbox/cli (embeds TCB). " +
22
+ "Or set TCB_SANDBOX_TRW_ENTRY to a built TCB file (…/tcb-remote-workspace/dist/index.js).";
23
+ export function resolveTcbEmbeddedMain() {
24
+ const tryPath = (p) => {
25
+ const abs = path.resolve(p);
26
+ try {
27
+ if (fs.existsSync(abs) && fs.statSync(abs).isFile())
28
+ return abs;
29
+ }
30
+ catch {
31
+ }
32
+ return null;
33
+ };
34
+ const env = process.env.TCB_SANDBOX_TRW_ENTRY?.trim();
35
+ if (env) {
36
+ const hit = tryPath(env);
37
+ if (hit)
38
+ return hit;
39
+ throw new Error(`TCB_SANDBOX_TRW_ENTRY points to missing file: ${path.resolve(env)}. ${RESOLVE_TRW_HINT}`);
40
+ }
41
+ const embedded = tryPath(EMBEDDED_TRW);
42
+ if (embedded)
43
+ return embedded;
44
+ throw new Error(`Embedded TCB not found at ${EMBEDDED_TRW}. ${RESOLVE_TRW_HINT}`);
45
+ }
46
+ function trwPackageVersion(trwMain) {
47
+ const dir = path.dirname(trwMain);
48
+ const versionJson = path.join(dir, "trw-version.json");
49
+ try {
50
+ if (fs.existsSync(versionJson)) {
51
+ const j = JSON.parse(fs.readFileSync(versionJson, "utf8"));
52
+ if (typeof j.version === "string")
53
+ return j.version;
54
+ }
55
+ }
56
+ catch {
57
+ }
58
+ return readJsonVersion(path.join(dir, "..", "package.json"));
59
+ }
60
+ function useColor() {
61
+ return Boolean(process.stdout.isTTY && !process.env.NO_COLOR && process.env.FORCE_COLOR !== "0");
62
+ }
63
+ function motdStyles() {
64
+ const on = useColor();
65
+ if (!on) {
66
+ return {
67
+ dim: (s) => s,
68
+ bold: (s) => s,
69
+ green: (s) => s,
70
+ yellow: (s) => s,
71
+ cyan: (s) => s,
72
+ };
73
+ }
74
+ const DIM = "\x1b[2m";
75
+ const BOLD = "\x1b[1m";
76
+ const GREEN = "\x1b[0;32m";
77
+ const YELLOW = "\x1b[0;33m";
78
+ const CYAN = "\x1b[0;36m";
79
+ const RESET = "\x1b[0m";
80
+ return {
81
+ dim: (s) => `${DIM}${s}${RESET}`,
82
+ bold: (s) => `${BOLD}${s}${RESET}`,
83
+ green: (s) => `${GREEN}${s}${RESET}`,
84
+ yellow: (s) => `${YELLOW}${s}${RESET}`,
85
+ cyan: (s) => `${CYAN}${s}${RESET}`,
86
+ };
87
+ }
88
+ function diskAvailableHuman(targetPath) {
89
+ if (process.platform === "win32") {
90
+ return "N/A";
91
+ }
92
+ try {
93
+ const out = execFileSync("df", ["-h", targetPath], { encoding: "utf8", timeout: 5000 }).trim();
94
+ const line = out.split("\n")[1];
95
+ if (!line)
96
+ return "N/A";
97
+ const parts = line.trim().split(/\s+/);
98
+ const avail = parts.length >= 4 ? parts[3] : "N/A";
99
+ return `${avail} available`;
100
+ }
101
+ catch {
102
+ return "N/A";
103
+ }
104
+ }
105
+ function tryExecVersion(file, args) {
106
+ try {
107
+ return execFileSync(file, args, { encoding: "utf8", timeout: 5000 }).trim();
108
+ }
109
+ catch {
110
+ return "?";
111
+ }
112
+ }
113
+ function bunVersion() {
114
+ const v = tryExecVersion("bun", ["-v"]);
115
+ return v === "?" ? "?" : v;
116
+ }
117
+ function pythonVersion() {
118
+ const line = tryExecVersion("python3", ["--version"]);
119
+ if (line === "?")
120
+ return "?";
121
+ const m = line.match(/(\d+\.\d+\.\d+)/);
122
+ return m ? m[1] : line.replace(/^Python\s+/i, "") || "?";
123
+ }
124
+ export function defaultWorkspaceRoot() {
125
+ return path.join(os.homedir(), ".tcb-sandbox", "workspaces");
126
+ }
127
+ const MOTD_ASCII = "\n ____ _ _ ____\n / ___| | ___ _ _ __| | __ ) __ _ ___ ___\n | | | |/ _ \\| | | |/ _` | _ \\ / _` / __|/ _ \\\n | |___| | (_) | |_| | (_| | |_) | (_| \\__ \\ __/\n \\____|_|\\___/ \\__,_|\\__,_|____/ \\__,_|___/\\___|\n Remote Workspace\n";
128
+ export function printServeBanner(opts, trwMain) {
129
+ const trwVer = trwPackageVersion(trwMain);
130
+ const cliVer = cliPackageVersion();
131
+ const displayHost = opts.host === "0.0.0.0" ? "127.0.0.1" : opts.host;
132
+ const base = `http://${displayHost}:${opts.port}`;
133
+ const wsRoot = path.resolve(opts.workspaceRoot);
134
+ const disk = diskAvailableHuman(wsRoot);
135
+ const nodeVer = process.version;
136
+ const S = motdStyles();
137
+ const lines = [];
138
+ lines.push(MOTD_ASCII.replace(/^\n/, "").replace(/\n$/, ""));
139
+ lines.push("");
140
+ lines.push(` ${S.dim("Listen :")} ${base}`);
141
+ lines.push(` ${S.dim("Session :")} ${S.bold("X-Cloudbase-Session-Id")} ${S.dim("(示例 test-03271546 → 子目录 test-03271546)")}`);
142
+ lines.push(` ${S.dim("Workspace:")} ${wsRoot}${path.sep}${S.dim("<session-id>")}`);
143
+ lines.push(` ${S.dim("Disk :")} ${disk}`);
144
+ lines.push(` ${S.dim("Runtime :")} Node ${nodeVer} · Bun ${bunVersion()} · Python ${pythonVersion()}`);
145
+ lines.push(` ${S.dim("Versions :")} TCB ${trwVer} · CLI ${cliVer}`);
146
+ lines.push("");
147
+ lines.push(` ${S.green("▸")} ${S.bold("快速开始")}`);
148
+ lines.push(` 打开 ${S.cyan(`${base}/reference`)} 查看 HTTP/MCP 说明;容器内还可读 ${S.bold(".workspace-info.md")}`);
149
+ lines.push("");
150
+ lines.push(` ${S.green("▸")} ${S.bold("功能")}`);
151
+ lines.push(` ${S.cyan(`${base}/preview/<port>/`)} dev server 预览${S.dim("(端口 1024–29999)")}`);
152
+ lines.push(` ${S.cyan("mcporter")} MCP 工具调用${S.dim("(list / call)")}`);
153
+ lines.push(` ${S.cyan("tcb-sandbox")} CLI 客户端${S.dim("(health / docs / snapshot / bash ...)")}`);
154
+ lines.push(` ${S.cyan("skills")} AI 技能管理${S.dim("(add / list / find)")}`);
155
+ lines.push(` ${S.cyan("tmux")} 多窗口${S.dim("(Ctrl-b c 新建 · Ctrl-b % 分屏)")}`);
156
+ lines.push("");
157
+ lines.push(` ${S.yellow("▸")} ${S.bold("须知")} ${S.dim("(本机 serve,非 SCF 容器)")}`);
158
+ lines.push(` ${S.dim("·")} 默认无鉴权;默认仅监听回环,${S.bold("--host 0.0.0.0")} 会暴露到网络,请自行评估风险`);
159
+ lines.push(` ${S.dim("·")} ${S.bold("Ctrl+C")} 停止本进程;无容器冻结 / TTL / 唤醒策略`);
160
+ lines.push(` ${S.dim("·")} 若系统 Python 受 PEP 668 限制,用 ${S.bold("uv")} ${S.dim("(推荐)")} 或 ${S.bold("python3 -m venv")}`);
161
+ lines.push(` ${S.dim("·")} 各 session 工作区在 ${S.bold(wsRoot + path.sep + "<session-id>")} 下;工具侧路径策略与 tcb-remote-workspace 一致`);
162
+ lines.push(` ${S.dim("·")} 长时间任务优先 ${S.bold("tmux")} 或 ${S.bold("tcb-sandbox pty")};子进程分离 ${S.bold("(cmd > log 2>&1 &)")} 仍可能随会话结束被清理`);
163
+ lines.push("");
164
+ process.stdout.write(lines.join("\n"));
165
+ }
166
+ export function runServe(opts) {
167
+ const trwMain = resolveTcbEmbeddedMain();
168
+ fs.mkdirSync(opts.workspaceRoot, { recursive: true });
169
+ printServeBanner(opts, trwMain);
170
+ const child = spawn(process.execPath, [trwMain], {
171
+ stdio: "inherit",
172
+ env: {
173
+ ...process.env,
174
+ HOST: opts.host,
175
+ PORT: String(opts.port),
176
+ WORKSPACE_ROOT: opts.workspaceRoot,
177
+ },
178
+ });
179
+ const forward = (sig) => {
180
+ try {
181
+ child.kill(sig);
182
+ }
183
+ catch {
184
+ }
185
+ };
186
+ process.on("SIGINT", () => forward("SIGINT"));
187
+ process.on("SIGTERM", () => forward("SIGTERM"));
188
+ return new Promise((resolve, reject) => {
189
+ child.on("error", reject);
190
+ child.on("close", (code, signal) => {
191
+ if (signal)
192
+ resolve(code);
193
+ else
194
+ resolve(code);
195
+ });
196
+ });
197
+ }