Repo

Repos: git repositories for the code your agents and apps write.

repo = noclick.Repo.from_name("agent-tools", create_if_missing=True)
repo.commit({"src/index.ts": source}, message="Add the lookup route")
print(repo.read("src/index.ts").decode())
print(repo.clone_url())   # plain git over HTTPS, a short-lived token inside

A repo is plain git: clone it, push to it, or move it to GitHub whole. A project (noclick.Project) deploys one to a URL.

class Repo

A git repository NoClick hosts. Repo.from_name("agent-tools", create_if_missing=True) is lazy; commit to it from code, or clone it and push with plain git.

Attributes
idstr
namestr
default_branchstr

The branch a project deploys to production (main unless set).

size_bytesint | None
last_commitCommit | None

The newest commit on the default branch (None in an empty repo).

used_bylist[dict[str, Any]]

The apps that deploy it: kind (project), id and name.

user_idstr | None
created_atAny

createclassmethodasync

noclick.Repo.create(name, *, default_branch=None, user=..., client=None) -> Repo

Create an empty repo called name (unique per user; a duplicate raises noclick.Conflict with existing_id).

Parameters
namestrrequired
default_branchstr | Nonedefault None
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
Repo

The new repo, loaded, with no commits yet.

Usage

Create one for your account's own code:

repo = noclick.Repo.create("agent-tools")
print(repo.name, repo.default_branch)   # agent-tools main

Inside as_user it's that user's, and bills to them:

with noclick.as_user("user_42"):
    site = noclick.Repo.create("portfolio", default_branch="production")

from_nameclassmethod

noclick.Repo.from_name(
    name, *, create_if_missing=False, default_branch=None, user=..., client=None
) -> Repo

A lazy reference to the repo called name in the current (or given) user. With create_if_missing=True it's created, empty, on first use; an existing repo is used as it is.

Parameters
namestrrequired
create_if_missingbooldefault False
default_branchstr | Nonedefault None
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
Repo

A repo that loads, or is created, the first time it's used. Nothing is sent yet.

Usage

The same line works on the first run and every run after:

repo = noclick.Repo.from_name("agent-tools", create_if_missing=True)
repo.commit({"README.md": "# Agent tools\n"}, message="Start the repo")

Without create_if_missing a missing repo raises noclick.NotFound at first use:

repo = noclick.Repo.from_name("booking", user="acme")
print(repo.last_commit)

from_idclassmethod

noclick.Repo.from_id(repo_id, *, client=None, user=...) -> Repo

A lazy reference to a repo by id.

Parameters
repo_idstrrequired
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
Returns
Repo

A repo that loads the first time you read a field. Nothing is sent yet.

Usage
repo = noclick.Repo.from_id("c4e1a7b2-5d3f-4a8e-9b6c-1f2d3e4a5b60")
for branch in repo.branches():
    print(branch.name, branch.head)

getclassmethodasync

noclick.Repo.get(repo_id, *, user=..., client=None) -> Repo

Load a repo by id now; an unknown id raises noclick.NotFound.

Parameters
repo_idstrrequired
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
Repo

The repo, loaded.

Usage
repo = noclick.Repo.get("c4e1a7b2-5d3f-4a8e-9b6c-1f2d3e4a5b60")
print(repo.name, repo.size_bytes, repo.last_commit)

listclassmethodasync

noclick.Repo.list(*, limit=None, user_filter=None, user=..., client=None) -> list[Repo]

The current user's repos (the account's own outside as_user, or those user_filter covers).

Parameters
limitint | None

Return at most this many.

(default None)
user_filterAny

Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
list[Repo]

Every matching repo, newest first, fetched page by page.

Usage
for repo in noclick.Repo.list():
    print(repo.name, repo.default_branch, repo.size_bytes)

everyone = noclick.Repo.list(user_filter=noclick.ALL_USERS)   # every user's, plus your account's

list_pageclassmethodasync

noclick.Repo.list_page(
    *, cursor=None, limit=None, user_filter=None, user=..., client=None
) -> Page

One page of repos; pass next_cursor back as cursor.

Parameters
cursorstr | None

The next_cursor of the page before; omit it for the first page.

(default None)
limitint | None

Return at most this many.

(default None)
user_filterAny

Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.

(default None)
userAny

Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).

(defaults to the current user)
clientClient | None

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

(default None)
Returns
Page

A page whose data holds the repos and whose next_cursor is None on the last page.

Usage
page = noclick.Repo.list_page(limit=50)
while True:
    for repo in page.data:
        print(repo.name)
    if page.next_cursor is None:
        break
    page = noclick.Repo.list_page(limit=50, cursor=page.next_cursor)

clone_urlasync

repo.clone_url(*, write=False, ttl_s=None) -> str

A git URL with a short-lived token inside, for git clone, fetch and, with write=True, push. ttl_s is 3,600 by default, at most 86,400; the token stops working after it. The token reaches this repo only. Clones and pushes cost nothing extra, within fair use. An agent's writing URL (asked for through NoClick's MCP server, by the coordinator or by a platform agent) goes through NoClick, which never lets it force-push or delete the default branch, and keeps the old head of any other branch its push rewrites or deletes (backups).

Parameters
writebooldefault False
ttl_sint | Nonedefault None
Returns
str

An https:// URL that plain git accepts as it is. Treat it as a secret until it expires.

Usage

Clone the repo on a build machine:

import subprocess

repo = noclick.Repo.from_name("agent-tools")
subprocess.run(["git", "clone", repo.clone_url(), "agent-tools"], check=True)

Let a sandbox push for the next ten minutes:

repo = noclick.Repo.from_name("agent-tools")
url = repo.clone_url(write=True, ttl_s=600)
with noclick.Sandbox.create(network=True) as sb:
    sb.exec(["git", "clone", url, "/workspace/agent-tools"])

checkoutinvite-only beta

repo.checkout(*, branch=None, path=None, push=None) -> RepoCheckout

This repo checked out in an agent's workspace, for Agent(repos=[...]): cloned into /workspace/<path> (the repo's name when omitted) at a conversation's first turn, on branch (the default branch when omitted). push is the branches the agent may push to, exact or prefix/*; without it the checkout is read-only. Sends nothing.

Parameters
branchstr | Nonedefault None
pathstr | Nonedefault None
pushstr | Sequence[str] | Nonedefault None
Returns
RepoCheckout

A RepoCheckout to list in an agent's repos.

Usage

Let an agent change the site on branches of its own, for review:

site = noclick.Repo.from_name("booking")
builder = noclick.Agent.from_name(
    "site-builder",
    create_if_missing=True,
    instructions="You make the changes asked for in /workspace/booking, on a new branch, and push it.",
    repos=[site.checkout(push="agent/*")],
)

commitasync

repo.commit(files, *, message, branch=None, author=None, exclude=()) -> Commit | None

Commit files in one commit: each path maps to its new content (text, or bytes for binary files) or to None to delete it. The commit goes on branch (the default branch when omitted), which is created from the default branch's head if it doesn't exist yet. At most 1,000 files and 50 MB in one commit.

files may instead be a folder on disk: the branch's tree is made to match it, and only what changed is sent (new and changed files written, files the folder no longer has deleted), so running it again after no change commits nothing. .git is skipped, and so is any path matching one of the exclude globs (["node_modules", "*.log"]), which also keeps the repo's own copies of those paths.

Parameters
filesMapping[str, str | bytes | None] | str | os.PathLike[str]required
messagestrrequired
branchstr | Nonedefault None
authorstr | Nonedefault None
excludeSequence[str]default ()
Returns
Commit | None

The new commit, with its sha; for a folder with nothing changed, None.

Usage

Write two files and delete one, as one commit:

repo = noclick.Repo.from_name("agent-tools", create_if_missing=True)
commit = repo.commit(
    {"src/index.ts": "export const lookup = () => 42;\n", "package.json": '{"name": "agent-tools"}', "old.txt": None},
    message="Add the lookup route",
)
print(commit.sha, commit.files_changed)

Commit to a branch of its own, for a preview deployment, and name the author:

repo = noclick.Repo.from_name("booking")
repo.commit(
    {"app/routes/book.tsx": "export default function Book() { return null; }\n"},
    message="Try the new booking form",
    branch="new-form",
    author="support-bot <support-bot@agents.noclick.app>",
)

Commit a folder a build wrote, sending only what changed since the last run:

site = noclick.Repo.from_name("marketing-site", create_if_missing=True)
commit = site.commit("./dist", message="Rebuild the site", exclude=["*.map"])
print(commit.sha if commit else "nothing changed")

readasync

repo.read(path, *, ref=None) -> bytes

Read one file at ref (a branch, tag or commit sha; the default branch's head when omitted). A missing path raises noclick.NotFound.

Parameters
pathstrrequired
refstr | Nonedefault None
Returns
bytes

The file's bytes exactly as committed; decode text yourself.

Usage
repo = noclick.Repo.from_name("booking")
manifest = repo.read("package.json", ref="main").decode()
print(manifest)

filesasync

repo.files(prefix='', *, ref=None, recursive=False) -> list[RepoFile]

What's in the directory prefix (the root when empty) at ref. recursive=True lists every file below it instead, without the directories.

Parameters
prefixstrdefault ''
refstr | Nonedefault None
recursivebooldefault False
Returns
list[RepoFile]

Its files and directories, directories first, each by path from the repo's root.

Usage

Walk the tree one level at a time:

repo = noclick.Repo.from_name("booking")
for entry in repo.files("app/", ref="main"):
    print(entry.type, entry.path, entry.size)

Or read every file under a directory:

repo = noclick.Repo.from_name("booking")
for entry in repo.files("app/routes/", recursive=True):
    print(entry.path, len(repo.read(entry.path)))

branchesasync

repo.branches() -> list[Branch]

The repo's branches, the default one first.

Returns
list[Branch]

Each branch with its head sha and when it last moved.

Usage
repo = noclick.Repo.from_name("booking")
for branch in repo.branches():
    print(branch.name, branch.head, branch.updated_at)

backupsasync

repo.backups(*, branch=None, limit=50) -> list[RepoBackup]

The branches agents' pushes rewrote or deleted, as NoClick kept them first: newest first, each for 30 days. Through NoClick an agent never force-pushes or deletes the default branch at all.

Parameters
branchstr | Nonedefault None
limitint | None

Return at most this many.

(default 50)
Returns
list[RepoBackup]

Up to limit backups (None for all of them), of branch alone when it's given.

Usage
repo = noclick.Repo.from_name("booking")
for backup in repo.backups(branch="agent/new-form"):
    print(backup.reason, backup.sha[:7], backup.created_at)

restore_backupasync

repo.restore_backup(backup) -> Branch

Make a backup's branch again at the commit it was kept at (backup: a RepoBackup or its id). When the repo has a branch by that name again, the backup comes back as <branch>-restored-<time> instead; nothing that's there moves.

Parameters
backupRepoBackup | strrequired
Returns
Branch

The branch made, with its head.

Usage
repo = noclick.Repo.from_name("booking")
[latest] = repo.backups(branch="agent/new-form", limit=1)
print(repo.restore_backup(latest).name)

commitsasync

repo.commits(*, branch=None, limit=20) -> list[Commit]

The history of branch (the default branch when omitted), newest first.

Parameters
branchstr | Nonedefault None
limitint | None

Return at most this many.

(default 20)
Returns
list[Commit]

Up to limit commits (20 by default; None for the whole history), fetched page by page.

Usage
repo = noclick.Repo.from_name("booking")
for commit in repo.commits(branch="main", limit=5):
    print(commit.sha[:7], commit.author, commit.message)

commits_pageasync

repo.commits_page(*, branch=None, cursor=None, limit=None) -> Page

One page of commits, newest first; pass next_cursor back as cursor.

Parameters
branchstr | Nonedefault None
cursorstr | None

The next_cursor of the page before; omit it for the first page.

(default None)
limitint | None

Return at most this many.

(default None)
Returns
Page

A page whose data holds Commits and whose next_cursor is None on the last page.

Usage
repo = noclick.Repo.from_name("booking")
page = repo.commits_page(branch="main", limit=50)
while True:
    for commit in page.data:
        print(commit.sha[:7], commit.message)
    if page.next_cursor is None:
        break
    page = repo.commits_page(branch="main", limit=50, cursor=page.next_cursor)

diffasync

repo.diff(sha) -> CommitDiff

What the commit sha (full or short) changed against its first parent. An unknown commit raises noclick.NotFound.

Parameters
shastrrequired
Returns
CommitDiff

The commit with each file it changed, the file's status, the lines it added and deleted and its patch in git's unified format.

Usage
repo = noclick.Repo.from_name("booking")
[latest] = repo.commits(limit=1)
changes = repo.diff(latest.sha)
print(f"+{changes.additions} -{changes.deletions}")
for file in changes.files:
    print(file.status, file.path)
    print(file.patch or "(not shown)")

exportasync

repo.export() -> Operation

Package the repo to take elsewhere: every branch and its history as a git bundle.

Returns
Operation

An operation whose .wait() gives an noclick.ExportBundle: a zip at url (a link that expires) holding the bundle, which git clone reads as it is.

Usage
repo = noclick.Repo.from_name("agent-tools")
bundle = repo.export().wait()
print(bundle.url, bundle.expires_at)

export_to_githubasync

repo.export_to_github(*, connection, owner, name=None, private=True) -> Operation

Copy the repo, every branch and tag with its history, into a new GitHub repository owner/name (name defaults to this repo's), through connection: a GitHub connection (a Connection or its name) of the repo's user, or of the account. owner is the connected account's own login or an organization it may create repositories in. The NoClick repo stays as it was.

A connection with no GitHub account connected yet raises noclick.NeedsConnection, whose connect_url connects it. The operation fails with noclick.Conflict when GitHub already has owner/name, and noclick.PermissionDenied when the connected account can't create repositories there.

Parameters
connectionAnyrequired
ownerstrrequired
namestr | Nonedefault None
privatebooldefault True
Returns
Operation

An operation whose .wait() gives the new GitHub repository's URL.

Usage

Hand a repo to its owner's GitHub organization:

repo = noclick.Repo.from_name("agent-tools")
try:
    url = repo.export_to_github(connection="github", owner="acme").wait()
    print(url)   # https://github.com/acme/agent-tools
except noclick.NeedsConnection as needed:
    print("Connect GitHub first:", needed.connect_url)

deleteasync

repo.delete() -> TrashItem

Delete the repo and its history: it goes to the trash, restorable for 30 days. A repo an app deploys from (see used_by) is refused with noclick.Conflict; delete that app first.

Returns
TrashItem

Its TrashItem: restore() puts it back, history and all.

Usage
repo = noclick.Repo.from_name("scratch")
repo.delete()

Commit

class Commit

One commit: its sha, message and author, the branch it was made on, and files_changed, how many files it added, changed or deleted.

Attributes
shastr
messagestr
authorstr | None
branchstr | None
created_atAny
files_changedint | None
rawdict[str, Any]

The response JSON exactly as the API sent it.

CommitDiff

class CommitDiff

What one commit changed against its first parent: the commit, its parents (none for the first commit), the lines it added and deleted in all, and each file it changed.

Attributes
commitCommit
parentslist[str]
additionsint
deletionsint
fileslist[FileChange]

FileChange

class FileChange

One file a commit changed: its path (and old_path when it was renamed), its status (added, modified, deleted, renamed, copied or type_changed), the lines it added and deleted, its size in bytes, and patch, its unified diff in git's format. patch (and the line counts) are None for a file that isn't shown: one that isn't text, or is too large.

Attributes
pathstr
statusstr
additionsint | None
deletionsint | None
sizeint | None
patchstr | None
old_pathstr | None
rawdict[str, Any]

The response JSON exactly as the API sent it.

Branch

class Branch

A branch: its name, the sha of its head commit, and when it last moved.

Attributes
namestr
headstr | None
updated_atAny
rawdict[str, Any]

The response JSON exactly as the API sent it.

RepoBackup

class RepoBackup

A branch's old head NoClick kept before an agent's push rewrote it (reason "rewritten": a force-push) or deleted it ("deleted"): the branch, the commit sha it was at, the platform agent whose push it was (agent_id, else None), and when it was kept. It's kept 30 days, until expires_at, out of the branch list and clones; Repo.restore_backup makes the branch again.

Attributes
idstr
branchstr
shastr
reasonstr
agent_idstr | None
created_atAny
expires_atAny
rawdict[str, Any]

The response JSON exactly as the API sent it.

RepoFile

class RepoFile

An entry in a repo's tree: its path from the root, its type (file or dir) and, for a file, its size in bytes and sha, git's blob id for its content (it changes whenever the content does).

Attributes
pathstr
typestr
sizeint | None
shastr | None
rawdict[str, Any]

The response JSON exactly as the API sent it.

RepoCheckout

class RepoCheckout

A git repository an agent works in, for Agent(repos=[...]): a connection's repository (connection.checkout("acme/docs")) or a NoClick repo (repo.checkout(); invite-only beta). NoClick clones it into /workspace/<path> (the repository's name when path is omitted) at a conversation's first turn, and the agent's git reaches that repository only, through NoClick, with a key made for its sandbox; the connection's own token never enters it. push lists the branches it may push to: exact names, or a prefix ending in /* ("noclick/*" reaches noclick/fix and noclick/a/b); NoClick's git endpoint refuses any other push, and with no push the checkout is read-only. Whatever push allows, the agent can't force-push or delete the repository's default branch, and before its push rewrites or deletes any other branch NoClick keeps the old head as a backup for 30 days (on a git host, refs/noclick/backups/<branch>/<time> in the repository; push it back to restore it). A policy can't do this job: a coding harness runs git push in its own shell, which is no tool call for a policy to judge. gh only reads the repository there.

Attributes
repositorystr | None

owner/name on the connection's service (None for a NoClick repo).

connectionAny
repoAny
branchstr | None

The branch it checks out (the repository's default when None).

pathstr | None

The folder under /workspace it's cloned into.

pushlist[str] | None

Branches it may push to, exact or prefix/* (None: read-only).