@delta-agentcloud/cli 1.1.30 → 1.1.34

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 CHANGED
@@ -281,7 +281,7 @@ delta-cli sandbox operation <request_id>
281
281
  | `delta-cli sandbox run-bg <id> --command "..." [--timeout <sec>] [--wait] [--cancel-on-timeout] [--format sse\|json] [--json-result] [--summary/--no-summary] [--artifacts]` | Run a command in the background; without `--wait` it returns `{execution_id, sandbox_id}` immediately (with `--format json`, the unified result object with `finished=false`); with `--wait` it follows via SSE (raw `data:` frames to stdout, no trailing envelope); `--format json` prints exactly one envelope with the same structure as `sandbox run --format json`. **On a wait timeout** → `error.type=timeout` and a **non-zero** process exit code, with `error.details` keeping `sandbox_id`/`execution_id`/`remote_state` (`not_cancelled`/`cancelled`/`cancel_failed`) so the command can be queried or cancelled afterwards; `remote_state=not_cancelled` means only the local wait stopped and **the remote command may still be running**. The remote command is left running by default — add `--cancel-on-timeout` to cancel it (a failed cancel is reported as `cancel_failed`) |
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
- | `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` |
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 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 a range filter the filter is applied to the **exact bytes**, so the copy is a byte-exact slice of the original (`byte_exact:true`, `integrity:"sha1"`). Printed inline, a text file gives `content` (strictly decoded, `byte_exact:true`) while a **non-UTF-8 file gives no `content` at all** — the exact original bytes are in `content_base64` (base64-decode it) and the envelope is marked `byte_exact:true`. Only a server that does not support that negotiation returns the lossy text (`integrity:"text_decoded"`, with a warning on stderr). |
285
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
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 |
package/checksums.txt CHANGED
@@ -1,5 +1,5 @@
1
- 643aa677f4597ffeca3e96dd87ae2a002355b65e8a51d3be691da4484db6a66b delta-cli-darwin-amd64.tar.gz
2
- fbc08c3e7d20830d33afbdd63f1d21d926b9220573afa2a39beb35d3ec8687d5 delta-cli-darwin-arm64.tar.gz
3
- e2c3e8c78f9b7aebb565bd3680ff948c6362cd9d884f7f34169f0397d4c1402f delta-cli-linux-amd64.tar.gz
4
- 1475959a99dbc0b7d47385ef1162ac41b1d696bf7ec128b22eefc865e9c21303 delta-cli-linux-arm64.tar.gz
5
- 0dbdc5ca2a2706befde271cbb0077c48810a81aa68abd759344d415b41c36e6f delta-cli-windows-amd64.zip
1
+ 492db2e329de38d28486a7df310bd0223c6bbcd465423d823b945b046a3a9173 delta-cli-darwin-amd64.tar.gz
2
+ f0000cae8dfdeafcb33aa6716c38fcaa38219883a2439d0ee27a9425dd81651d delta-cli-darwin-arm64.tar.gz
3
+ 7fff0a38b42c2694a37bb9415ebed791f70e3f41d8fde24670824ad5b7f56182 delta-cli-linux-amd64.tar.gz
4
+ 1ef7e3c95d6c44bb59e0d8b7c66258686fa85426e6f5fc298d406b1b4b9bbee6 delta-cli-linux-arm64.tar.gz
5
+ 68db2c20c6bb2127a2c6c8beecb383f362c5fbb7ca577df62392aea69c62f039 delta-cli-windows-amd64.zip
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delta-agentcloud/cli",
3
- "version": "1.1.30",
3
+ "version": "1.1.34",
4
4
  "description": "Delta Agent Cloud CLI for sandbox compute and science tools.",
5
5
  "bin": {
6
6
  "delta-cli": "scripts/run.js"
@@ -9,23 +9,38 @@ const crypto = require("crypto");
9
9
 
10
10
  const PKG = "@delta-agentcloud/cli";
11
11
 
12
- // Resolve the version to download:
13
- // env var > npm registry > local package.json (fallback)
14
- function resolveVersion() {
15
- if (process.env.DELTA_CLI_VERSION) return process.env.DELTA_CLI_VERSION.replace(/^v/, "");
16
- try {
17
- const out = require("child_process").execFileSync("npm", ["view", PKG, "version"], {
18
- stdio: ["ignore", "pipe", "pipe"],
19
- timeout: 15000,
20
- encoding: "utf8",
21
- });
22
- const ver = out.trim();
23
- if (/^\d+\.\d+\.\d+/.test(ver)) return ver;
24
- } catch {}
25
- return require("../package.json").version.replace(/-.*$/, "");
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
- const VERSION = resolveVersion();
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 getExpectedChecksum(archiveName, checksumsDir) {
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
- releaseAssetUrls(VERSION, archiveName, function(downloadUrls) {
335
- try {
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 = getExpectedChecksum(archiveName);
439
+ const expectedHash = expectedChecksumFor(version, archiveName, tmpDir);
344
440
  verifyChecksum(archivePath, expectedHash);
345
441
  console.error("Checksum verified");
346
- downloaded = true;
347
- break;
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
- if (!downloaded) throw lastErr;
354
-
355
- if (isWindows) {
356
- console.error("Extracting archive...");
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
- const binaryName = NAME + (isWindows ? ".exe" : "");
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) {
@@ -174,7 +174,7 @@ The reason is that, after running a command, this skill reads `log_file` from th
174
174
  | List active tunnels | `sandbox exposes <id>` — list the public tunnels currently active for this sandbox |
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
- | 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) |
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]` — **byte-exact**: a full copy goes through the chunked download (sha256-verified) and a filtered copy is a byte-exact slice of the original. Printed inline, a text file gives `content`; a **non-UTF-8 file gives no `content`** — the exact bytes are in `content_base64` |
178
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
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 |
@@ -38,7 +38,7 @@
38
38
 
39
39
  | Command | Description |
40
40
  |------|------|
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`. |
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 a range filter the filter is applied to the **exact bytes**, so the copy is a byte-exact slice of the original (`byte_exact:true`, `integrity:"sha1"`). Printed inline, a text file gives `content` (strictly decoded, `byte_exact:true`) while a **non-UTF-8 file gives no `content` at all** — the exact original bytes are in `content_base64` (base64-decode it) and the envelope is marked `byte_exact:true`. Only a server that does not support that negotiation returns the lossy text (`integrity:"text_decoded"`, with a warning on stderr). |
42
42
  | `sandbox working-directory <id>` | Get the sandbox's current working directory (absolute path `/workspace/{user_id}/{sandbox_id}`). |
43
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). |