github
github.
GitHub — repositories: profile, branches, commits, tags, releases.
setup & usage
create a token in GitHub (Settings → Developer settings → Personal access tokens), then paste it into oto.
- classic — tick *scopes*: repo (private repositories, issues, PRs), read:org (organizations and teams), workflow (GitHub Actions)
- fine-grained — tick *permissions* (Contents, Issues, Pull requests, Actions, Members…) and the list of repositories the token applies to. A repository missing from this list is invisible, even with the right permission
https://<your-host>/api/v3github_repos(op="get") then github_files(op="readme")github_files(op="read", path="src/x.py") — the content comes back decodedgithub_repos(op="compare", base="v1.2.0", head="main")github_issues(op="search", state="open")github_issues(op="create", fields={"title": "…", "body": "…"})github_pulls(op="search", state="open") then op="reviews" on a numbergithub_pulls(op="files", number=…)github_actions(op="runs", status="failure") → op="jobs" → op="logs"github_orgs(op="collaborators"), and op="permission" for a person's EFFECTIVE level (team inheritance included)github_search(op="code", q="myFunction repo:org/repo")github_orgs(op="rate_limit") (does not consume quota) and op="me"⚠️ on a private resource the token is not allowed to see, GitHub answers 404, not 403 — on purpose, so as not to disclose its existence.
"repository not found" therefore means, in order of likelihood: the token does not have the repo scope; or (fine-grained) this repository is not in the token's list; or the organization enforces an SSO authorization that the token has not yet received. Checking the name comes last.
⚠️ at GitHub, the two share the same numbering and the same list endpoint. Consequences:
github_issues(op="search") drops PRs by default, without which "how many open tickets?" gives a wrong number, often by a lot. include_pull_requests=true returns the API's raw responsegithub_issues with its numbergithub_search(op="issues", q="repo:org/repo is:issue is:open")dry_run=false to trigger. The workflow must declare workflow_dispatch, otherwise 404 ("not triggerable", not "does not exist"), and the response does not return the created run: find it by listing the runs right afterwardsmerge adds a merge commit, squash squashes the branch into a single commit, rebase rewrites the commits — three different effects on history. Passing sha protects against the race: if the head moved since the read, GitHub refuses instead of merging something elsePENDING): nothing is published, nobody is notified, and it is only visible to its author. APPROVE can unblock a protected merge — it is an act of governanceop="list"): without it, 409. This is GitHub's concurrency control, which guarantees we replace the version read. Each write is a real commit, attributed to the token holder⚠️ member of an organization, member of a team, collaborator on a repository: removing someone from one does not remove them from the others.
github_orgs(op="remove_member") leaves the organization and all its teamsop="remove_team_member" only touches the teamop="remove_collaborator" only touches one repository, and does not remove access inherited from a team — then check with op="permission"⚠️ op="members" only shows what the token is allowed to see: without read:org, only public members come out — a shorter list, with no error. It is not a census. And op="set_membership" / op="add_collaborator" send an invitation: access is only effective once accepted.
pagetotal_count announces (which is an estimate of the corpus, not a number of retrievable rows). github_search therefore surfaces a troncature block, which is also true when GitHub gave up the search midwayrepo:x alone is not enough. It does not return the content: read it afterwards with github_files(op="read")github_pulls(op="files") is capped at 3,000 files (and omits big patches), op="commits" at 250; github_repos(op="commit") at 300 files. A massive PR or commit is returned incomplete, without errorthis connector accepts multiple comptes: each stored credential becomes a named account (one name per compte), at your level, your team's or your org's.
_account="<name>" on the tool; list them: oto_identity(op='list', connector='github') (scope='org' or scope='group' for the org's or team's)oto_identity(op='set', connector='github', identity_id='<name>'); rename: op='rename' with new_name_account) and adding upoutils
usage
claude plugin marketplace add otomata-tech/oto-plugin — mcp + skill configured.pipx install oto-cli then oto github …