Ask a repository
Clone a git repository into a session's shell and let an agent answer your questions about it by reading the code (ls, grep, cat, git log), not a web page about it. Every answer names the files it rests on, and the example checks that those files exist.
When to use it: You want answers about a repository from its code, with the files they rest on.
| Uses | a shell session without a browser, exec, an agent run in that session with an output schema, files |
|---|---|
| Needs | shell sessions and a model on the API (no model key of your own) |
| Site | any git repository the machine can clone; the runner uses a stand-in repository |
| Output | output/answers.md, output/result.json (answers, files, the agent's tool steps, its run id) |
| Languages | Node and Python |
The agent works in the same session the repository was cloned into, and that session has no browser: it can only use the shell, inside the machine. It is told not to change anything. Its answer is JSON (output schema): each question with its answer and the files it read.
The code
The example as it is in its folder. Both versions do the same job and write the same output/, so one result check covers both.
/** * Ask a repository: clone a git repository into a session's shell, then let an agent answer questions about it by * reading the code (ls, grep, cat, git log), not a web page about it. Every answer names the files it rests on. * * npx tsx node/index.ts (BOXLINE_API_KEY; REPO_URL, QUESTIONS separated by |; a plan with shell sessions) * * The agent works in a session without a browser: only the shell, in the machine. Writes output/result.json and * output/answers.md. */import { mkdirSync, writeFileSync } from "node:fs";import { join } from "node:path";import { Boxline } from "@boxline/sdk";const out = process.env.OUTPUT_DIR ?? "output";const repoUrl = process.env.REPO_URL ?? "https://github.com/expressjs/cors.git";const questions = (process.env.QUESTIONS ?? "What does this project do, in two sentences?|How do I use it? Show the smallest example.|How are its tests run, and with which test framework?") .split("|") .map((q) => q.trim()) .filter(Boolean);const bx = new Boxline();type Answers = { answers: { question: string; answer: string; files: string[] }[] };const session = await bx.sessions.create({ browser: false, shell: true, timeout: 900, idleTimeout: 300, userMetadata: { example: "ask-a-repo" } });console.log(`Session: ${session.id}`);try { // 1. The repository goes into the machine (the address as a variable, not inside the command); shallow when the // server can (a "dumb" HTTP git server cannot, then all of it). const clone = await session.exec('(git clone --depth 1 --quiet "$REPO_URL" repo 2>/dev/null || git clone --quiet "$REPO_URL" repo) && git -C repo log -1 --format="%h %s" && find repo -type f -not -path "*/.git/*" | wc -l', { env: { REPO_URL: repoUrl }, timeoutMs: 180_000 }); if (clone.exitCode !== 0) throw new Error(`git clone failed: ${clone.stderr.trim()}`); const [head, count] = clone.stdout.trim().split("\n"); console.log(`Cloned ${repoUrl} at ${head} (${count?.trim()} files)`); // 2. An agent answers from the code, in the same session (its shell; there is no browser). const started = await bx.agent.run({ sessionId: session.id, task: `The git repository is checked out in /workspace/repo. Answer these questions from its files only (use the shell: ls, ` + `grep, cat, git log). Do not change anything. For each answer list the repository files it rests on, as paths ` + `relative to the repository root.\n\n${questions.map((q, i) => `${i + 1}. ${q}`).join("\n")}`, maxSteps: 25, output: { type: "object", properties: { answers: { type: "array", items: { type: "object", properties: { question: { type: "string" }, answer: { type: "string" }, files: { type: "array", items: { type: "string" } } }, required: ["question", "answer", "files"] }, }, }, required: ["answers"], }, }); console.log(`Agent run ${started.id} (${started.model}) is reading the code…`); const run = await bx.agent.wait<Answers>(started.id); if (run.status !== "completed" || !run.result) throw new Error(`the run ${run.status}: ${run.error ?? "no answer"}`); // 3. Every file an answer names must exist in the checkout (the paths go in as a file, never into the command). const files = [...new Set(run.result.answers.flatMap((a) => a.files))]; let missing: string[] = []; if (files.length) { await session.files.write("cited.txt", files.join("\n") + "\n"); const listed = await session.exec('cd repo && while IFS= read -r f; do [ -f "$f" ] || echo "$f"; done < ../cited.txt', { timeoutMs: 30_000 }); missing = listed.stdout.split("\n").filter(Boolean); } for (const a of run.result.answers) console.log(`\nQ: ${a.question}\nA: ${a.answer}\n (${a.files.join(", ") || "no files named"})`); console.log(`\n${run.steps.filter((s) => s.type === "tool").length} tool steps, $${(run.usage?.costUsd ?? 0).toFixed(4)} of model use${missing.length ? `; files named but not in the repo: ${missing.join(", ")}` : "; every file named exists"}`); const md = [`# ${repoUrl}`, "", `At ${head}.`, "", ...run.result.answers.flatMap((a) => [`## ${a.question}`, "", a.answer, "", `Files: ${a.files.map((f) => `\`${f}\``).join(", ") || "none"}`, ""])].join("\n"); mkdirSync(out, { recursive: true }); writeFileSync(join(out, "answers.md"), md); writeFileSync(join(out, "result.json"), JSON.stringify({ repoUrl, head, questions, answers: run.result.answers, missingFiles: missing, toolSteps: run.steps.filter((s) => s.type === "tool").map((s) => ({ name: s.name, input: s.input })), agentRuns: [{ id: run.id }] }, null, 2));} finally { await session.stop();}"""Ask a repository: clone a git repository into a session's shell, then let an agent answer questions about it byreading the code (ls, grep, cat, git log), not a web page about it. Every answer names the files it rests on. python python/main.py (BOXLINE_API_KEY; REPO_URL, QUESTIONS separated by |; a plan with shell sessions)The agent works in a session without a browser: only the shell, in the machine. Writes output/result.json andoutput/answers.md."""import jsonimport osfrom pathlib import Pathfrom boxline import Boxlineout = Path(os.environ.get("OUTPUT_DIR", "output"))repo_url = os.environ.get("REPO_URL", "https://github.com/expressjs/cors.git")questions = [q.strip() for q in os.environ.get("QUESTIONS", "What does this project do, in two sentences?|How do I use it? Show the smallest example.|How are its tests run, and with which test framework?").split("|") if q.strip()]bx = Boxline()OUTPUT = { "type": "object", "properties": { "answers": { "type": "array", "items": {"type": "object", "properties": {"question": {"type": "string"}, "answer": {"type": "string"}, "files": {"type": "array", "items": {"type": "string"}}}, "required": ["question", "answer", "files"]}, } }, "required": ["answers"],}with bx.sessions.create(browser=False, shell=True, timeout=900, idle_timeout=300, user_metadata={"example": "ask-a-repo"}) as session: print(f"Session: {session.id}", flush=True) # 1. The repository goes into the machine (the address as a variable, not inside the command); shallow when the # server can (a "dumb" HTTP git server cannot, then all of it). clone = session.exec('(git clone --depth 1 --quiet "$REPO_URL" repo 2>/dev/null || git clone --quiet "$REPO_URL" repo) && git -C repo log -1 --format="%h %s" && find repo -type f -not -path "*/.git/*" | wc -l', env={"REPO_URL": repo_url}, timeout_ms=180_000) if clone["exitCode"] != 0: raise SystemExit(f"git clone failed: {clone['stderr'].strip()}") head, count = (clone["stdout"].strip().split("\n") + [""])[:2] print(f"Cloned {repo_url} at {head} ({count.strip()} files)") # 2. An agent answers from the code, in the same session (its shell; there is no browser). numbered = "\n".join(f"{i}. {q}" for i, q in enumerate(questions, 1)) started = bx.agent.run( "The git repository is checked out in /workspace/repo. Answer these questions from its files only (use the shell: ls, " "grep, cat, git log). Do not change anything. For each answer list the repository files it rests on, as paths " f"relative to the repository root.\n\n{numbered}", session_id=session.id, max_steps=25, output=OUTPUT, ) print(f"Agent run {started['id']} ({started['model']}) is reading the code…", flush=True) run = bx.agent.wait(started["id"]) if run["status"] != "completed" or not run["result"]: raise SystemExit(f"the run {run['status']}: {run.get('error') or 'no answer'}") answers = run["result"]["answers"] # 3. Every file an answer names must exist in the checkout (the paths go in as a file, never into the command). files = sorted({f for a in answers for f in a["files"]}) missing = [] if files: session.files.write("cited.txt", "\n".join(files) + "\n") listed = session.exec('cd repo && while IFS= read -r f; do [ -f "$f" ] || echo "$f"; done < ../cited.txt', timeout_ms=30_000) missing = [line for line in listed["stdout"].split("\n") if line] tool_steps = [s for s in run["steps"] if s["type"] == "tool"] for a in answers: print(f"\nQ: {a['question']}\nA: {a['answer']}\n ({', '.join(a['files']) or 'no files named'})") print(f"\n{len(tool_steps)} tool steps, ${(run.get('usage') or {}).get('costUsd', 0):.4f} of model use" + (f"; files named but not in the repo: {', '.join(missing)}" if missing else "; every file named exists")) md = [f"# {repo_url}", "", f"At {head}.", ""] for a in answers: md += [f"## {a['question']}", "", a["answer"], "", "Files: " + (", ".join(f"`{f}`" for f in a["files"]) or "none"), ""] out.mkdir(parents=True, exist_ok=True) (out / "answers.md").write_text("\n".join(md)) result = {"repoUrl": repo_url, "head": head, "questions": questions, "answers": answers, "missingFiles": missing, "toolSteps": [{"name": s.get("name"), "input": s.get("input")} for s in tool_steps], "agentRuns": [{"id": run["id"]}]} (out / "result.json").write_text(json.dumps(result, indent=2, ensure_ascii=False))Inputs
Environment variables the example reads:
REPO_URL(defaulthttps://github.com/expressjs/cors.git) andQUESTIONS, separated by|.
Run it
Get the examples with git clone https://github.com/Boxline-dev/examples.git and open this one: cd examples/ask-a-repo. Put your API key in BOXLINE_API_KEY (and BOXLINE_API_URL for another API), then run either version from the example’s folder. Both write to output/ and stop their sessions when they finish.
# Node 18 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keynpm install @boxline/sdk tsxnpx tsx node/index.ts# Python 3.9 or newer, in the example's folderexport BOXLINE_API_KEY=… # your API keypip install boxline-sdkpython python/main.pyThen check the result, from the same folder: npx tsx check.ts output. It exits with 0 only when the result is right.
What its check proves
check.ts reads what the example wrote and passes only when the result is right, not merely when the example ran.
Every question has an answer that names files, every named file exists in the clone, and the agent used the shell. On the runner's stand-in repository: the tests use node:test, they check the home page ("Example Widgets"), /pricing and /about, STAGING_URL must be set, and an answer cites test/pages.test.mjs.
Source
github.com/Boxline-dev/examples/ask-a-repo on GitHub, with its README.md, check.ts and .env.example. The examples are open source. See all examples.
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.