Clone repositories and use git
Start a sandbox with your repository already cloned, public or private, then read status and diffs, commit, branch and push from code.
Clone at create
import { Sandbox } from "@tangle-network/sandbox";
const client = new Sandbox({
apiKey: process.env.TANGLE_API_KEY!,
baseUrl: "https://sandbox.tangle.tools",
});
// The clone finishes before create() resolves.
const box = await client.create({
git: { url: "https://github.com/tangle-network/sandbox-demo", ref: "main", depth: 1 },
});
try {
const status = await box.git.status();
console.log(status.branch, status.head, status.isDirty);
console.log((await box.exec("ls")).stdout);
} finally {
await box.delete();
}git field | Effect |
|---|---|
url | The HTTPS URL to clone. |
ref | Branch, tag or commit to check out. |
depth | Shallow clone depth. |
sparse | Paths for a sparse checkout. |
auth | { token } or { grant: "read" } for a private repository. |
The repository is cloned into the workspace, so exec, read and the coding agent start inside it.
Clone a private repository
With a connected GitHub account
If your account has connected the repository through the Tangle GitHub App, ask for a read grant instead of passing a token. The platform mints a short-lived token for that one repository, read only, uses it for the clone, and never returns it or writes it to a log.
const box = await client.create({
git: { url: "https://github.com/acme/private-service", auth: { grant: "read" } },
});The API key must hold the git:grant scope, and the URL must be https://github.com/<owner>/<repo>. The create is refused with one of these codes:
| Code | Cause |
|---|---|
GIT_GRANT_SCOPE_REQUIRED | The API key lacks git:grant. |
GIT_GRANT_UNSUPPORTED_URL | The URL is not a GitHub repository URL. |
GIT_GRANT_NOT_CONNECTED | Your account has not connected the repository. |
GIT_GRANT_ACCESS_DENIED | GitHub does not show you access to it. |
grant and token are exclusive, and an isolated create refuses a grant.
With a token
const box = await client.create({
git: { url: "https://github.com/acme/private-service", auth: { token: process.env.GITHUB_TOKEN! } },
});In the CLI, the token never goes in argv: --git-token-env GITHUB_TOKEN names an environment variable that holds it, or --git-token-stdin reads it from stdin.
Work with the repository
const status = await box.git.status();
console.log(status.modified, status.untracked, status.ahead, status.behind);
const diff = await box.git.diff();
for (const file of diff.files) console.log(file.status, file.path, `+${file.additions} -${file.deletions}`);
await box.git.checkout("fix/null-guard", { create: true });
await box.git.add(["src/server.js"]);
const commit = await box.git.commit("fix: guard a null request body");
console.log(commit.sha, commit.message);
for (const c of await box.git.log(5)) console.log(c.shortSha, c.message);
for (const b of await box.git.branches()) console.log(b.current ? "*" : " ", b.name);
await box.git.pull({ rebase: true });
await box.git.push();| Method | Returns |
|---|---|
status() | branch, head, isDirty, ahead, behind, staged, modified, untracked. |
diff(ref?) | files with status and line counts, totals, and the unified diff in raw. |
log(limit?) | Commits with sha, shortSha, message, author, email, date. |
branches() | Branches with name, current, upstream and commit. |
add(paths), commit(message, { amend }), push({ force }), pull({ rebase }), checkout(ref, { create }) | Run the matching git command. |
push needs write access to the remote. A grant token is read only.
Save the agent’s change as a patch
import { writeFile } from "node:fs/promises";
const { raw } = await box.git.diff();
await writeFile("agent.patch", raw);Apply it locally with git apply agent.patch.
Let a coding agent fix a test, then commit
import { Sandbox } from "@tangle-network/sandbox";
const client = new Sandbox({
apiKey: process.env.TANGLE_API_KEY!,
baseUrl: "https://sandbox.tangle.tools",
});
const box = await client.create({
git: { url: "https://github.com/tangle-network/sandbox-demo" },
backend: { type: "codex" },
});
try {
const run = await box.prompt("The test tagged null-guard fails. Fix the cause.");
if (!run.success) throw new Error(run.error);
console.log((await box.exec("git status --short")).stdout.trim());
const tests = await box.exec("npm test -- --test-name-pattern=null-guard");
if (tests.exitCode !== 0) throw new Error("The test still fails.");
await box.exec('git -c user.name=Codex -c [email protected] commit -am "fix: guard a null request body"');
console.log((await box.exec("git log --oneline -1")).stdout.trim());
} finally {
await box.delete();
}From the CLI
tangle git status demo
tangle git diff demo --ref main
tangle git log demo --limit 5
tangle git checkout demo fix/null-guard --create
tangle git add demo src/server.js
tangle git commit demo -m "fix: guard a null request body"
tangle git branches demo
tangle git pull demo --rebase
tangle git push demoEach command resumes a stopped sandbox first.
Python and REST
Python’s box.git has the same nine operations: status, log(limit=10), diff(ref=None), add(paths), commit(message, amend=False), push(force=False), pull(rebase=False), branches() and checkout(ref, create=False). GitConfig and GitAuth take url, ref, depth, sparse and a token. For a read grant from Python, send the create through client.fetch, which returns the raw response and does not raise on an error status:
response = client.fetch(
"POST",
"/v1/sandboxes",
json={"git": {"url": "https://github.com/acme/private-service", "auth": {"grant": "read"}}},
)