farai 0.3.2 → 0.3.3

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/dist/cli/index.js CHANGED
@@ -5398,7 +5398,7 @@ function parseManifest(value) {
5398
5398
  if (!value || typeof value !== "object")
5399
5399
  throw new Error("invalid farai tool manifest");
5400
5400
  const candidate = value;
5401
- if (typeof candidate.contract !== "string" || !candidate.contract || !Array.isArray(candidate.aptPackages) || !candidate.pinnedTools || typeof candidate.pinnedTools !== "object" || !candidate.pinnedAssets || typeof candidate.pinnedAssets !== "object" || !candidate.workflows || typeof candidate.workflows !== "object") {
5401
+ if (!Array.isArray(candidate.aptPackages) || !candidate.pinnedTools || typeof candidate.pinnedTools !== "object" || !candidate.pinnedAssets || typeof candidate.pinnedAssets !== "object" || !candidate.workflows || typeof candidate.workflows !== "object") {
5402
5402
  throw new Error("invalid farai tool manifest");
5403
5403
  }
5404
5404
  const aptPackages = candidate.aptPackages.filter((item) => typeof item === "string" && Boolean(item));
@@ -5445,7 +5445,6 @@ function parseManifest(value) {
5445
5445
  if (!Object.keys(pinnedAssets).length)
5446
5446
  throw new Error("empty pinned asset manifest");
5447
5447
  return {
5448
- contract: candidate.contract,
5449
5448
  aptPackages,
5450
5449
  pinnedTools,
5451
5450
  pinnedAssets,
@@ -5455,11 +5454,15 @@ function parseManifest(value) {
5455
5454
  function isSha256(value) {
5456
5455
  return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
5457
5456
  }
5458
- var KALI_TOOL_MANIFEST_PATH, KALI_TOOL_MANIFEST;
5457
+ var KALI_TOOL_MANIFEST_PATH, KALI_IMAGE_CONTRACT_PATH, KALI_TOOL_MANIFEST, KALI_IMAGE_CONTRACT;
5459
5458
  var init_kali_tool_manifest = __esm(() => {
5460
5459
  init_file_read();
5461
5460
  KALI_TOOL_MANIFEST_PATH = join4(import.meta.dir, "..", "..", "docker", "kali", "farai-tool-manifest.json");
5461
+ KALI_IMAGE_CONTRACT_PATH = join4(import.meta.dir, "..", "..", "docker", "kali", "farai-image-contract");
5462
5462
  KALI_TOOL_MANIFEST = parseManifest(JSON.parse(readBoundedFileTextSync(KALI_TOOL_MANIFEST_PATH, 1024 * 1024, "kali tool manifest")));
5463
+ KALI_IMAGE_CONTRACT = readBoundedFileTextSync(KALI_IMAGE_CONTRACT_PATH, 1024, "kali image contract").trim();
5464
+ if (!KALI_IMAGE_CONTRACT)
5465
+ throw new Error("empty kali image contract");
5463
5466
  });
5464
5467
 
5465
5468
  // src/agent-tools/mcp-builtins.ts
@@ -7284,20 +7287,59 @@ class KaliContainerBackend {
7284
7287
  pullImageCommand() {
7285
7288
  return ["docker", "pull", this.image];
7286
7289
  }
7287
- async ensureImage() {
7288
- const current = await this.resolveImage();
7289
- if (current.exists && current.contract === KALI_IMAGE_CONTRACT) {
7290
+ async remoteImageDigest() {
7291
+ const inspect = await this.processRunner("docker", ["buildx", "imagetools", "inspect", this.image, "--format", "{{.Manifest.Digest}}"]);
7292
+ if (inspect.exitCode !== 0)
7293
+ return;
7294
+ return digestOf(inspect.stdout.trim());
7295
+ }
7296
+ async checkForImageUpdate() {
7297
+ const local = await this.resolveImage();
7298
+ if (!local.exists)
7290
7299
  return {
7291
- exitCode: 0,
7292
- stdout: "image ready",
7293
- stderr: "",
7294
- durationMs: 0,
7295
- timedOut: false
7300
+ exists: false,
7301
+ upToDate: false,
7302
+ ...local.error ? {
7303
+ error: local.error
7304
+ } : {}
7296
7305
  };
7306
+ const remote = await this.remoteImageDigest();
7307
+ if (!remote || !local.repoDigest)
7308
+ return {
7309
+ exists: true,
7310
+ upToDate: true
7311
+ };
7312
+ return {
7313
+ exists: true,
7314
+ upToDate: local.repoDigest === remote
7315
+ };
7316
+ }
7317
+ async ensureImage() {
7318
+ const local = await this.resolveImage();
7319
+ if (local.exists) {
7320
+ const remote = await this.remoteImageDigest();
7321
+ if (!remote || local.repoDigest !== undefined && local.repoDigest === remote) {
7322
+ return {
7323
+ exitCode: 0,
7324
+ stdout: "image ready",
7325
+ stderr: "",
7326
+ durationMs: 0,
7327
+ timedOut: false
7328
+ };
7329
+ }
7297
7330
  }
7298
7331
  const started = Date.now();
7299
7332
  const pulled = await this.pullRunner("docker", ["pull", this.image]);
7300
7333
  if (pulled.exitCode !== 0) {
7334
+ if (local.exists) {
7335
+ return {
7336
+ exitCode: 0,
7337
+ stdout: pulled.stdout || "using local kali image",
7338
+ stderr: "",
7339
+ durationMs: Date.now() - started,
7340
+ timedOut: false
7341
+ };
7342
+ }
7301
7343
  return {
7302
7344
  ...pulled,
7303
7345
  stderr: dockerFailure(pulled, `could not pull kali image ${this.image}; check network access to ${KALI_IMAGE_REPO}`),
@@ -7315,15 +7357,6 @@ class KaliContainerBackend {
7315
7357
  timedOut: false
7316
7358
  };
7317
7359
  }
7318
- if (resolved.contract !== KALI_IMAGE_CONTRACT) {
7319
- return {
7320
- exitCode: 1,
7321
- stdout: "",
7322
- stderr: `pulled kali image ${this.image} does not satisfy the farai capability contract (${resolved.contract ?? "missing"})`,
7323
- durationMs: Date.now() - started,
7324
- timedOut: false
7325
- };
7326
- }
7327
7360
  return {
7328
7361
  exitCode: 0,
7329
7362
  stdout: pulled.stdout || "image pulled",
@@ -7922,6 +7955,7 @@ function parseImageInspect(raw) {
7922
7955
  exists: true
7923
7956
  };
7924
7957
  const contract = image.Config?.Labels?.[KALI_IMAGE_CONTRACT_LABEL];
7958
+ const repoDigest = digestOf((image.RepoDigests ?? []).find((entry) => entry.includes("@sha256:")));
7925
7959
  return {
7926
7960
  exists: true,
7927
7961
  ...image.Id ? {
@@ -7929,6 +7963,9 @@ function parseImageInspect(raw) {
7929
7963
  } : {},
7930
7964
  ...contract ? {
7931
7965
  contract
7966
+ } : {},
7967
+ ...repoDigest ? {
7968
+ repoDigest
7932
7969
  } : {}
7933
7970
  };
7934
7971
  } catch {
@@ -7937,6 +7974,12 @@ function parseImageInspect(raw) {
7937
7974
  };
7938
7975
  }
7939
7976
  }
7977
+ function digestOf(reference) {
7978
+ if (!reference)
7979
+ return;
7980
+ const match = reference.match(/sha256:[a-f0-9]{64}/i);
7981
+ return match ? match[0].toLowerCase() : undefined;
7982
+ }
7940
7983
  function dockerFailure(result, fallback) {
7941
7984
  const detail = `${result.stderr}
7942
7985
  ${result.stdout}`.trim().replace(/\s+/g, " ");
@@ -7960,7 +8003,7 @@ function containerDoesNotExist2(result) {
7960
8003
  return /no such (container|object)/i.test(`${result.stdout}
7961
8004
  ${result.stderr}`);
7962
8005
  }
7963
- var CONTAINER_WORKSPACE_MOUNT = "/workspace", CONTAINER_WORKTREES_MOUNT = "/worktrees", kaliSessions, kaliPtySessions, containerStartLocks, globalContainerStartChain, CONTAINER_EXEC_MARKER_DIR = "/tmp/farai-exec", CONTAINER_EXEC_WRAPPER, CONTAINER_EXEC_KILLER, CONTAINER_PREFIX, KALI_IMAGE_CONTRACT, KALI_IMAGE_REPO = "ghcr.io/pajarori/farai-kali", DEFAULT_KALI_IMAGE, KALI_IMAGE_CONTRACT_LABEL = "org.farai.kali.contract", KALI_IMAGE_PULL_TIMEOUT_MS;
8006
+ var CONTAINER_WORKSPACE_MOUNT = "/workspace", CONTAINER_WORKTREES_MOUNT = "/worktrees", kaliSessions, kaliPtySessions, containerStartLocks, globalContainerStartChain, CONTAINER_EXEC_MARKER_DIR = "/tmp/farai-exec", CONTAINER_EXEC_WRAPPER, CONTAINER_EXEC_KILLER, CONTAINER_PREFIX, KALI_IMAGE_REPO = "ghcr.io/pajarori/farai-kali", KALI_IMAGE_TAG = "latest", DEFAULT_KALI_IMAGE, KALI_IMAGE_CONTRACT_LABEL = "org.farai.kali.contract", KALI_IMAGE_PULL_TIMEOUT_MS;
7964
8007
  var init_kali = __esm(() => {
7965
8008
  init_spawn_session();
7966
8009
  init_pty_session();
@@ -7978,8 +8021,7 @@ var init_kali = __esm(() => {
7978
8021
  CONTAINER_EXEC_KILLER = ["import os, signal, sys, time", "path = sys.argv[1]", "root = None", "marker_deadline = time.monotonic() + 0.25", "while root is None and time.monotonic() < marker_deadline:", " try:", " with open(path, encoding='ascii') as handle:", " root = int(handle.read().strip())", " except (FileNotFoundError, OSError, ValueError):", " time.sleep(0.01)", "if root is None:", " raise SystemExit(0)", "def direct_children(pid):", " try:", " with open(f'/proc/{pid}/task/{pid}/children', encoding='ascii') as handle:", " return [int(value) for value in handle.read().split()]", " except (FileNotFoundError, OSError, ValueError):", " return []", "def descendants(pid):", " found = []", " pending = direct_children(pid)", " seen = set()", " while pending:", " child = pending.pop()", " if child in seen:", " continue", " seen.add(child)", " found.append(child)", " pending.extend(direct_children(child))", " return found", "def alive(pid):", " try:", " os.kill(pid, 0)", " return True", " except ProcessLookupError:", " return False", " except PermissionError:", " return True", "targets = descendants(root)", "for pid in [*reversed(targets), root]:", " try:", " os.kill(pid, signal.SIGTERM)", " except (ProcessLookupError, PermissionError):", " pass", "deadline = time.monotonic() + 1.0", "while time.monotonic() < deadline and any(alive(pid) for pid in [root, *targets]):", " time.sleep(0.025)", "targets = list(dict.fromkeys([*targets, *descendants(root)]))", "for pid in [*reversed(targets), root]:", " try:", " os.kill(pid, signal.SIGKILL)", " except (ProcessLookupError, PermissionError):", " pass", "try:", " os.unlink(path)", "except FileNotFoundError:", " pass"].join(`
7979
8022
  `);
7980
8023
  CONTAINER_PREFIX = FARAI_CONTAINER_NAME_PREFIX;
7981
- KALI_IMAGE_CONTRACT = KALI_TOOL_MANIFEST.contract;
7982
- DEFAULT_KALI_IMAGE = `${KALI_IMAGE_REPO}:${KALI_IMAGE_CONTRACT}`;
8024
+ DEFAULT_KALI_IMAGE = `${KALI_IMAGE_REPO}:${KALI_IMAGE_TAG}`;
7983
8025
  KALI_IMAGE_PULL_TIMEOUT_MS = 30 * 60 * 1000;
7984
8026
  });
7985
8027
 
@@ -8732,8 +8774,8 @@ var init_process_output = __esm(() => {
8732
8774
 
8733
8775
  // src/version.ts
8734
8776
  function resolveFaraiVersion() {
8735
- if ("0.3.2")
8736
- return "0.3.2";
8777
+ if ("0.3.3")
8778
+ return "0.3.3";
8737
8779
  try {
8738
8780
  const parsed = JSON.parse(readBoundedFileTextSync(new URL("../package.json", import.meta.url), 1024 * 1024, "package metadata"));
8739
8781
  if (typeof parsed.version === "string" && parsed.version)
@@ -43757,17 +43799,17 @@ async function runStartupContainerPreflight(workspace) {
43757
43799
  const backend2 = new KaliContainerBackend({
43758
43800
  workspace
43759
43801
  });
43760
- const image = await backend2.resolveImage().catch(() => {
43802
+ const update = await backend2.checkForImageUpdate().catch(() => {
43761
43803
  return;
43762
43804
  });
43763
- if (!image || image.error)
43805
+ if (!update || update.error)
43764
43806
  return "continue";
43765
- if (image.exists && image.contract === KALI_IMAGE_CONTRACT)
43807
+ if (update.exists && update.upToDate)
43766
43808
  return "continue";
43767
43809
  const config = loadConfig(workspace);
43768
43810
  if (config.updates?.prompt === false || !process.stdin.isTTY || !process.stdout.isTTY)
43769
43811
  return "continue";
43770
- const answer = await promptForImagePull(KALI_IMAGE_CONTRACT, image.exists);
43812
+ const answer = await promptForImagePull(update.exists);
43771
43813
  if (answer === "cancelled")
43772
43814
  return "cancelled";
43773
43815
  if (answer === "later")
@@ -43779,11 +43821,11 @@ async function runStartupContainerPreflight(workspace) {
43779
43821
  }
43780
43822
  return "continue";
43781
43823
  }
43782
- async function promptForImagePull(contract, exists) {
43824
+ async function promptForImagePull(exists) {
43783
43825
  console.log("");
43784
43826
  console.log(FARAI_BANNER);
43785
43827
  console.log("");
43786
- console.log(exists ? `kali container image is outdated (needs ${contract})` : `kali container image ${contract} is not installed`);
43828
+ console.log(exists ? "a newer kali container image is available" : "kali container image is not installed");
43787
43829
  const interfaceHandle = createInterface2({
43788
43830
  input: process.stdin,
43789
43831
  output: process.stdout
@@ -73872,11 +73914,12 @@ async function doctor() {
73872
73914
  const backend2 = new KaliContainerBackend({
73873
73915
  workspace: process.cwd()
73874
73916
  });
73875
- const image = await backend2.resolveImage();
73876
- const capabilities = !image.exists ? "not installed (pulled on first run)" : image.contract === KALI_IMAGE_CONTRACT ? "ready" : "update available (pulled on first run)";
73877
- console.log(`kali image: ${backend2.image} (${image.exists ? "installed" : "missing"})`);
73878
- console.log(`kali contract: ${image.contract ?? "missing"} (expected ${KALI_IMAGE_CONTRACT})`);
73879
- console.log(`kali capabilities: ${capabilities}`);
73917
+ const update = await backend2.checkForImageUpdate().catch(() => {
73918
+ return;
73919
+ });
73920
+ const status = !update ? "unknown (docker unavailable)" : !update.exists ? "not installed (pulled on first run)" : update.upToDate ? "up to date" : "update available (pulled on first run)";
73921
+ console.log(`kali image: ${backend2.image}`);
73922
+ console.log(`kali image status: ${status}`);
73880
73923
  const {
73881
73924
  contentStatus: contentStatus2
73882
73925
  } = await Promise.resolve().then(() => (init_updater(), exports_updater));
@@ -74260,5 +74303,5 @@ Examples:
74260
74303
  `);
74261
74304
  }
74262
74305
 
74263
- //# debugId=4868BF6C23AAC2A664756E2164756E21
74306
+ //# debugId=11DF5F38873B6FC764756E2164756E21
74264
74307
  //# sourceMappingURL=index.js.map