@tcb-sandbox/cli 0.3.9 → 0.4.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 +8 -2
- package/dist/bundled-docs.js +5 -36
- package/dist/cli.js +29 -69
- package/dist/serve.js +14 -3
- package/dist/trw-embedded.js +1206 -1292
- package/dist/trw-version.json +1 -1
- package/docs/local-mode.md +33 -6
- package/docs/quick-start.md +1 -1
- package/docs/thin-client.md +4 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -32,8 +32,8 @@ tcb-sandbox --session-id my-session read --path hello.txt
|
|
|
32
32
|
## 环境变量
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
export
|
|
36
|
-
export
|
|
35
|
+
export TCB_SANDBOX_ENDPOINT=https://your-gateway.com
|
|
36
|
+
export TCB_SANDBOX_SESSION_ID=your-session-id
|
|
37
37
|
|
|
38
38
|
# 然后可直接使用
|
|
39
39
|
tcb-sandbox health
|
|
@@ -60,6 +60,12 @@ tcb-sandbox bash --command "ls -la"
|
|
|
60
60
|
| `pty` | PTY 终端 |
|
|
61
61
|
| `snapshot` | 快照管理 |
|
|
62
62
|
|
|
63
|
+
## 测试说明
|
|
64
|
+
|
|
65
|
+
- `pnpm test`:CLI 契约回归(mock HTTP)
|
|
66
|
+
- 不包含:`serve` 真实内嵌 TRW 行为验证
|
|
67
|
+
- 跨门面(HTTP/MCP/CLI/E2B)完整矩阵请使用 sibling `../trw-example` 的 `pnpm start` 或 `pnpm test:full`
|
|
68
|
+
|
|
63
69
|
## 本地模式详情
|
|
64
70
|
|
|
65
71
|
```bash
|
package/dist/bundled-docs.js
CHANGED
|
@@ -486,46 +486,15 @@ export const BUNDLED_API_DOCS = {
|
|
|
486
486
|
notes: ["Bundled fallback docs snapshot."],
|
|
487
487
|
},
|
|
488
488
|
{
|
|
489
|
-
name: "
|
|
490
|
-
description: "
|
|
491
|
-
endpoint: "POST /api/tools/
|
|
489
|
+
name: "workspace_persist",
|
|
490
|
+
description: "Immediately persist workspace to COS via rsync. Returns elapsed time in ms.",
|
|
491
|
+
endpoint: "POST /api/tools/workspace_persist",
|
|
492
492
|
input_schema: {
|
|
493
493
|
type: "object",
|
|
494
|
-
properties: {
|
|
495
|
-
exclude: { type: "array", items: { type: "string" } },
|
|
496
|
-
include_all: { type: "boolean" },
|
|
497
|
-
},
|
|
498
|
-
},
|
|
499
|
-
limits: { max_concurrent: 2 },
|
|
500
|
-
notes: ["Bundled fallback docs snapshot."],
|
|
501
|
-
},
|
|
502
|
-
{
|
|
503
|
-
name: "workspace_snapshot_status",
|
|
504
|
-
description: "Check snapshot task status by snapshot_id or list all session tasks.",
|
|
505
|
-
endpoint: "POST /api/tools/workspace_snapshot_status",
|
|
506
|
-
input_schema: {
|
|
507
|
-
type: "object",
|
|
508
|
-
properties: {
|
|
509
|
-
snapshot_id: { type: "string" },
|
|
510
|
-
},
|
|
494
|
+
properties: {},
|
|
511
495
|
},
|
|
512
496
|
limits: {},
|
|
513
|
-
notes: ["Bundled fallback docs
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
name: "workspace_restore",
|
|
517
|
-
description: 'Restore workspace from snapshot. mode="merge" overlays, mode="replace" cleans first.',
|
|
518
|
-
endpoint: "POST /api/tools/workspace_restore",
|
|
519
|
-
input_schema: {
|
|
520
|
-
type: "object",
|
|
521
|
-
required: ["snapshot_id"],
|
|
522
|
-
properties: {
|
|
523
|
-
snapshot_id: { type: "string" },
|
|
524
|
-
mode: { type: "string", enum: ["merge", "replace"] },
|
|
525
|
-
},
|
|
526
|
-
},
|
|
527
|
-
limits: {},
|
|
528
|
-
notes: ["Bundled fallback docs snapshot."],
|
|
497
|
+
notes: ["Bundled fallback docs."],
|
|
529
498
|
},
|
|
530
499
|
],
|
|
531
500
|
};
|
package/dist/cli.js
CHANGED
|
@@ -5,9 +5,8 @@ import process from "node:process";
|
|
|
5
5
|
import { TcbSandboxClient } from "@tcb-sandbox/sdk-js";
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
import { BUNDLED_API_DOCS } from "./bundled-docs.js";
|
|
8
|
-
import { defaultWorkspaceRoot, runServe } from "./serve.js";
|
|
8
|
+
import { defaultMultiSessionWorkspaceRoot, defaultWorkspaceRoot, runServe } from "./serve.js";
|
|
9
9
|
const DEFAULT_TIMEOUT = 600_000;
|
|
10
|
-
const DEFAULT_ACCEPT_HEADER = "application/problem+json, application/json;q=0.9, text/markdown;q=0.8, */*;q=0.1";
|
|
11
10
|
const LOG_PRIORITIES = {
|
|
12
11
|
debug: 10,
|
|
13
12
|
info: 20,
|
|
@@ -92,17 +91,6 @@ function resolveLogFormat(opts) {
|
|
|
92
91
|
function shouldLog(ctx, level) {
|
|
93
92
|
return LOG_PRIORITIES[level] >= LOG_PRIORITIES[ctx.logLevel];
|
|
94
93
|
}
|
|
95
|
-
function maskHeaderValue(key, value) {
|
|
96
|
-
const lower = key.toLowerCase();
|
|
97
|
-
if (lower.includes("authorization") ||
|
|
98
|
-
lower.includes("api-key") ||
|
|
99
|
-
lower.includes("token") ||
|
|
100
|
-
lower.includes("cookie") ||
|
|
101
|
-
lower.includes("session")) {
|
|
102
|
-
return "***";
|
|
103
|
-
}
|
|
104
|
-
return value;
|
|
105
|
-
}
|
|
106
94
|
const SENSITIVE_JSON_KEY_RE = /(authorization|api[_-]?key|token|secret|password|cookie|session|value)/i;
|
|
107
95
|
function redactJsonForDebug(input) {
|
|
108
96
|
if (Array.isArray(input)) {
|
|
@@ -262,7 +250,7 @@ Quick Start
|
|
|
262
250
|
tcb-sandbox serve # alias: local — embedded TRW on loopback
|
|
263
251
|
tcb-sandbox --endpoint http://127.0.0.1:9000 health
|
|
264
252
|
tcb-sandbox --endpoint http://127.0.0.1:9000 docs
|
|
265
|
-
tcb-sandbox --endpoint http://127.0.0.1:9000 --session-id demo
|
|
253
|
+
tcb-sandbox --endpoint http://127.0.0.1:9000 --session-id demo read README.md
|
|
266
254
|
|
|
267
255
|
Error Handling
|
|
268
256
|
HTTP failures are parsed as RFC 9457 problem details when available.
|
|
@@ -271,17 +259,23 @@ Error Handling
|
|
|
271
259
|
program
|
|
272
260
|
.command("serve")
|
|
273
261
|
.alias("local")
|
|
274
|
-
.description("Run embedded tcb-remote-workspace locally (multi-session
|
|
262
|
+
.description("Run embedded tcb-remote-workspace locally. Default: single-workspace (no session header needed). Use --multi-session for session-based subdirectories.")
|
|
275
263
|
.option("--port <n>", "Listen port", "9000")
|
|
276
264
|
.option("--host <addr>", "Bind address (use 0.0.0.0 for all interfaces)", "127.0.0.1")
|
|
277
|
-
.option("--workspace-root <path>", "
|
|
265
|
+
.option("--workspace-root <path>", "Workspace directory (single mode) or parent directory for session subdirectories (multi-session mode)")
|
|
266
|
+
.option("--multi-session", "Enable multi-session mode: each X-Cloudbase-Session-Id creates a subdirectory under workspace-root", false)
|
|
278
267
|
.action(async function (opts) {
|
|
279
268
|
const port = asInt(opts.port);
|
|
280
|
-
const
|
|
269
|
+
const resolvedRoot = opts.workspaceRoot
|
|
270
|
+
? path.resolve(opts.workspaceRoot)
|
|
271
|
+
: opts.multiSession
|
|
272
|
+
? defaultMultiSessionWorkspaceRoot()
|
|
273
|
+
: defaultWorkspaceRoot();
|
|
281
274
|
const code = await runServe({
|
|
282
275
|
host: opts.host,
|
|
283
276
|
port,
|
|
284
|
-
workspaceRoot,
|
|
277
|
+
workspaceRoot: resolvedRoot,
|
|
278
|
+
multiSession: opts.multiSession,
|
|
285
279
|
});
|
|
286
280
|
process.exit(code === null ? 0 : code);
|
|
287
281
|
});
|
|
@@ -310,7 +304,7 @@ Error Handling
|
|
|
310
304
|
const response = await client.system.apiDocs();
|
|
311
305
|
docs = ("data" in response ? response.data : response);
|
|
312
306
|
}
|
|
313
|
-
catch (
|
|
307
|
+
catch (_err) {
|
|
314
308
|
logMessage(ctx, "warn", "GET /api/docs unavailable, using bundled fallback docs");
|
|
315
309
|
docs = BUNDLED_API_DOCS;
|
|
316
310
|
}
|
|
@@ -731,61 +725,27 @@ Error Handling
|
|
|
731
725
|
return printJson(result);
|
|
732
726
|
printPretty("Capability Invoke", result);
|
|
733
727
|
});
|
|
734
|
-
|
|
735
|
-
.command("
|
|
736
|
-
.description("
|
|
737
|
-
|
|
738
|
-
.command("create")
|
|
739
|
-
.description("Create async workspace snapshot and upload to S3")
|
|
740
|
-
.option("--exclude <patterns...>", "Additional exclude patterns")
|
|
741
|
-
.option("--include-all", "Skip default excludes (only sensitive files still excluded)")
|
|
742
|
-
.action(async function (options) {
|
|
743
|
-
const ctx = buildContext(this);
|
|
744
|
-
ensureSessionId(ctx);
|
|
745
|
-
const client = createSdkClientFromContext(ctx);
|
|
746
|
-
logMessage(ctx, "info", "POST /api/tools/workspace_snapshot");
|
|
747
|
-
const response = await client.other.workspaceSnapshot({
|
|
748
|
-
exclude: options.exclude,
|
|
749
|
-
include_all: options.includeAll,
|
|
750
|
-
});
|
|
751
|
-
const result = "data" in response ? response.data : response;
|
|
752
|
-
if (ctx.output === "json")
|
|
753
|
-
return printJson(result);
|
|
754
|
-
printPretty("Snapshot Create", result);
|
|
755
|
-
});
|
|
756
|
-
snapshot
|
|
757
|
-
.command("status [snapshotId]")
|
|
758
|
-
.description("Check snapshot status (single or all session tasks)")
|
|
759
|
-
.action(async function (snapshotId) {
|
|
760
|
-
const ctx = buildContext(this);
|
|
761
|
-
ensureSessionId(ctx);
|
|
762
|
-
const client = createSdkClientFromContext(ctx);
|
|
763
|
-
logMessage(ctx, "info", "POST /api/tools/workspace_snapshot_status");
|
|
764
|
-
const response = await client.other.workspaceSnapshotStatus({
|
|
765
|
-
snapshot_id: snapshotId,
|
|
766
|
-
});
|
|
767
|
-
const result = "data" in response ? response.data : response;
|
|
768
|
-
if (ctx.output === "json")
|
|
769
|
-
return printJson(result);
|
|
770
|
-
printPretty("Snapshot Status", result);
|
|
771
|
-
});
|
|
772
|
-
snapshot
|
|
773
|
-
.command("restore <snapshotId>")
|
|
774
|
-
.description("Restore workspace from snapshot (supports cross-session)")
|
|
775
|
-
.option("--mode <mode>", 'Restore mode: "merge" (default) or "replace"', "merge")
|
|
776
|
-
.action(async function (snapshotId, options) {
|
|
728
|
+
program
|
|
729
|
+
.command("persist")
|
|
730
|
+
.description("Persist workspace to COS storage immediately (rsync)")
|
|
731
|
+
.action(async function () {
|
|
777
732
|
const ctx = buildContext(this);
|
|
778
733
|
ensureSessionId(ctx);
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
const response = await
|
|
782
|
-
|
|
783
|
-
|
|
734
|
+
logMessage(ctx, "info", "POST /api/tools/workspace_persist");
|
|
735
|
+
const url = `${ctx.endpoint}/api/tools/workspace_persist`;
|
|
736
|
+
const response = await fetch(url, {
|
|
737
|
+
method: "POST",
|
|
738
|
+
headers: {
|
|
739
|
+
"Content-Type": "application/json",
|
|
740
|
+
...ctx.headers,
|
|
741
|
+
},
|
|
742
|
+
body: JSON.stringify({}),
|
|
743
|
+
signal: AbortSignal.timeout(ctx.timeout),
|
|
784
744
|
});
|
|
785
|
-
const result =
|
|
745
|
+
const result = await response.json();
|
|
786
746
|
if (ctx.output === "json")
|
|
787
747
|
return printJson(result);
|
|
788
|
-
printPretty("
|
|
748
|
+
printPretty("Persist", result);
|
|
789
749
|
});
|
|
790
750
|
const secrets = program.command("secrets").description("Secrets helpers (session-scoped)");
|
|
791
751
|
secrets
|
package/dist/serve.js
CHANGED
|
@@ -123,6 +123,9 @@ function pythonVersion() {
|
|
|
123
123
|
return m ? m[1] : line.replace(/^Python\s+/i, "") || "?";
|
|
124
124
|
}
|
|
125
125
|
export function defaultWorkspaceRoot() {
|
|
126
|
+
return path.join(os.homedir(), ".tcb-sandbox", "workspace");
|
|
127
|
+
}
|
|
128
|
+
export function defaultMultiSessionWorkspaceRoot() {
|
|
126
129
|
return path.join(os.homedir(), ".tcb-sandbox", "workspaces");
|
|
127
130
|
}
|
|
128
131
|
const MOTD_ASCII = "\n ____ _ _ ____\n / ___| | ___ _ _ __| | __ ) __ _ ___ ___\n | | | |/ _ \\| | | |/ _` | _ \\ / _` / __|/ _ \\\n | |___| | (_) | |_| | (_| | |_) | (_| \\__ \\ __/\n \\____|_|\\___/ \\__,_|\\__,_|____/ \\__,_|___/\\___|\n Remote Workspace\n";
|
|
@@ -139,8 +142,14 @@ export function printServeBanner(opts, trwMain, sandboxStatus) {
|
|
|
139
142
|
lines.push(MOTD_ASCII.replace(/^\n/, "").replace(/\n$/, ""));
|
|
140
143
|
lines.push("");
|
|
141
144
|
lines.push(` ${S.dim("Listen :")} ${base}`);
|
|
142
|
-
|
|
143
|
-
|
|
145
|
+
if (opts.multiSession) {
|
|
146
|
+
lines.push(` ${S.dim("Session :")} ${S.bold("X-Cloudbase-Session-Id")} ${S.dim("(multi-session mode; each session → subdirectory)")}`);
|
|
147
|
+
lines.push(` ${S.dim("Workspace:")} ${wsRoot}${path.sep}${S.dim("<session-id>")}`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
lines.push(` ${S.dim("Session :")} ${S.dim("(single-workspace mode; no session header needed)")}`);
|
|
151
|
+
lines.push(` ${S.dim("Workspace:")} ${wsRoot}`);
|
|
152
|
+
}
|
|
144
153
|
lines.push(` ${S.dim("Disk :")} ${disk}`);
|
|
145
154
|
if (sandboxStatus) {
|
|
146
155
|
const sandboxColor = sandboxStatus.startsWith("active") ? S.green : S.yellow;
|
|
@@ -193,7 +202,7 @@ async function isPortInUse(host, port) {
|
|
|
193
202
|
const SHUTDOWN_TIMEOUT_MS = 10_000;
|
|
194
203
|
const MAX_RESTART_COUNT = 3;
|
|
195
204
|
const MIN_UPTIME_FOR_HEALTHY_MS = 5_000;
|
|
196
|
-
function ensureMcporterConfig(
|
|
205
|
+
function ensureMcporterConfig(_workspaceRoot) {
|
|
197
206
|
const mcporterDir = path.join(os.homedir(), ".mcporter");
|
|
198
207
|
const configPath = path.join(mcporterDir, "mcporter.json");
|
|
199
208
|
if (fs.existsSync(configPath))
|
|
@@ -239,6 +248,8 @@ export async function runServe(opts) {
|
|
|
239
248
|
HOST: opts.host,
|
|
240
249
|
PORT: String(opts.port),
|
|
241
250
|
WORKSPACE_ROOT: opts.workspaceRoot,
|
|
251
|
+
WORKSPACE_MODE: opts.multiSession ? "multi" : "single",
|
|
252
|
+
LOCAL_MODE: "true",
|
|
242
253
|
};
|
|
243
254
|
let shutdownRequested = false;
|
|
244
255
|
let restartCount = 0;
|