@delta-agentcloud/cli 1.1.28 → 1.1.32
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 +2 -2
- package/checksums.txt +5 -5
- package/package.json +1 -1
- package/scripts/install.js +131 -65
- package/skills/delta-sandbox/SKILL.md +2 -2
- package/skills/delta-sandbox/references/commands.md +2 -2
package/README.md
CHANGED
|
@@ -282,8 +282,8 @@ delta-cli sandbox operation <request_id>
|
|
|
282
282
|
| `delta-cli sandbox logs <id> --execution-id <eid> [--tail N --grep <pattern> --context N --max-bytes N]` | Show background logs; returns `stderr_size + stderr_tail` by default (avoids context blow-up); filter with `--tail/--grep` |
|
|
283
283
|
| `delta-cli sandbox cancel <id> --execution-id <eid>` | Interrupt a background command |
|
|
284
284
|
| `delta-cli sandbox read <id> --path <path> [--output <local path>] [--tail N] [--grep <pattern>] [--offset N] [--limit N] [--context N] [--max-bytes N] [--parse-json]` | Read a file in the container; `--output <path>` saves locally; filter with `--tail/--grep`. **A full copy (without `--tail/--grep/--offset/--limit/--max-bytes`) is byte-exact**: it goes through the chunked download path (binary-safe, sha256-verified — the envelope reports `byte_exact:true`, `integrity:"sha256"`, `digest_verified`); with any range filter it falls back to the text channel, where the server decodes as UTF-8 and replaces invalid bytes with U+FFFD, so the envelope then reports `byte_exact:false` / `integrity:"text_decoded"` and a warning is printed to stderr — **use `sandbox pull` to copy a binary file**. When the result is printed inline and the file is not valid UTF-8, `content` is the lossy text and the **exact original bytes** are in `content_base64` (base64-decode it); the envelope is marked `byte_exact:true`. Text files carry no `content_base64` |
|
|
285
|
-
| `delta-cli sandbox pull <id> --source <sandbox path> --target <local path> [--pattern <glob>] [--concurrency N] [--no-resume] [--no-verify]` | Pull files/dirs from the sandbox to local (mirror of `upload`; flag direction reversed: source=remote, target=local). **Directories are always pulled recursively** (`--recursive` is deprecated and no longer needed); the transfer uses the **chunked download** path: binary-safe, end-to-end **sha256** verified (`digest_verified`), **resumable** (a partial `<target>.delta.part` is reused), and **parallel** across files and chunks (`--concurrency`, default 4) — so a large file is not bound by a single command timeout. The server publishes its limits with every response (`limits`): 4 MiB chunks (16 MiB max), 100 GiB max per file, 120 s per chunk, 600 s for the whole-file digest. `--no-resume` ignores a leftover `.delta.part`; `--no-verify` skips the sandbox-side sha256 comparison |
|
|
286
|
-
| `delta-cli sandbox write <id> --path <path> --source <local path>` | Write a file. **Binary-safe**: the content travels as base64 and the CLI declares its `size`/`sha256`, which the server verifies before writing (a mismatch is rejected); the response carries `server_sha256`/`sha256`/`size`/`size_verified`. A `--source` above 32 MiB goes through the streaming upload path instead of being read into memory |
|
|
285
|
+
| `delta-cli sandbox pull <id> --source <sandbox path> --target <local path> [--pattern <glob>] [--concurrency N] [--no-resume] [--no-verify]` | Pull files/dirs from the sandbox to local (mirror of `upload`; flag direction reversed: source=remote, target=local). **Directories are always pulled recursively** (`--recursive` is deprecated and no longer needed); the transfer uses the **chunked download** path: binary-safe, end-to-end **sha256** verified (`digest_verified`), **resumable** (a partial `<target>.delta.part` is reused), and **parallel** across files and chunks (`--concurrency`, default 4) — so a large file is not bound by a single command timeout. The server publishes its limits with every response (`limits`): 4 MiB chunks (16 MiB max), 100 GiB max per file, 120 s per chunk, 600 s for the whole-file digest. `--no-resume` ignores a leftover `.delta.part`; `--no-verify` skips the sandbox-side sha256 comparison. **The payload moves as raw bytes** (the server's binary endpoint, so it is not inflated by base64; older servers fall back automatically), and **the chunks must tile the whole file**: a gap or an interrupted transfer is reported as a failure, never as a successful download |
|
|
286
|
+
| `delta-cli sandbox write <id> --path <path> --source <local path>` | Write a file. **Binary-safe**: the content travels as raw bytes (the server's binary endpoint, with a base64 fallback for older servers) and the CLI declares its `size`/`sha256`, which the server verifies before writing (a mismatch is rejected); the response carries `server_sha256`/`sha256`/`size`/`size_verified`. A `--source` above 32 MiB goes through the streaming upload path instead of being read into memory |
|
|
287
287
|
| `delta-cli sandbox write-multiple <id> --entry <remote path>=<local path>` | Write multiple files |
|
|
288
288
|
| `delta-cli sandbox ls <id> --path <path>` | List a directory |
|
|
289
289
|
| `delta-cli sandbox stat <id> --path <path>` | File metadata |
|
package/checksums.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
7b0f7b586921d997251890127fb983fb01ab8a2b3c06f8c4eccea108e5a96ebf delta-cli-darwin-amd64.tar.gz
|
|
2
|
+
e92b9722828a454dc57aeb6ea91f2810ea38fd617c42ecaa25f02f7502f68215 delta-cli-darwin-arm64.tar.gz
|
|
3
|
+
faecf702bca55268c33b5a916053e4fa600d6b0e8f19b9aac1645a4415df2a62 delta-cli-linux-amd64.tar.gz
|
|
4
|
+
31bd5981840b5822e9b20bf58458110d8ff5a182f3baf4940218029fbc6b9fda delta-cli-linux-arm64.tar.gz
|
|
5
|
+
3382cb55ce3535df1085366513736f14564b1086438c9841983a432bc4e3fa65 delta-cli-windows-amd64.zip
|
package/package.json
CHANGED
package/scripts/install.js
CHANGED
|
@@ -9,23 +9,38 @@ const crypto = require("crypto");
|
|
|
9
9
|
|
|
10
10
|
const PKG = "@delta-agentcloud/cli";
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
// The version of the package npm is installing. Its bundled checksums.txt is
|
|
13
|
+
// the only checksum source a mirror cannot tamper with, so the downloaded
|
|
14
|
+
// asset and the expected checksum always have to describe the same version.
|
|
15
|
+
const PACKAGED_VERSION = require("../package.json").version.replace(/-.*$/, "");
|
|
16
|
+
|
|
17
|
+
// The newest version the registry knows about. Used as a fallback when the
|
|
18
|
+
// packaged version has no matching release.
|
|
19
|
+
function registryLatestVersion() {
|
|
20
|
+
try {
|
|
21
|
+
const out = require("child_process").execFileSync("npm", ["view", PKG, "version"], {
|
|
22
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
23
|
+
timeout: 15000,
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
});
|
|
26
|
+
const ver = out.trim();
|
|
27
|
+
if (/^\d+\.\d+\.\d+/.test(ver)) return ver;
|
|
28
|
+
} catch {}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
// Resolve the version to download:
|
|
33
|
+
// env var > the packaged version > npm registry (fallback)
|
|
34
|
+
function resolveVersion() {
|
|
35
|
+
if (process.env.DELTA_CLI_VERSION) return process.env.DELTA_CLI_VERSION.replace(/^v/, "");
|
|
36
|
+
// The packaged version is what the user asked npm to install. Resolving to
|
|
37
|
+
// the registry's "latest" instead downloads the newest asset and verifies it
|
|
38
|
+
// against this package's older checksums.txt, which can never match.
|
|
39
|
+
if (PACKAGED_VERSION) return PACKAGED_VERSION;
|
|
40
|
+
return registryLatestVersion() || PACKAGED_VERSION;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const VERSION = resolveVersion();
|
|
29
44
|
const NAME = "delta-cli";
|
|
30
45
|
const REPO = "delta-agentcloud/delta-cli";
|
|
31
46
|
const DEFAULT_ALLOWED_HOSTS = new Set([
|
|
@@ -283,14 +298,7 @@ function extractZipWindows(archivePath, destDir) {
|
|
|
283
298
|
|
|
284
299
|
// ── Checksum ───────────────────────────────────────────────────────────────
|
|
285
300
|
|
|
286
|
-
function
|
|
287
|
-
const dir = checksumsDir || path.join(__dirname, "..");
|
|
288
|
-
const checksumsPath = path.join(dir, "checksums.txt");
|
|
289
|
-
if (!fs.existsSync(checksumsPath)) {
|
|
290
|
-
console.error("[WARN] checksums.txt not found, skipping checksum verification");
|
|
291
|
-
return null;
|
|
292
|
-
}
|
|
293
|
-
const content = fs.readFileSync(checksumsPath, "utf8");
|
|
301
|
+
function parseChecksums(content, archiveName) {
|
|
294
302
|
for (const line of content.split("\n")) {
|
|
295
303
|
const trimmed = line.trim();
|
|
296
304
|
if (!trimmed) continue;
|
|
@@ -303,6 +311,45 @@ function getExpectedChecksum(archiveName, checksumsDir) {
|
|
|
303
311
|
throw new Error(`Checksum entry not found for ${archiveName}`);
|
|
304
312
|
}
|
|
305
313
|
|
|
314
|
+
function getExpectedChecksum(archiveName, checksumsDir) {
|
|
315
|
+
const dir = checksumsDir || path.join(__dirname, "..");
|
|
316
|
+
const checksumsPath = path.join(dir, "checksums.txt");
|
|
317
|
+
if (!fs.existsSync(checksumsPath)) {
|
|
318
|
+
console.error("[WARN] checksums.txt not found, skipping checksum verification");
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
return parseChecksums(fs.readFileSync(checksumsPath, "utf8"), archiveName);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// The canonical URL of one file inside a release, optionally through the
|
|
325
|
+
// configured mirror.
|
|
326
|
+
function releaseFileUrl(version, name) {
|
|
327
|
+
const url = "https://github.com/" + REPO + "/releases/download/v" + version + "/" + name;
|
|
328
|
+
const mirror = process.env.DELTA_CLI_MIRROR || "";
|
|
329
|
+
if (mirror) return mirror.replace(/\/$/, "") + "/" + url;
|
|
330
|
+
return url;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// The checksum of the release that is actually being downloaded. The bundled
|
|
334
|
+
// checksums.txt only describes the packaged version, so any other version has
|
|
335
|
+
// to bring its own checksums.txt from that same release.
|
|
336
|
+
function expectedChecksumFor(version, archiveName, tmpDir) {
|
|
337
|
+
if (version === PACKAGED_VERSION) return getExpectedChecksum(archiveName);
|
|
338
|
+
const dest = path.join(tmpDir, "checksums-" + version + ".txt");
|
|
339
|
+
try {
|
|
340
|
+
download(releaseFileUrl(version, "checksums.txt"), dest);
|
|
341
|
+
} catch (e) {
|
|
342
|
+
console.error("[WARN] could not fetch checksums for v" + version + ": " + e.message + " — skipping checksum verification");
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
return parseChecksums(fs.readFileSync(dest, "utf8"), archiveName);
|
|
347
|
+
} catch (e) {
|
|
348
|
+
console.error("[WARN] " + e.message + " — skipping checksum verification");
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
306
353
|
function verifyChecksum(archivePath, expectedHash) {
|
|
307
354
|
if (expectedHash === null) return;
|
|
308
355
|
const hash = crypto.createHash("sha256");
|
|
@@ -326,66 +373,85 @@ function verifyChecksum(archivePath, expectedHash) {
|
|
|
326
373
|
|
|
327
374
|
// ── Main install ───────────────────────────────────────────────────────────
|
|
328
375
|
|
|
376
|
+
function extractAndInstall(tmpDir, archivePath) {
|
|
377
|
+
try {
|
|
378
|
+
if (isWindows) {
|
|
379
|
+
console.error("Extracting archive...");
|
|
380
|
+
extractZipWindows(archivePath, tmpDir);
|
|
381
|
+
} else {
|
|
382
|
+
console.error("Extracting archive...");
|
|
383
|
+
execFileSync("tar", ["-xzf", archivePath, "-C", tmpDir], { stdio: "ignore" });
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const binaryName = NAME + (isWindows ? ".exe" : "");
|
|
387
|
+
const extractedBinary = path.join(tmpDir, NAME + "-" + platform + "-" + arch);
|
|
388
|
+
if (fs.existsSync(extractedBinary)) {
|
|
389
|
+
fs.copyFileSync(extractedBinary, dest);
|
|
390
|
+
} else {
|
|
391
|
+
// Fallback: try the plain name (older release format)
|
|
392
|
+
const plainBinary = path.join(tmpDir, binaryName);
|
|
393
|
+
if (fs.existsSync(plainBinary)) {
|
|
394
|
+
fs.copyFileSync(plainBinary, dest);
|
|
395
|
+
} else {
|
|
396
|
+
// Search in tmpDir
|
|
397
|
+
const files = fs.readdirSync(tmpDir).filter(function(f) { return f.indexOf(NAME) !== -1 || f.indexOf(binaryName) !== -1; });
|
|
398
|
+
if (files.length > 0) {
|
|
399
|
+
fs.copyFileSync(path.join(tmpDir, files[0]), dest);
|
|
400
|
+
} else {
|
|
401
|
+
throw new Error("Binary not found in extracted archive. Files: " + fs.readdirSync(tmpDir).join(", "));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
fs.chmodSync(dest, 0o755);
|
|
406
|
+
ensureExecutable(path.join(__dirname, "run.js"));
|
|
407
|
+
console.error("Installed to " + dest);
|
|
408
|
+
} finally {
|
|
409
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
329
413
|
function install() {
|
|
330
414
|
fs.mkdirSync(binDir, { recursive: true });
|
|
331
415
|
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "delta-cli-"));
|
|
332
416
|
var archivePath = path.join(tmpDir, archiveName);
|
|
333
417
|
|
|
334
|
-
|
|
335
|
-
|
|
418
|
+
// The packaged version first; the registry's latest is a fallback for a
|
|
419
|
+
// package whose version has no matching release.
|
|
420
|
+
var candidates = [VERSION];
|
|
421
|
+
var latest = registryLatestVersion();
|
|
422
|
+
if (latest && latest !== VERSION) candidates.push(latest);
|
|
423
|
+
|
|
424
|
+
var vi = 0;
|
|
425
|
+
function tryNextVersion() {
|
|
426
|
+
if (vi >= candidates.length) {
|
|
427
|
+
console.error("[FAIL] delta-cli could not be installed from any source");
|
|
428
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
429
|
+
process.exit(1);
|
|
430
|
+
}
|
|
431
|
+
var version = candidates[vi++];
|
|
432
|
+
releaseAssetUrls(version, archiveName, function(downloadUrls) {
|
|
336
433
|
var lastErr;
|
|
337
|
-
var downloaded = false;
|
|
338
434
|
for (var i = 0; i < downloadUrls.length; i++) {
|
|
339
435
|
try {
|
|
340
436
|
console.error("Downloading from " + downloadUrls[i]);
|
|
341
437
|
download(downloadUrls[i], archivePath);
|
|
342
438
|
console.error("Download complete, verifying checksum...");
|
|
343
|
-
const expectedHash =
|
|
439
|
+
const expectedHash = expectedChecksumFor(version, archiveName, tmpDir);
|
|
344
440
|
verifyChecksum(archivePath, expectedHash);
|
|
345
441
|
console.error("Checksum verified");
|
|
346
|
-
|
|
347
|
-
|
|
442
|
+
extractAndInstall(tmpDir, archivePath);
|
|
443
|
+
return;
|
|
348
444
|
} catch (e) {
|
|
349
445
|
console.error("Failed: " + e.message + " (" + downloadUrls[i] + ")");
|
|
350
446
|
lastErr = e;
|
|
351
447
|
}
|
|
352
448
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
extractZipWindows(archivePath, tmpDir);
|
|
358
|
-
} else {
|
|
359
|
-
console.error("Extracting archive...");
|
|
360
|
-
execFileSync("tar", ["-xzf", archivePath, "-C", tmpDir], { stdio: "ignore" });
|
|
361
|
-
}
|
|
449
|
+
console.error("Version v" + version + " could not be installed: " + (lastErr ? lastErr.message : "no source responded"));
|
|
450
|
+
tryNextVersion();
|
|
451
|
+
});
|
|
452
|
+
}
|
|
362
453
|
|
|
363
|
-
|
|
364
|
-
const extractedBinary = path.join(tmpDir, NAME + "-" + platform + "-" + arch);
|
|
365
|
-
if (fs.existsSync(extractedBinary)) {
|
|
366
|
-
fs.copyFileSync(extractedBinary, dest);
|
|
367
|
-
} else {
|
|
368
|
-
// Fallback: try the plain name (older release format)
|
|
369
|
-
const plainBinary = path.join(tmpDir, binaryName);
|
|
370
|
-
if (fs.existsSync(plainBinary)) {
|
|
371
|
-
fs.copyFileSync(plainBinary, dest);
|
|
372
|
-
} else {
|
|
373
|
-
// Search in tmpDir
|
|
374
|
-
const files = fs.readdirSync(tmpDir).filter(function(f) { return f.indexOf(NAME) !== -1 || f.indexOf(binaryName) !== -1; });
|
|
375
|
-
if (files.length > 0) {
|
|
376
|
-
fs.copyFileSync(path.join(tmpDir, files[0]), dest);
|
|
377
|
-
} else {
|
|
378
|
-
throw new Error("Binary not found in extracted archive. Files: " + fs.readdirSync(tmpDir).join(", "));
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
fs.chmodSync(dest, 0o755);
|
|
383
|
-
ensureExecutable(path.join(__dirname, "run.js"));
|
|
384
|
-
console.error("Installed to " + dest);
|
|
385
|
-
} finally {
|
|
386
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
387
|
-
}
|
|
388
|
-
});
|
|
454
|
+
tryNextVersion();
|
|
389
455
|
}
|
|
390
456
|
|
|
391
457
|
function ensureExecutable(filePath) {
|
|
@@ -175,9 +175,9 @@ The reason is that, after running a command, this skill reads `log_file` from th
|
|
|
175
175
|
| Reclaim a tunnel | `sandbox dispose <id> --port N` — explicitly reclaim a single tunnel (stop frpc and release the public port); killing the sandbox also reclaims all its tunnels |
|
|
176
176
|
| **File operations** | |
|
|
177
177
|
| Read a file | `sandbox read <id> --path <path> [--output <local-path> --tail N --grep <pattern> --offset N --limit N --context N --max-bytes N --parse-json]` — **a full copy is byte-exact** (chunked download, sha256-verified); with `--tail`/`--grep`/`--offset`/`--limit` it uses the text channel and the envelope reports `byte_exact:false` — use `sandbox pull` for binary files. Printed inline, a non-UTF-8 file puts the **exact bytes** in `content_base64` (`content` is the lossy text) |
|
|
178
|
-
| Pull a file/directory | `sandbox pull <id> --source <sandbox-path> --target <local-path> [--pattern <glob>] [--concurrency N] [--no-resume] [--no-verify]` — pull a single file or **recursively** pull a directory (directories are always recursive), over the **chunked** path: binary-safe, sha256-verified, resumable and parallel (the reverse of upload: source=remote, target=local) |
|
|
178
|
+
| Pull a file/directory | `sandbox pull <id> --source <sandbox-path> --target <local-path> [--pattern <glob>] [--concurrency N] [--no-resume] [--no-verify]` — pull a single file or **recursively** pull a directory (directories are always recursive), over the **chunked** path: bytes move verbatim (no base64 inflation), binary-safe, sha256-verified, resumable and parallel (the reverse of upload: source=remote, target=local) |
|
|
179
179
|
| View the working directory | `sandbox working-directory <id>` (returns `data.path`, such as `/workspace/{user_id}/{sandbox_id}`) |
|
|
180
|
-
| Write a file | `sandbox write <id> --source <filename>` (without `--path`, writes to the working directory by default); **every sandbox-side path** — `--path`, the remote part of `write-multiple --entry`, `upload --target`, `pull --source`, `run`/`run-bg --log-file` — is prefixed with the working directory when relative, while an absolute path is used as given. The content is **binary-safe** (base64) and the server verifies the declared `size`/`sha256` before writing, so the response's `server_sha256`/`size_verified` can be compared with the local file |
|
|
180
|
+
| Write a file | `sandbox write <id> --source <filename>` (without `--path`, writes to the working directory by default); **every sandbox-side path** — `--path`, the remote part of `write-multiple --entry`, `upload --target`, `pull --source`, `run`/`run-bg --log-file` — is prefixed with the working directory when relative, while an absolute path is used as given. The content is **binary-safe** (raw bytes, with a base64 fallback for older servers) and the server verifies the declared `size`/`sha256` before writing, so the response's `server_sha256`/`size_verified` can be compared with the local file |
|
|
181
181
|
| Write multiple files | `sandbox write-multiple <id> --entry <remote-path>=<local-path> [--entry ...]` |
|
|
182
182
|
| Upload a directory | `sandbox upload <id> --source <local-directory> --target <sandbox-path>` |
|
|
183
183
|
| List a directory | `sandbox ls <id> --path <path>` (defaults to `.`) |
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
|------|------|
|
|
41
41
|
| `sandbox read <id> --path <path> [--output <local-path>] [--tail N] [--grep <pattern>] [--offset N] [--limit N] [--context N] [--max-bytes N] [--parse-json]` | Read a file; returns `content` + `size` + `content_length`. `--output <path>` saves it locally; `--tail`/`--grep` filter the output. **A full copy (no `--tail`/`--grep`/`--offset`/`--limit`/`--max-bytes`) is byte-exact**: it goes through the chunked download path (binary-safe, sha256-verified — the envelope reports `byte_exact:true`, `integrity:"sha256"`, `digest_verified`). With any range filter it falls back to the text channel, where the server decodes as UTF-8 and replaces invalid bytes with U+FFFD; the envelope then reports `byte_exact:false` / `integrity:"text_decoded"` and a warning is printed to stderr. **Use `sandbox pull` to copy a binary file.** When the result is printed inline and the file is not valid UTF-8, `content` is the lossy text and the **exact original bytes** are in `content_base64` (base64-decode it); the envelope is marked `byte_exact:true`. Text files carry no `content_base64`. |
|
|
42
42
|
| `sandbox working-directory <id>` | Get the sandbox's current working directory (absolute path `/workspace/{user_id}/{sandbox_id}`). |
|
|
43
|
-
| `sandbox write <id> [--path <path>] --source <filename>` | Write a file (recommended). If `--path` is omitted, write to the working directory (`<working-directory>/<filename>`); a relative `--path` is also resolved under the working directory. An absolute `--path` is used as-is (but the `/workspace/<filename>` root path is outside the OSS sync scope and is not recommended). **Binary-safe**: the content travels as base64 and the CLI declares its `size`/`sha256`, which the server verifies before writing (a mismatch is rejected). The response carries `server_sha256`/`sha256`/`size`/`size_verified`. |
|
|
43
|
+
| `sandbox write <id> [--path <path>] --source <filename>` | Write a file (recommended). If `--path` is omitted, write to the working directory (`<working-directory>/<filename>`); a relative `--path` is also resolved under the working directory. An absolute `--path` is used as-is (but the `/workspace/<filename>` root path is outside the OSS sync scope and is not recommended). **Binary-safe**: the content travels as raw bytes (the server's binary endpoint, with a base64 fallback for older servers) and the CLI declares its `size`/`sha256`, which the server verifies before writing (a mismatch is rejected). The response carries `server_sha256`/`sha256`/`size`/`size_verified`. |
|
|
44
44
|
| `sandbox write <id> --path <absolute-path> --data "..."` | Write a small amount of inline content (`--data` has no source filename, so `--path` must be specified explicitly). |
|
|
45
45
|
| `sandbox write-multiple <id> --entry <remote-path>=<local-path> [--entry ...]` | Write multiple files in one operation (a relative target `path` is also resolved under the working directory). |
|
|
46
|
-
| `sandbox pull <id> --source <sandbox-path> --target <local-path> [--pattern <glob>] [--concurrency N] [--no-resume] [--no-verify]` | Pull files/directories to the local machine (the reverse of `upload`; flag directions are reversed: `--source`=remote sandbox path, `--target`=local path). Single files are detected automatically; **directories are always traversed recursively** (`--recursive` is deprecated and no longer needed). The transfer uses the **chunked download** path: binary-safe, end-to-end **sha256** verified (`digest_verified`), **resumable** (a partial `<target>.delta.part` is reused), and **parallel** across files and chunks (`--concurrency`, default 4) — so a large file is not bound by a single command timeout. The server publishes its limits with every response (`limits`): 4 MiB chunks (16 MiB max), 100 GiB max per file, 120 s per chunk, 600 s for the whole-file digest. `--no-resume` ignores a leftover `.delta.part`; `--no-verify` skips the sandbox-side sha256 comparison. |
|
|
46
|
+
| `sandbox pull <id> --source <sandbox-path> --target <local-path> [--pattern <glob>] [--concurrency N] [--no-resume] [--no-verify]` | Pull files/directories to the local machine (the reverse of `upload`; flag directions are reversed: `--source`=remote sandbox path, `--target`=local path). Single files are detected automatically; **directories are always traversed recursively** (`--recursive` is deprecated and no longer needed). The transfer uses the **chunked download** path: the bytes move verbatim (no base64 inflation) when the server offers the binary endpoint, binary-safe, end-to-end **sha256** verified (`digest_verified`), **resumable** (a partial `<target>.delta.part` is reused), and **parallel** across files and chunks (`--concurrency`, default 4) — so a large file is not bound by a single command timeout. The server publishes its limits with every response (`limits`): 4 MiB chunks (16 MiB max), 100 GiB max per file, 120 s per chunk, 600 s for the whole-file digest. `--no-resume` ignores a leftover `.delta.part`; `--no-verify` skips the sandbox-side sha256 comparison. |
|
|
47
47
|
| `sandbox upload <id> --source <local-directory> --target <sandbox-path>` | Upload a local directory (packaged as tar.gz, extracted server-side) and verify the resulting file list against the local manifest. `--source` is local, `--target` is inside the sandbox. |
|
|
48
48
|
| `sandbox ls <id> --path <path>` | List directory contents (defaults to `.`). |
|
|
49
49
|
| `sandbox stat <id> --path <path>` | Get file metadata (size / mode / owner / group). |
|