farai 0.3.1 → 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
@@ -7270,6 +7273,7 @@ class KaliContainerBackend {
7270
7273
  this.workspacePath = containerWorkspacePath(this.rootWorkspace, this.workspace);
7271
7274
  this.timeoutMs = options.timeoutMs ?? 120000;
7272
7275
  this.processRunner = options.processRunner ?? ((command, args) => runProcess(command, args, Math.min(this.timeoutMs, 15000)));
7276
+ this.pullRunner = options.pullRunner ?? options.processRunner ?? ((command, args) => runProcess(command, args, KALI_IMAGE_PULL_TIMEOUT_MS));
7273
7277
  this.signal = options.signal;
7274
7278
  this.onOutputChunk = options.onOutputChunk;
7275
7279
  this.lifecycle = options.lifecycle;
@@ -7280,9 +7284,86 @@ class KaliContainerBackend {
7280
7284
  imageContract: KALI_IMAGE_CONTRACT
7281
7285
  } : undefined;
7282
7286
  }
7283
- buildImageCommand() {
7284
- const contextDir = join9(import.meta.dir, "..", "..", "docker", "kali");
7285
- return ["docker", "build", "-t", this.image, "-f", join9(contextDir, "Dockerfile"), contextDir];
7287
+ pullImageCommand() {
7288
+ return ["docker", "pull", this.image];
7289
+ }
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)
7299
+ return {
7300
+ exists: false,
7301
+ upToDate: false,
7302
+ ...local.error ? {
7303
+ error: local.error
7304
+ } : {}
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
+ }
7330
+ }
7331
+ const started = Date.now();
7332
+ const pulled = await this.pullRunner("docker", ["pull", this.image]);
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
+ }
7343
+ return {
7344
+ ...pulled,
7345
+ stderr: dockerFailure(pulled, `could not pull kali image ${this.image}; check network access to ${KALI_IMAGE_REPO}`),
7346
+ durationMs: Date.now() - started,
7347
+ timedOut: false
7348
+ };
7349
+ }
7350
+ const resolved = await this.resolveImage();
7351
+ if (!resolved.exists) {
7352
+ return {
7353
+ exitCode: 1,
7354
+ stdout: "",
7355
+ stderr: resolved.error ?? `kali image ${this.image} is still missing after pull`,
7356
+ durationMs: Date.now() - started,
7357
+ timedOut: false
7358
+ };
7359
+ }
7360
+ return {
7361
+ exitCode: 0,
7362
+ stdout: pulled.stdout || "image pulled",
7363
+ stderr: "",
7364
+ durationMs: Date.now() - started,
7365
+ timedOut: false
7366
+ };
7286
7367
  }
7287
7368
  async status() {
7288
7369
  const image = await this.resolveImage();
@@ -7427,18 +7508,13 @@ class KaliContainerBackend {
7427
7508
  }
7428
7509
  async startPersistentBody() {
7429
7510
  try {
7430
- const status = await this.status();
7431
- if (!status.imageExists) {
7511
+ const ensured = await this.ensureImage();
7512
+ if (ensured.exitCode !== 0) {
7432
7513
  if (this.identity && this.lifecycle)
7433
7514
  this.lifecycle.release(this.identity);
7434
- return {
7435
- exitCode: 1,
7436
- stdout: "",
7437
- stderr: status.dockerError ?? `kali image ${this.image} is missing; run \`farai setup --no-kb\``,
7438
- durationMs: 0,
7439
- timedOut: false
7440
- };
7515
+ return ensured;
7441
7516
  }
7517
+ const status = await this.status();
7442
7518
  if (status.dockerError) {
7443
7519
  if (this.identity && this.lifecycle)
7444
7520
  this.lifecycle.release(this.identity);
@@ -7460,17 +7536,6 @@ class KaliContainerBackend {
7460
7536
  timedOut: false
7461
7537
  };
7462
7538
  }
7463
- if (!status.imageContractCurrent) {
7464
- if (this.identity && this.lifecycle)
7465
- this.lifecycle.release(this.identity);
7466
- return {
7467
- exitCode: 1,
7468
- stdout: "",
7469
- stderr: `kali image ${this.image} does not satisfy the farai kali capability contract; run \`farai setup --no-kb\``,
7470
- durationMs: 0,
7471
- timedOut: false
7472
- };
7473
- }
7474
7539
  if (status.persistentRunning && status.persistentImageCurrent && (!this.identity || status.persistentIdentityCurrent)) {
7475
7540
  return {
7476
7541
  exitCode: 0,
@@ -7890,6 +7955,7 @@ function parseImageInspect(raw) {
7890
7955
  exists: true
7891
7956
  };
7892
7957
  const contract = image.Config?.Labels?.[KALI_IMAGE_CONTRACT_LABEL];
7958
+ const repoDigest = digestOf((image.RepoDigests ?? []).find((entry) => entry.includes("@sha256:")));
7893
7959
  return {
7894
7960
  exists: true,
7895
7961
  ...image.Id ? {
@@ -7897,6 +7963,9 @@ function parseImageInspect(raw) {
7897
7963
  } : {},
7898
7964
  ...contract ? {
7899
7965
  contract
7966
+ } : {},
7967
+ ...repoDigest ? {
7968
+ repoDigest
7900
7969
  } : {}
7901
7970
  };
7902
7971
  } catch {
@@ -7905,6 +7974,12 @@ function parseImageInspect(raw) {
7905
7974
  };
7906
7975
  }
7907
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
+ }
7908
7983
  function dockerFailure(result, fallback) {
7909
7984
  const detail = `${result.stderr}
7910
7985
  ${result.stdout}`.trim().replace(/\s+/g, " ");
@@ -7928,7 +8003,7 @@ function containerDoesNotExist2(result) {
7928
8003
  return /no such (container|object)/i.test(`${result.stdout}
7929
8004
  ${result.stderr}`);
7930
8005
  }
7931
- 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, DEFAULT_KALI_IMAGE = "farai-kali:latest", KALI_IMAGE_CONTRACT_LABEL = "org.farai.kali.contract";
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;
7932
8007
  var init_kali = __esm(() => {
7933
8008
  init_spawn_session();
7934
8009
  init_pty_session();
@@ -7946,7 +8021,8 @@ var init_kali = __esm(() => {
7946
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(`
7947
8022
  `);
7948
8023
  CONTAINER_PREFIX = FARAI_CONTAINER_NAME_PREFIX;
7949
- KALI_IMAGE_CONTRACT = KALI_TOOL_MANIFEST.contract;
8024
+ DEFAULT_KALI_IMAGE = `${KALI_IMAGE_REPO}:${KALI_IMAGE_TAG}`;
8025
+ KALI_IMAGE_PULL_TIMEOUT_MS = 30 * 60 * 1000;
7950
8026
  });
7951
8027
 
7952
8028
  // src/agent-tools/shared/backend.ts
@@ -8698,8 +8774,8 @@ var init_process_output = __esm(() => {
8698
8774
 
8699
8775
  // src/version.ts
8700
8776
  function resolveFaraiVersion() {
8701
- if ("0.3.1")
8702
- return "0.3.1";
8777
+ if ("0.3.3")
8778
+ return "0.3.3";
8703
8779
  try {
8704
8780
  const parsed = JSON.parse(readBoundedFileTextSync(new URL("../package.json", import.meta.url), 1024 * 1024, "package metadata"));
8705
8781
  if (typeof parsed.version === "string" && parsed.version)
@@ -19996,15 +20072,15 @@ var init_add_finding = __esm(() => {
19996
20072
  },
19997
20073
  impact: {
19998
20074
  type: "string",
19999
- description: "security impact demonstrated by the evidence"
20075
+ description: "security impact demonstrated by the evidence. rendered as markdown in the findings tab and reports"
20000
20076
  },
20001
20077
  reproduction: {
20002
20078
  type: "string",
20003
- description: "minimal reproducible steps and observed result"
20079
+ description: "minimal reproducible steps and observed result. rendered as markdown, so write it well: use an ordered list for steps, fenced code blocks for requests, responses, payloads, and commands, and tables where they clarify"
20004
20080
  },
20005
20081
  remediation: {
20006
20082
  type: "string",
20007
- description: "specific corrective action"
20083
+ description: "specific corrective action. rendered as markdown in the findings tab and reports"
20008
20084
  },
20009
20085
  campaignId: {
20010
20086
  type: "string",
@@ -20151,15 +20227,15 @@ var init_update_finding = __esm(() => {
20151
20227
  },
20152
20228
  impact: {
20153
20229
  type: "string",
20154
- description: "updated demonstrated security impact"
20230
+ description: "updated demonstrated security impact. rendered as markdown in the findings tab and reports"
20155
20231
  },
20156
20232
  reproduction: {
20157
20233
  type: "string",
20158
- description: "updated minimal reproducible steps and observed result"
20234
+ description: "updated minimal reproducible steps and observed result. rendered as markdown, so write it well: use an ordered list for steps, fenced code blocks for requests, responses, payloads, and commands, and tables where they clarify"
20159
20235
  },
20160
20236
  remediation: {
20161
20237
  type: "string",
20162
- description: "updated specific corrective action"
20238
+ description: "updated specific corrective action. rendered as markdown in the findings tab and reports"
20163
20239
  }
20164
20240
  },
20165
20241
  additionalProperties: false,
@@ -43713,6 +43789,81 @@ var init_preflight = __esm(() => {
43713
43789
  init_updater();
43714
43790
  });
43715
43791
 
43792
+ // src/agent-container/preflight.ts
43793
+ var exports_preflight2 = {};
43794
+ __export(exports_preflight2, {
43795
+ runStartupContainerPreflight: () => runStartupContainerPreflight
43796
+ });
43797
+ import { createInterface as createInterface2 } from "readline";
43798
+ async function runStartupContainerPreflight(workspace) {
43799
+ const backend2 = new KaliContainerBackend({
43800
+ workspace
43801
+ });
43802
+ const update = await backend2.checkForImageUpdate().catch(() => {
43803
+ return;
43804
+ });
43805
+ if (!update || update.error)
43806
+ return "continue";
43807
+ if (update.exists && update.upToDate)
43808
+ return "continue";
43809
+ const config = loadConfig(workspace);
43810
+ if (config.updates?.prompt === false || !process.stdin.isTTY || !process.stdout.isTTY)
43811
+ return "continue";
43812
+ const answer = await promptForImagePull(update.exists);
43813
+ if (answer === "cancelled")
43814
+ return "cancelled";
43815
+ if (answer === "later")
43816
+ return "continue";
43817
+ console.log(`pulling ${DEFAULT_KALI_IMAGE}...`);
43818
+ const pulled = await spawnPull();
43819
+ if (pulled !== 0) {
43820
+ console.error("kali image pull failed; farai will retry when the container is first needed");
43821
+ }
43822
+ return "continue";
43823
+ }
43824
+ async function promptForImagePull(exists) {
43825
+ console.log("");
43826
+ console.log(FARAI_BANNER);
43827
+ console.log("");
43828
+ console.log(exists ? "a newer kali container image is available" : "kali container image is not installed");
43829
+ const interfaceHandle = createInterface2({
43830
+ input: process.stdin,
43831
+ output: process.stdout
43832
+ });
43833
+ return await new Promise((resolve10) => {
43834
+ let settled = false;
43835
+ const finish = (value) => {
43836
+ if (settled)
43837
+ return;
43838
+ settled = true;
43839
+ interfaceHandle.close();
43840
+ resolve10(value);
43841
+ };
43842
+ interfaceHandle.once("SIGINT", () => finish("cancelled"));
43843
+ interfaceHandle.question("pull before starting? [enter=yes, n=later] ", (value) => {
43844
+ const normalized = value.trim().toLowerCase();
43845
+ if (normalized === "n" || normalized === "no" || normalized === "later")
43846
+ finish("later");
43847
+ else
43848
+ finish("apply");
43849
+ });
43850
+ });
43851
+ }
43852
+ async function spawnPull() {
43853
+ const proc = Bun.spawn(["docker", "pull", DEFAULT_KALI_IMAGE], {
43854
+ stdout: "inherit",
43855
+ stderr: "inherit",
43856
+ env: faraiDockerEnvironment()
43857
+ });
43858
+ return await proc.exited;
43859
+ }
43860
+ var init_preflight2 = __esm(() => {
43861
+ init_branding();
43862
+ init_config();
43863
+ init_docker_environment();
43864
+ init_kali();
43865
+ });
43866
+
43716
43867
  // node_modules/solid-js/dist/solid.js
43717
43868
  function getContextId(count2) {
43718
43869
  const num2 = String(count2), len = num2.length - 1;
@@ -65250,8 +65401,8 @@ function FindingsEmpty(props) {
65250
65401
  }
65251
65402
  function findingMarkdown(finding) {
65252
65403
  const evidence = finding.evidenceIds.length > 0 ? finding.evidenceIds.map((id2) => `- \`${id2}\``) : ["_no linked evidence._"];
65253
- const technical = [finding.cvssVector ? `- cvss 3.1 vector: \`${finding.cvssVector}\`` : "", finding.campaignId ? `- campaign: \`${finding.campaignId}\`` : "", finding.hypothesisId ? `- hypothesis: \`${finding.hypothesisId}\`` : "", finding.duplicateOf ? `- duplicate of: \`${finding.duplicateOf}\`` : ""].filter(Boolean);
65254
- return ["## impact", "", finding.impact.trim() || "_not recorded._", "", "## reproduction", "", finding.reproduction.trim() || "_not recorded._", "", "## evidence", "", ...evidence, "", "## remediation", "", finding.remediation.trim() || "_not recorded._", ...technical.length > 0 ? ["", "## technical details", "", ...technical] : []].join(`
65404
+ const technical = [finding.cvssVector ? `- CVSS 3.1 : \`${finding.cvssVector}\`` : "", finding.campaignId ? `- campaign : \`${finding.campaignId}\`` : "", finding.hypothesisId ? `- hypothesis: \`${finding.hypothesisId}\`` : "", finding.duplicateOf ? `- duplicate of: \`${finding.duplicateOf}\`` : ""].filter(Boolean);
65405
+ return [...technical.length > 0 ? ["## technical details", "", ...technical, ""] : [], "## impact", "", finding.impact.trim() || "_not recorded._", "", "## reproduction", "", finding.reproduction.trim() || "_not recorded._", "", "## remediation", "", finding.remediation.trim() || "_not recorded._", "", "## evidence", "", ...evidence].join(`
65255
65406
  `);
65256
65407
  }
65257
65408
  function filteredFindings(findings, query) {
@@ -71890,11 +72041,15 @@ class BenchmarkDockerLifecycle {
71890
72041
  }
71891
72042
  async startUnlocked() {
71892
72043
  const processRunner = (command, args2) => this.runner(command, args2);
71893
- const image = await new KaliContainerBackend({
72044
+ const provisioner = new KaliContainerBackend({
71894
72045
  workspace: this.workspace,
71895
72046
  image: DEFAULT_KALI_IMAGE,
71896
72047
  processRunner
71897
- }).resolveImage();
72048
+ });
72049
+ const ensured = await provisioner.ensureImage();
72050
+ if (ensured.exitCode !== 0)
72051
+ throw new Error(ensured.stderr || `benchmark agent image is unavailable: ${DEFAULT_KALI_IMAGE}`);
72052
+ const image = await provisioner.resolveImage();
71898
72053
  if (!image.exists)
71899
72054
  throw new Error(image.error ?? `benchmark agent image is missing: ${DEFAULT_KALI_IMAGE}`);
71900
72055
  if (image.error)
@@ -73204,7 +73359,6 @@ var init_suite = __esm(() => {
73204
73359
  // src/cli/index.ts
73205
73360
  init_runtime();
73206
73361
  init_kali();
73207
- init_docker_environment();
73208
73362
  init_model_registry();
73209
73363
  init_model_catalog();
73210
73364
  init_model_profiles();
@@ -73254,9 +73408,6 @@ function parseSetupArguments(args) {
73254
73408
  "api-key-stdin": {
73255
73409
  type: "boolean"
73256
73410
  },
73257
- "no-docker": {
73258
- type: "boolean"
73259
- },
73260
73411
  "no-kb": {
73261
73412
  type: "boolean"
73262
73413
  },
@@ -73277,7 +73428,6 @@ function parseSetupArguments(args) {
73277
73428
  if (!model && (baseUrl || apiKeyEnv || apiKeyStdin))
73278
73429
  throw new Error("--base-url and api key options require --model");
73279
73430
  return {
73280
- skipDocker: optionalBoolean(values, "no-docker"),
73281
73431
  skipKnowledge: aliasedBoolean(values, "no-kb", "no-knowledge"),
73282
73432
  ...optionalProperty("model", model),
73283
73433
  ...optionalProperty("baseUrl", baseUrl),
@@ -73764,16 +73914,17 @@ async function doctor() {
73764
73914
  const backend2 = new KaliContainerBackend({
73765
73915
  workspace: process.cwd()
73766
73916
  });
73767
- const image = await backend2.resolveImage();
73768
- console.log(`kali image: ${backend2.image} (${image.exists ? "exists" : "missing"})`);
73769
- console.log(`kali contract: ${image.contract ?? "missing"}`);
73770
- console.log(`kali capabilities: ${image.contract === KALI_IMAGE_CONTRACT ? "ready" : "rebuild required"}`);
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}`);
73771
73923
  const {
73772
73924
  contentStatus: contentStatus2
73773
73925
  } = await Promise.resolve().then(() => (init_updater(), exports_updater));
73774
73926
  const content = contentStatus2();
73775
73927
  console.log(`content: ${content.active?.version ?? "local fallback"}`);
73776
- console.log(`setup command: farai setup`);
73777
73928
  }
73778
73929
  async function setup(args2) {
73779
73930
  const parsed = parseSetupArguments(args2);
@@ -73787,17 +73938,7 @@ async function setup(args2) {
73787
73938
  const addArgs = [parsed.model, ...parsed.baseUrl ? ["--base-url", parsed.baseUrl] : [], ...parsed.apiKeyEnv ? ["--api-key-env", parsed.apiKeyEnv] : [], ...parsed.apiKeyStdin ? ["--api-key-stdin"] : [], "--set-default"];
73788
73939
  await addModel(addArgs);
73789
73940
  }
73790
- if (!parsed.skipDocker) {
73791
- console.log("[*] building Farai Kali image");
73792
- const code = await buildContainer();
73793
- if (code !== 0) {
73794
- process.exitCode = code;
73795
- console.error("[!] docker image build failed; rerun `farai setup --no-kb` after fixing Docker");
73796
- return;
73797
- }
73798
- } else {
73799
- console.log("[*] skipping Docker image build");
73800
- }
73941
+ console.log(`[*] kali image: ${DEFAULT_KALI_IMAGE} (pulled on first run)`);
73801
73942
  if (!parsed.skipKnowledge) {
73802
73943
  const contentInstalled = await syncContentForSetup(process.cwd());
73803
73944
  if (!contentInstalled) {
@@ -73937,11 +74078,18 @@ async function launchTui(workspace, sessionId) {
73937
74078
  const {
73938
74079
  runStartupContentPreflight: runStartupContentPreflight2
73939
74080
  } = await Promise.resolve().then(() => (init_preflight(), exports_preflight));
74081
+ const {
74082
+ runStartupContainerPreflight: runStartupContainerPreflight2
74083
+ } = await Promise.resolve().then(() => (init_preflight2(), exports_preflight2));
73940
74084
  const effectiveWorkspace = sessionId ? resolveSessionLocation(sessionId)?.workspace ?? workspace : workspace;
73941
74085
  if (await runStartupContentPreflight2(effectiveWorkspace) === "cancelled") {
73942
74086
  process.exitCode = 130;
73943
74087
  return;
73944
74088
  }
74089
+ if (await runStartupContainerPreflight2(effectiveWorkspace) === "cancelled") {
74090
+ process.exitCode = 130;
74091
+ return;
74092
+ }
73945
74093
  if (import.meta.path.endsWith(".ts")) {
73946
74094
  const sourceTuiPreload = "@opentui/solid/preload";
73947
74095
  await import(sourceTuiPreload);
@@ -74047,20 +74195,6 @@ async function benchmark(args2) {
74047
74195
  return;
74048
74196
  }
74049
74197
  }
74050
- async function buildContainer() {
74051
- const backend2 = new KaliContainerBackend({
74052
- workspace: process.cwd()
74053
- });
74054
- console.log(backend2.buildImageCommand().join(" "));
74055
- const proc = Bun.spawn(backend2.buildImageCommand(), {
74056
- stdout: "inherit",
74057
- stderr: "inherit",
74058
- env: faraiDockerEnvironment()
74059
- });
74060
- const code = await proc.exited;
74061
- process.exitCode = code;
74062
- return code;
74063
- }
74064
74198
  function wantsHelp(args2) {
74065
74199
  return args2.includes("--help") || args2.includes("-h") || args2[0] === "help";
74066
74200
  }
@@ -74076,7 +74210,6 @@ Options:
74076
74210
  --base-url <url> OpenAI-compatible provider URL
74077
74211
  --api-key-env <ENV> Environment variable containing the API key
74078
74212
  --api-key-stdin Read the API key from stdin
74079
- --no-docker Skip Farai Kali image build
74080
74213
  --no-kb, --no-knowledge Skip knowledge base content sync
74081
74214
 
74082
74215
  Examples:
@@ -74148,7 +74281,7 @@ Usage:
74148
74281
  farai
74149
74282
  farai resume [session-name-or-id]
74150
74283
  farai run <prompt> [--session <id>] [--json]
74151
- farai setup [--model provider:model] [--base-url url] [--api-key-env ENV | --api-key-stdin] [--no-docker] [--no-kb]
74284
+ farai setup [--model provider:model] [--base-url url] [--api-key-env ENV | --api-key-stdin] [--no-kb]
74152
74285
  farai init [--target <ip-or-host>] [--name <name>] [--model provider:model]
74153
74286
  farai doctor
74154
74287
  farai model
@@ -74170,5 +74303,5 @@ Examples:
74170
74303
  `);
74171
74304
  }
74172
74305
 
74173
- //# debugId=DE3417A125CB5EC964756E2164756E21
74306
+ //# debugId=11DF5F38873B6FC764756E2164756E21
74174
74307
  //# sourceMappingURL=index.js.map