farai 0.3.1 → 0.3.2

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
@@ -7270,6 +7270,7 @@ class KaliContainerBackend {
7270
7270
  this.workspacePath = containerWorkspacePath(this.rootWorkspace, this.workspace);
7271
7271
  this.timeoutMs = options.timeoutMs ?? 120000;
7272
7272
  this.processRunner = options.processRunner ?? ((command, args) => runProcess(command, args, Math.min(this.timeoutMs, 15000)));
7273
+ this.pullRunner = options.pullRunner ?? options.processRunner ?? ((command, args) => runProcess(command, args, KALI_IMAGE_PULL_TIMEOUT_MS));
7273
7274
  this.signal = options.signal;
7274
7275
  this.onOutputChunk = options.onOutputChunk;
7275
7276
  this.lifecycle = options.lifecycle;
@@ -7280,9 +7281,56 @@ class KaliContainerBackend {
7280
7281
  imageContract: KALI_IMAGE_CONTRACT
7281
7282
  } : undefined;
7282
7283
  }
7283
- buildImageCommand() {
7284
- const contextDir = join9(import.meta.dir, "..", "..", "docker", "kali");
7285
- return ["docker", "build", "-t", this.image, "-f", join9(contextDir, "Dockerfile"), contextDir];
7284
+ pullImageCommand() {
7285
+ return ["docker", "pull", this.image];
7286
+ }
7287
+ async ensureImage() {
7288
+ const current = await this.resolveImage();
7289
+ if (current.exists && current.contract === KALI_IMAGE_CONTRACT) {
7290
+ return {
7291
+ exitCode: 0,
7292
+ stdout: "image ready",
7293
+ stderr: "",
7294
+ durationMs: 0,
7295
+ timedOut: false
7296
+ };
7297
+ }
7298
+ const started = Date.now();
7299
+ const pulled = await this.pullRunner("docker", ["pull", this.image]);
7300
+ if (pulled.exitCode !== 0) {
7301
+ return {
7302
+ ...pulled,
7303
+ stderr: dockerFailure(pulled, `could not pull kali image ${this.image}; check network access to ${KALI_IMAGE_REPO}`),
7304
+ durationMs: Date.now() - started,
7305
+ timedOut: false
7306
+ };
7307
+ }
7308
+ const resolved = await this.resolveImage();
7309
+ if (!resolved.exists) {
7310
+ return {
7311
+ exitCode: 1,
7312
+ stdout: "",
7313
+ stderr: resolved.error ?? `kali image ${this.image} is still missing after pull`,
7314
+ durationMs: Date.now() - started,
7315
+ timedOut: false
7316
+ };
7317
+ }
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
+ return {
7328
+ exitCode: 0,
7329
+ stdout: pulled.stdout || "image pulled",
7330
+ stderr: "",
7331
+ durationMs: Date.now() - started,
7332
+ timedOut: false
7333
+ };
7286
7334
  }
7287
7335
  async status() {
7288
7336
  const image = await this.resolveImage();
@@ -7427,18 +7475,13 @@ class KaliContainerBackend {
7427
7475
  }
7428
7476
  async startPersistentBody() {
7429
7477
  try {
7430
- const status = await this.status();
7431
- if (!status.imageExists) {
7478
+ const ensured = await this.ensureImage();
7479
+ if (ensured.exitCode !== 0) {
7432
7480
  if (this.identity && this.lifecycle)
7433
7481
  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
- };
7482
+ return ensured;
7441
7483
  }
7484
+ const status = await this.status();
7442
7485
  if (status.dockerError) {
7443
7486
  if (this.identity && this.lifecycle)
7444
7487
  this.lifecycle.release(this.identity);
@@ -7460,17 +7503,6 @@ class KaliContainerBackend {
7460
7503
  timedOut: false
7461
7504
  };
7462
7505
  }
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
7506
  if (status.persistentRunning && status.persistentImageCurrent && (!this.identity || status.persistentIdentityCurrent)) {
7475
7507
  return {
7476
7508
  exitCode: 0,
@@ -7928,7 +7960,7 @@ function containerDoesNotExist2(result) {
7928
7960
  return /no such (container|object)/i.test(`${result.stdout}
7929
7961
  ${result.stderr}`);
7930
7962
  }
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";
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;
7932
7964
  var init_kali = __esm(() => {
7933
7965
  init_spawn_session();
7934
7966
  init_pty_session();
@@ -7947,6 +7979,8 @@ var init_kali = __esm(() => {
7947
7979
  `);
7948
7980
  CONTAINER_PREFIX = FARAI_CONTAINER_NAME_PREFIX;
7949
7981
  KALI_IMAGE_CONTRACT = KALI_TOOL_MANIFEST.contract;
7982
+ DEFAULT_KALI_IMAGE = `${KALI_IMAGE_REPO}:${KALI_IMAGE_CONTRACT}`;
7983
+ KALI_IMAGE_PULL_TIMEOUT_MS = 30 * 60 * 1000;
7950
7984
  });
7951
7985
 
7952
7986
  // src/agent-tools/shared/backend.ts
@@ -8698,8 +8732,8 @@ var init_process_output = __esm(() => {
8698
8732
 
8699
8733
  // src/version.ts
8700
8734
  function resolveFaraiVersion() {
8701
- if ("0.3.1")
8702
- return "0.3.1";
8735
+ if ("0.3.2")
8736
+ return "0.3.2";
8703
8737
  try {
8704
8738
  const parsed = JSON.parse(readBoundedFileTextSync(new URL("../package.json", import.meta.url), 1024 * 1024, "package metadata"));
8705
8739
  if (typeof parsed.version === "string" && parsed.version)
@@ -19996,15 +20030,15 @@ var init_add_finding = __esm(() => {
19996
20030
  },
19997
20031
  impact: {
19998
20032
  type: "string",
19999
- description: "security impact demonstrated by the evidence"
20033
+ description: "security impact demonstrated by the evidence. rendered as markdown in the findings tab and reports"
20000
20034
  },
20001
20035
  reproduction: {
20002
20036
  type: "string",
20003
- description: "minimal reproducible steps and observed result"
20037
+ 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
20038
  },
20005
20039
  remediation: {
20006
20040
  type: "string",
20007
- description: "specific corrective action"
20041
+ description: "specific corrective action. rendered as markdown in the findings tab and reports"
20008
20042
  },
20009
20043
  campaignId: {
20010
20044
  type: "string",
@@ -20151,15 +20185,15 @@ var init_update_finding = __esm(() => {
20151
20185
  },
20152
20186
  impact: {
20153
20187
  type: "string",
20154
- description: "updated demonstrated security impact"
20188
+ description: "updated demonstrated security impact. rendered as markdown in the findings tab and reports"
20155
20189
  },
20156
20190
  reproduction: {
20157
20191
  type: "string",
20158
- description: "updated minimal reproducible steps and observed result"
20192
+ 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
20193
  },
20160
20194
  remediation: {
20161
20195
  type: "string",
20162
- description: "updated specific corrective action"
20196
+ description: "updated specific corrective action. rendered as markdown in the findings tab and reports"
20163
20197
  }
20164
20198
  },
20165
20199
  additionalProperties: false,
@@ -43713,6 +43747,81 @@ var init_preflight = __esm(() => {
43713
43747
  init_updater();
43714
43748
  });
43715
43749
 
43750
+ // src/agent-container/preflight.ts
43751
+ var exports_preflight2 = {};
43752
+ __export(exports_preflight2, {
43753
+ runStartupContainerPreflight: () => runStartupContainerPreflight
43754
+ });
43755
+ import { createInterface as createInterface2 } from "readline";
43756
+ async function runStartupContainerPreflight(workspace) {
43757
+ const backend2 = new KaliContainerBackend({
43758
+ workspace
43759
+ });
43760
+ const image = await backend2.resolveImage().catch(() => {
43761
+ return;
43762
+ });
43763
+ if (!image || image.error)
43764
+ return "continue";
43765
+ if (image.exists && image.contract === KALI_IMAGE_CONTRACT)
43766
+ return "continue";
43767
+ const config = loadConfig(workspace);
43768
+ if (config.updates?.prompt === false || !process.stdin.isTTY || !process.stdout.isTTY)
43769
+ return "continue";
43770
+ const answer = await promptForImagePull(KALI_IMAGE_CONTRACT, image.exists);
43771
+ if (answer === "cancelled")
43772
+ return "cancelled";
43773
+ if (answer === "later")
43774
+ return "continue";
43775
+ console.log(`pulling ${DEFAULT_KALI_IMAGE}...`);
43776
+ const pulled = await spawnPull();
43777
+ if (pulled !== 0) {
43778
+ console.error("kali image pull failed; farai will retry when the container is first needed");
43779
+ }
43780
+ return "continue";
43781
+ }
43782
+ async function promptForImagePull(contract, exists) {
43783
+ console.log("");
43784
+ console.log(FARAI_BANNER);
43785
+ console.log("");
43786
+ console.log(exists ? `kali container image is outdated (needs ${contract})` : `kali container image ${contract} is not installed`);
43787
+ const interfaceHandle = createInterface2({
43788
+ input: process.stdin,
43789
+ output: process.stdout
43790
+ });
43791
+ return await new Promise((resolve10) => {
43792
+ let settled = false;
43793
+ const finish = (value) => {
43794
+ if (settled)
43795
+ return;
43796
+ settled = true;
43797
+ interfaceHandle.close();
43798
+ resolve10(value);
43799
+ };
43800
+ interfaceHandle.once("SIGINT", () => finish("cancelled"));
43801
+ interfaceHandle.question("pull before starting? [enter=yes, n=later] ", (value) => {
43802
+ const normalized = value.trim().toLowerCase();
43803
+ if (normalized === "n" || normalized === "no" || normalized === "later")
43804
+ finish("later");
43805
+ else
43806
+ finish("apply");
43807
+ });
43808
+ });
43809
+ }
43810
+ async function spawnPull() {
43811
+ const proc = Bun.spawn(["docker", "pull", DEFAULT_KALI_IMAGE], {
43812
+ stdout: "inherit",
43813
+ stderr: "inherit",
43814
+ env: faraiDockerEnvironment()
43815
+ });
43816
+ return await proc.exited;
43817
+ }
43818
+ var init_preflight2 = __esm(() => {
43819
+ init_branding();
43820
+ init_config();
43821
+ init_docker_environment();
43822
+ init_kali();
43823
+ });
43824
+
43716
43825
  // node_modules/solid-js/dist/solid.js
43717
43826
  function getContextId(count2) {
43718
43827
  const num2 = String(count2), len = num2.length - 1;
@@ -65250,8 +65359,8 @@ function FindingsEmpty(props) {
65250
65359
  }
65251
65360
  function findingMarkdown(finding) {
65252
65361
  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(`
65362
+ 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);
65363
+ 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
65364
  `);
65256
65365
  }
65257
65366
  function filteredFindings(findings, query) {
@@ -71890,11 +71999,15 @@ class BenchmarkDockerLifecycle {
71890
71999
  }
71891
72000
  async startUnlocked() {
71892
72001
  const processRunner = (command, args2) => this.runner(command, args2);
71893
- const image = await new KaliContainerBackend({
72002
+ const provisioner = new KaliContainerBackend({
71894
72003
  workspace: this.workspace,
71895
72004
  image: DEFAULT_KALI_IMAGE,
71896
72005
  processRunner
71897
- }).resolveImage();
72006
+ });
72007
+ const ensured = await provisioner.ensureImage();
72008
+ if (ensured.exitCode !== 0)
72009
+ throw new Error(ensured.stderr || `benchmark agent image is unavailable: ${DEFAULT_KALI_IMAGE}`);
72010
+ const image = await provisioner.resolveImage();
71898
72011
  if (!image.exists)
71899
72012
  throw new Error(image.error ?? `benchmark agent image is missing: ${DEFAULT_KALI_IMAGE}`);
71900
72013
  if (image.error)
@@ -73204,7 +73317,6 @@ var init_suite = __esm(() => {
73204
73317
  // src/cli/index.ts
73205
73318
  init_runtime();
73206
73319
  init_kali();
73207
- init_docker_environment();
73208
73320
  init_model_registry();
73209
73321
  init_model_catalog();
73210
73322
  init_model_profiles();
@@ -73254,9 +73366,6 @@ function parseSetupArguments(args) {
73254
73366
  "api-key-stdin": {
73255
73367
  type: "boolean"
73256
73368
  },
73257
- "no-docker": {
73258
- type: "boolean"
73259
- },
73260
73369
  "no-kb": {
73261
73370
  type: "boolean"
73262
73371
  },
@@ -73277,7 +73386,6 @@ function parseSetupArguments(args) {
73277
73386
  if (!model && (baseUrl || apiKeyEnv || apiKeyStdin))
73278
73387
  throw new Error("--base-url and api key options require --model");
73279
73388
  return {
73280
- skipDocker: optionalBoolean(values, "no-docker"),
73281
73389
  skipKnowledge: aliasedBoolean(values, "no-kb", "no-knowledge"),
73282
73390
  ...optionalProperty("model", model),
73283
73391
  ...optionalProperty("baseUrl", baseUrl),
@@ -73765,15 +73873,15 @@ async function doctor() {
73765
73873
  workspace: process.cwd()
73766
73874
  });
73767
73875
  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"}`);
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}`);
73771
73880
  const {
73772
73881
  contentStatus: contentStatus2
73773
73882
  } = await Promise.resolve().then(() => (init_updater(), exports_updater));
73774
73883
  const content = contentStatus2();
73775
73884
  console.log(`content: ${content.active?.version ?? "local fallback"}`);
73776
- console.log(`setup command: farai setup`);
73777
73885
  }
73778
73886
  async function setup(args2) {
73779
73887
  const parsed = parseSetupArguments(args2);
@@ -73787,17 +73895,7 @@ async function setup(args2) {
73787
73895
  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
73896
  await addModel(addArgs);
73789
73897
  }
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
- }
73898
+ console.log(`[*] kali image: ${DEFAULT_KALI_IMAGE} (pulled on first run)`);
73801
73899
  if (!parsed.skipKnowledge) {
73802
73900
  const contentInstalled = await syncContentForSetup(process.cwd());
73803
73901
  if (!contentInstalled) {
@@ -73937,11 +74035,18 @@ async function launchTui(workspace, sessionId) {
73937
74035
  const {
73938
74036
  runStartupContentPreflight: runStartupContentPreflight2
73939
74037
  } = await Promise.resolve().then(() => (init_preflight(), exports_preflight));
74038
+ const {
74039
+ runStartupContainerPreflight: runStartupContainerPreflight2
74040
+ } = await Promise.resolve().then(() => (init_preflight2(), exports_preflight2));
73940
74041
  const effectiveWorkspace = sessionId ? resolveSessionLocation(sessionId)?.workspace ?? workspace : workspace;
73941
74042
  if (await runStartupContentPreflight2(effectiveWorkspace) === "cancelled") {
73942
74043
  process.exitCode = 130;
73943
74044
  return;
73944
74045
  }
74046
+ if (await runStartupContainerPreflight2(effectiveWorkspace) === "cancelled") {
74047
+ process.exitCode = 130;
74048
+ return;
74049
+ }
73945
74050
  if (import.meta.path.endsWith(".ts")) {
73946
74051
  const sourceTuiPreload = "@opentui/solid/preload";
73947
74052
  await import(sourceTuiPreload);
@@ -74047,20 +74152,6 @@ async function benchmark(args2) {
74047
74152
  return;
74048
74153
  }
74049
74154
  }
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
74155
  function wantsHelp(args2) {
74065
74156
  return args2.includes("--help") || args2.includes("-h") || args2[0] === "help";
74066
74157
  }
@@ -74076,7 +74167,6 @@ Options:
74076
74167
  --base-url <url> OpenAI-compatible provider URL
74077
74168
  --api-key-env <ENV> Environment variable containing the API key
74078
74169
  --api-key-stdin Read the API key from stdin
74079
- --no-docker Skip Farai Kali image build
74080
74170
  --no-kb, --no-knowledge Skip knowledge base content sync
74081
74171
 
74082
74172
  Examples:
@@ -74148,7 +74238,7 @@ Usage:
74148
74238
  farai
74149
74239
  farai resume [session-name-or-id]
74150
74240
  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]
74241
+ farai setup [--model provider:model] [--base-url url] [--api-key-env ENV | --api-key-stdin] [--no-kb]
74152
74242
  farai init [--target <ip-or-host>] [--name <name>] [--model provider:model]
74153
74243
  farai doctor
74154
74244
  farai model
@@ -74170,5 +74260,5 @@ Examples:
74170
74260
  `);
74171
74261
  }
74172
74262
 
74173
- //# debugId=DE3417A125CB5EC964756E2164756E21
74263
+ //# debugId=4868BF6C23AAC2A664756E2164756E21
74174
74264
  //# sourceMappingURL=index.js.map