bitpull 0.1.10 → 0.1.13

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 +133 -8
  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
  }
@@ -18515,7 +18564,8 @@ async function ensureImage(sb, image, timeoutMs = 6e5) {
18515
18564
  }
18516
18565
  var cached2 = null;
18517
18566
  async function selectSandbox(preference = "auto", probe = probeSandbox) {
18518
- if (cached2 && cached2.preference === preference && Date.now() - cached2.at < 864e5) return cached2;
18567
+ const ttl = cached2?.sandbox ? 864e5 : 5 * 6e4;
18568
+ if (cached2 && cached2.preference === preference && Date.now() - cached2.at < ttl) return cached2;
18519
18569
  const candidates = (await availableRuntimes()).filter((r) => preference === "auto" || r.runtime === preference);
18520
18570
  const tried = [];
18521
18571
  let chosen = null;
@@ -18751,9 +18801,82 @@ async function collectOutputs(dir) {
18751
18801
  return out;
18752
18802
  }
18753
18803
  async function runContainer(spec, sandbox, signal) {
18804
+ return usesVolumes(sandbox) ? runDockerJob(spec, sandbox, signal) : runFolderJob(spec, sandbox, signal);
18805
+ }
18806
+ function inputFiles(spec) {
18807
+ const files = [];
18808
+ for (const [path, b64] of Object.entries(spec.inputs ?? {})) {
18809
+ const rel = normalizeRepoPath(path);
18810
+ if (!rel || rel.startsWith("out/")) return null;
18811
+ files.push({ path: rel, data: Buffer.from(b64, "base64") });
18812
+ }
18813
+ return files;
18814
+ }
18815
+ function runWithInput(cmd, args, input) {
18816
+ return new Promise((resolve, reject) => {
18817
+ const child = (0, import_node_child_process5.spawn)(cmd, args, { stdio: ["pipe", "ignore", "pipe"], windowsHide: true });
18818
+ let stderr = "";
18819
+ child.stderr.on("data", (c) => stderr += c.toString("utf8"));
18820
+ child.on("error", reject);
18821
+ child.on("close", (code) => resolve({ code: code ?? -1, stderr }));
18822
+ child.stdin.end(input);
18823
+ });
18824
+ }
18825
+ async function runDockerJob(spec, sandbox, signal) {
18826
+ const files = inputFiles(spec);
18827
+ if (!files) return { ok: false, usage: {}, error: "bad input path" };
18828
+ const name = `bitpull-${(0, import_node_crypto6.randomBytes)(6).toString("hex")}`;
18829
+ const volume = `${name}-work`;
18830
+ const docker = (args, timeout = 12e4) => exec4("docker", args, { timeout, windowsHide: true });
18831
+ let outDir = null;
18832
+ try {
18833
+ for (const image of [spec.image, PROBE_IMAGE]) {
18834
+ const pulled = await ensureImage(sandbox, image);
18835
+ if (!pulled.ok) return { ok: false, usage: {}, error: `sandbox_error: ${pulled.error}` };
18836
+ }
18837
+ await docker(["volume", "create", volume]);
18838
+ await docker(dockerCreateArgs(sandbox, name, volume, spec));
18839
+ 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]);
18840
+ const copied = await runWithInput("docker", ["cp", "-a", "-", `${name}:/work`], tar);
18841
+ if (copied.code !== 0) return { ok: false, usage: {}, error: `sandbox_error: ${copied.stderr.trim().slice(0, 300)}` };
18842
+ 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"]);
18843
+ const started = Date.now();
18844
+ const stdout = capped();
18845
+ const stderr = capped();
18846
+ const exitCode = await new Promise((resolve, reject) => {
18847
+ const child = (0, import_node_child_process5.spawn)("docker", ["start", "-a", name], { stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
18848
+ child.stdout.on("data", (c) => stdout.push(c));
18849
+ child.stderr.on("data", (c) => stderr.push(c));
18850
+ const kill = () => (0, import_node_child_process5.spawn)("docker", ["kill", name], { stdio: "ignore", windowsHide: true });
18851
+ const timer = setTimeout(kill, spec.timeoutSec * 1e3);
18852
+ signal.addEventListener("abort", kill, { once: true });
18853
+ child.on("error", reject);
18854
+ child.on("close", (code) => {
18855
+ clearTimeout(timer);
18856
+ resolve(code ?? -1);
18857
+ });
18858
+ });
18859
+ const seconds = (Date.now() - started) / 1e3;
18860
+ if (signal.aborted) return { ok: false, usage: { seconds }, error: "stopped_by_owner" };
18861
+ outDir = await (0, import_promises12.mkdtemp)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "bitpull-out-"));
18862
+ await docker(["cp", `${name}:/work/out/.`, outDir]).catch(() => void 0);
18863
+ return {
18864
+ ok: true,
18865
+ result: { exitCode, stdout: stdout.text(), stderr: stderr.text(), outputs: await collectOutputs(outDir) },
18866
+ usage: { seconds }
18867
+ };
18868
+ } catch (err) {
18869
+ return { ok: false, usage: {}, error: `sandbox_error: ${String(err.stderr || err.message).trim().slice(0, 300)}` };
18870
+ } finally {
18871
+ await docker(["rm", "-f", name], 6e4).catch(() => void 0);
18872
+ await docker(["volume", "rm", "-f", volume], 6e4).catch(() => void 0);
18873
+ if (outDir) await (0, import_promises12.rm)(outDir, { recursive: true, force: true });
18874
+ }
18875
+ }
18876
+ async function runFolderJob(spec, sandbox, signal) {
18754
18877
  const workdir = await (0, import_promises12.mkdtemp)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), "bitpull-job-"));
18755
18878
  const name = `bitpull-${(0, import_node_crypto6.randomBytes)(6).toString("hex")}`;
18756
- const started = Date.now();
18879
+ let started = Date.now();
18757
18880
  try {
18758
18881
  for (const [path, b64] of Object.entries(spec.inputs ?? {})) {
18759
18882
  const rel = normalizeRepoPath(path);
@@ -18765,7 +18888,8 @@ async function runContainer(spec, sandbox, signal) {
18765
18888
  await (0, import_promises12.chmod)(workdir, 511);
18766
18889
  await (0, import_promises12.chmod)((0, import_node_path10.join)(workdir, "out"), 511);
18767
18890
  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}` };
18891
+ if (!pulled.ok) return { ok: false, usage: {}, error: `sandbox_error: ${pulled.error}` };
18892
+ started = Date.now();
18769
18893
  const stdout = capped();
18770
18894
  const stderr = capped();
18771
18895
  const exitCode = await new Promise((resolve, reject) => {
@@ -19131,7 +19255,7 @@ async function doctor(cfg) {
19131
19255
  }
19132
19256
 
19133
19257
  // apps/agent/src/control.ts
19134
- var APP_VERSION = true ? "0.1.10" : "dev";
19258
+ var APP_VERSION = true ? "0.1.13" : "dev";
19135
19259
  var UPDATE_MANIFEST = "https://github.com/SamuealPK/bitpull-releases/releases/latest/download/latest.json";
19136
19260
  var inDesktopApp = () => /bitpull-agent(\.exe)?$/i.test(process.execPath);
19137
19261
  function newerVersion(a, b) {
@@ -19273,7 +19397,8 @@ function startControlServer(ctx, node, port = ctx.config.controlPort) {
19273
19397
  const home = ctx.home ?? bitpullHome();
19274
19398
  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
19399
  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 };
19400
+ const shown = latest && APP_VERSION !== "dev" && newerVersion(APP_VERSION, latest) ? APP_VERSION : latest;
19401
+ return { current: APP_VERSION, latest: shown, available: !!latest && APP_VERSION !== "dev" && newerVersion(latest, APP_VERSION), inApp: inDesktopApp(), status };
19277
19402
  },
19278
19403
  /** Asks the desktop app to check for an update and install it now. */
19279
19404
  "POST /api/update": async () => {
@@ -30460,7 +30585,7 @@ async function startNode(ctx, home, opts = {}) {
30460
30585
  busy = false;
30461
30586
  }
30462
30587
  };
30463
- await tick();
30588
+ void tick();
30464
30589
  const timer = setInterval(tick, opts.heartbeatMs ?? 3e4);
30465
30590
  const worker = startWorker(ctx, { pollMs: opts.workerPollMs, sandbox });
30466
30591
  status.worker = worker.status;
@@ -30550,7 +30675,7 @@ function openBrowser(url) {
30550
30675
  const args = (0, import_node_os10.platform)() === "win32" ? ["/c", "start", "", url] : [url];
30551
30676
  (0, import_node_child_process7.spawn)(cmd, args, { stdio: "ignore", detached: true }).unref();
30552
30677
  }
30553
- var agentVersion = () => true ? "0.1.10" : "dev";
30678
+ var agentVersion = () => true ? "0.1.13" : "dev";
30554
30679
  var dashboardUrl = (cfg) => `http://127.0.0.1:${cfg.controlPort}/#token=${cfg.controlToken}`;
30555
30680
  var AppNotRunning = class extends Error {
30556
30681
  };
@@ -30881,7 +31006,7 @@ ${DRAFT_INSTRUCTIONS.replace(/call publish_repo.*/, 'run "bitpull publish <folde
30881
31006
  const control2 = await startControlServer(ctx, node);
30882
31007
  await (0, import_promises21.writeFile)((0, import_node_path18.join)(bitpullHome(), "agent.json"), JSON.stringify({ pid: process.pid, version: agentVersion() }));
30883
31008
  console.log(`BitPull is running. Dashboard: ${dashboardUrl(cfg)}`);
30884
- console.log(node.status.online ? "Connected to the network." : `Not connected yet: ${node.status.lastError}`);
31009
+ console.log('Connecting to the network in the background (see the dashboard or "bitpull status").');
30885
31010
  const stop = async () => {
30886
31011
  control2.close();
30887
31012
  await node.stop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitpull",
3
- "version": "0.1.10",
3
+ "version": "0.1.13",
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": {