bitpull 0.1.10 → 0.1.12

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 (2) hide show
  1. package/bitpull.cjs +129 -5
  2. package/package.json +1 -1
package/bitpull.cjs CHANGED
@@ -18444,6 +18444,55 @@ function sandboxArgs(sb, name, workdir, spec) {
18444
18444
  ...spec.command
18445
18445
  ];
18446
18446
  }
18447
+ function dockerCreateArgs(sb, name, volume, spec) {
18448
+ const args = sandboxArgs(sb, name, "HOST", spec);
18449
+ const v = args.indexOf("-v");
18450
+ args.splice(v, 2, "-v", `${volume}:/work:rw`);
18451
+ args.splice(0, 2, "create");
18452
+ return args;
18453
+ }
18454
+ var usesVolumes = (sb) => sb.cli === "docker";
18455
+ function tarArchive(entries) {
18456
+ const blocks = [];
18457
+ const octal = (n, len) => n.toString(8).padStart(len - 1, "0") + "\0";
18458
+ for (const e of entries) {
18459
+ const isDir = e.data === void 0;
18460
+ const full = isDir && !e.path.endsWith("/") ? `${e.path}/` : e.path;
18461
+ let name = full;
18462
+ let prefix = "";
18463
+ if (Buffer.byteLength(full) > 100) {
18464
+ const cut = [...full.matchAll(/\//g)].map((m) => m.index).find((i) => i <= 155 && Buffer.byteLength(full.slice(i + 1)) <= 100);
18465
+ if (cut === void 0) throw new Error(`path too long: ${e.path}`);
18466
+ prefix = full.slice(0, cut);
18467
+ name = full.slice(cut + 1);
18468
+ }
18469
+ const h = Buffer.alloc(512);
18470
+ h.write(name, 0, 100);
18471
+ h.write(octal(isDir ? 511 : 438, 8), 100);
18472
+ h.write(octal(65534, 8), 108);
18473
+ h.write(octal(65534, 8), 116);
18474
+ h.write(octal(isDir ? 0 : e.data.length, 12), 124);
18475
+ h.write(octal(Math.floor(Date.now() / 1e3), 12), 136);
18476
+ h.write(" ", 148);
18477
+ h.write(isDir ? "5" : "0", 156);
18478
+ h.write("ustar\0", 257);
18479
+ h.write("00", 263);
18480
+ h.write("nobody", 265);
18481
+ h.write("nobody", 297);
18482
+ h.write(prefix, 345, 155);
18483
+ let sum = 0;
18484
+ for (const b of h) sum += b;
18485
+ h.write(octal(sum, 7) + " ", 148);
18486
+ blocks.push(h);
18487
+ if (!isDir && e.data.length) {
18488
+ blocks.push(e.data);
18489
+ const pad = (512 - e.data.length % 512) % 512;
18490
+ if (pad) blocks.push(Buffer.alloc(pad));
18491
+ }
18492
+ }
18493
+ blocks.push(Buffer.alloc(1024));
18494
+ return Buffer.concat(blocks);
18495
+ }
18447
18496
  function killArgs(sb, name) {
18448
18497
  return sb.runtime === "apple" ? ["kill", name] : ["kill", name];
18449
18498
  }
@@ -18751,9 +18800,82 @@ async function collectOutputs(dir) {
18751
18800
  return out;
18752
18801
  }
18753
18802
  async function runContainer(spec, sandbox, signal) {
18803
+ return usesVolumes(sandbox) ? runDockerJob(spec, sandbox, signal) : runFolderJob(spec, sandbox, signal);
18804
+ }
18805
+ function inputFiles(spec) {
18806
+ const files = [];
18807
+ for (const [path, b64] of Object.entries(spec.inputs ?? {})) {
18808
+ const rel = normalizeRepoPath(path);
18809
+ if (!rel || rel.startsWith("out/")) return null;
18810
+ files.push({ path: rel, data: Buffer.from(b64, "base64") });
18811
+ }
18812
+ return files;
18813
+ }
18814
+ function runWithInput(cmd, args, input) {
18815
+ return new Promise((resolve, reject) => {
18816
+ const child = (0, import_node_child_process5.spawn)(cmd, args, { stdio: ["pipe", "ignore", "pipe"], windowsHide: true });
18817
+ let stderr = "";
18818
+ child.stderr.on("data", (c) => stderr += c.toString("utf8"));
18819
+ child.on("error", reject);
18820
+ child.on("close", (code) => resolve({ code: code ?? -1, stderr }));
18821
+ child.stdin.end(input);
18822
+ });
18823
+ }
18824
+ async function runDockerJob(spec, sandbox, signal) {
18825
+ const files = inputFiles(spec);
18826
+ if (!files) return { ok: false, usage: {}, error: "bad input path" };
18827
+ const name = `bitpull-${(0, import_node_crypto6.randomBytes)(6).toString("hex")}`;
18828
+ const volume = `${name}-work`;
18829
+ const docker = (args, timeout = 12e4) => exec4("docker", args, { timeout, windowsHide: true });
18830
+ let outDir = null;
18831
+ try {
18832
+ for (const image of [spec.image, PROBE_IMAGE]) {
18833
+ const pulled = await ensureImage(sandbox, image);
18834
+ if (!pulled.ok) return { ok: false, usage: {}, error: `sandbox_error: ${pulled.error}` };
18835
+ }
18836
+ await docker(["volume", "create", volume]);
18837
+ await docker(dockerCreateArgs(sandbox, name, volume, spec));
18838
+ const tar = tarArchive([{ path: "." }, { path: "out" }, ...[...new Set(files.flatMap((f) => f.path.split("/").slice(0, -1).map((_, i, a) => a.slice(0, i + 1).join("/"))))].map((d) => ({ path: d })), ...files]);
18839
+ const copied = await runWithInput("docker", ["cp", "-a", "-", `${name}:/work`], tar);
18840
+ if (copied.code !== 0) return { ok: false, usage: {}, error: `sandbox_error: ${copied.stderr.trim().slice(0, 300)}` };
18841
+ await docker(["run", "--rm", "--network", "none", "--cap-drop", "ALL", "--cap-add", "CHOWN", "--security-opt", "no-new-privileges", "-v", `${volume}:/work`, PROBE_IMAGE, "chown", "65534:65534", "/work"]);
18842
+ const started = Date.now();
18843
+ const stdout = capped();
18844
+ const stderr = capped();
18845
+ const exitCode = await new Promise((resolve, reject) => {
18846
+ const child = (0, import_node_child_process5.spawn)("docker", ["start", "-a", name], { stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
18847
+ child.stdout.on("data", (c) => stdout.push(c));
18848
+ child.stderr.on("data", (c) => stderr.push(c));
18849
+ const kill = () => (0, import_node_child_process5.spawn)("docker", ["kill", name], { stdio: "ignore", windowsHide: true });
18850
+ const timer = setTimeout(kill, spec.timeoutSec * 1e3);
18851
+ signal.addEventListener("abort", kill, { once: true });
18852
+ child.on("error", reject);
18853
+ child.on("close", (code) => {
18854
+ clearTimeout(timer);
18855
+ resolve(code ?? -1);
18856
+ });
18857
+ });
18858
+ const seconds = (Date.now() - started) / 1e3;
18859
+ if (signal.aborted) return { ok: false, usage: { seconds }, error: "stopped_by_owner" };
18860
+ outDir = await (0, import_promises12.mkdtemp)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "bitpull-out-"));
18861
+ await docker(["cp", `${name}:/work/out/.`, outDir]).catch(() => void 0);
18862
+ return {
18863
+ ok: true,
18864
+ result: { exitCode, stdout: stdout.text(), stderr: stderr.text(), outputs: await collectOutputs(outDir) },
18865
+ usage: { seconds }
18866
+ };
18867
+ } catch (err) {
18868
+ return { ok: false, usage: {}, error: `sandbox_error: ${String(err.stderr || err.message).trim().slice(0, 300)}` };
18869
+ } finally {
18870
+ await docker(["rm", "-f", name], 6e4).catch(() => void 0);
18871
+ await docker(["volume", "rm", "-f", volume], 6e4).catch(() => void 0);
18872
+ if (outDir) await (0, import_promises12.rm)(outDir, { recursive: true, force: true });
18873
+ }
18874
+ }
18875
+ async function runFolderJob(spec, sandbox, signal) {
18754
18876
  const workdir = await (0, import_promises12.mkdtemp)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "bitpull-job-"));
18755
18877
  const name = `bitpull-${(0, import_node_crypto6.randomBytes)(6).toString("hex")}`;
18756
- const started = Date.now();
18878
+ let started = Date.now();
18757
18879
  try {
18758
18880
  for (const [path, b64] of Object.entries(spec.inputs ?? {})) {
18759
18881
  const rel = normalizeRepoPath(path);
@@ -18765,7 +18887,8 @@ async function runContainer(spec, sandbox, signal) {
18765
18887
  await (0, import_promises12.chmod)(workdir, 511);
18766
18888
  await (0, import_promises12.chmod)((0, import_node_path10.join)(workdir, "out"), 511);
18767
18889
  const pulled = await ensureImage(sandbox, spec.image);
18768
- if (!pulled.ok) return { ok: false, usage: { seconds: (Date.now() - started) / 1e3 }, error: `sandbox_error: ${pulled.error}` };
18890
+ if (!pulled.ok) return { ok: false, usage: {}, error: `sandbox_error: ${pulled.error}` };
18891
+ started = Date.now();
18769
18892
  const stdout = capped();
18770
18893
  const stderr = capped();
18771
18894
  const exitCode = await new Promise((resolve, reject) => {
@@ -19131,7 +19254,7 @@ async function doctor(cfg) {
19131
19254
  }
19132
19255
 
19133
19256
  // apps/agent/src/control.ts
19134
- var APP_VERSION = true ? "0.1.10" : "dev";
19257
+ var APP_VERSION = true ? "0.1.12" : "dev";
19135
19258
  var UPDATE_MANIFEST = "https://github.com/SamuealPK/bitpull-releases/releases/latest/download/latest.json";
19136
19259
  var inDesktopApp = () => /bitpull-agent(\.exe)?$/i.test(process.execPath);
19137
19260
  function newerVersion(a, b) {
@@ -19273,7 +19396,8 @@ function startControlServer(ctx, node, port = ctx.config.controlPort) {
19273
19396
  const home = ctx.home ?? bitpullHome();
19274
19397
  const status = await (0, import_promises15.readFile)((0, import_node_path12.join)(home, "update-status.json"), "utf8").then((x) => JSON.parse(x)).catch(() => null);
19275
19398
  const latest = await fetch(UPDATE_MANIFEST, { signal: AbortSignal.timeout(8e3) }).then((r) => r.ok ? r.json() : null).then((m) => m?.version ?? null).catch(() => null);
19276
- return { current: APP_VERSION, latest, available: !!latest && APP_VERSION !== "dev" && newerVersion(latest, APP_VERSION), inApp: inDesktopApp(), status };
19399
+ const shown = latest && APP_VERSION !== "dev" && newerVersion(APP_VERSION, latest) ? APP_VERSION : latest;
19400
+ return { current: APP_VERSION, latest: shown, available: !!latest && APP_VERSION !== "dev" && newerVersion(latest, APP_VERSION), inApp: inDesktopApp(), status };
19277
19401
  },
19278
19402
  /** Asks the desktop app to check for an update and install it now. */
19279
19403
  "POST /api/update": async () => {
@@ -30550,7 +30674,7 @@ function openBrowser(url) {
30550
30674
  const args = (0, import_node_os10.platform)() === "win32" ? ["/c", "start", "", url] : [url];
30551
30675
  (0, import_node_child_process7.spawn)(cmd, args, { stdio: "ignore", detached: true }).unref();
30552
30676
  }
30553
- var agentVersion = () => true ? "0.1.10" : "dev";
30677
+ var agentVersion = () => true ? "0.1.12" : "dev";
30554
30678
  var dashboardUrl = (cfg) => `http://127.0.0.1:${cfg.controlPort}/#token=${cfg.controlToken}`;
30555
30679
  var AppNotRunning = class extends Error {
30556
30680
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitpull",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "BitPull: share signed, AI-readable code and computing power with AI tools through MCP. CLI and MCP server.",
5
5
  "mcpName": "io.github.SamuealPK/bitpull",
6
6
  "bin": {