@foundation0/git
v1.3.9
Published
Foundation 0 Git API and MCP server
Maintainers
Readme
git
@foundation0/git exposes a generated Git API object where methods follow the shape repo.issue.create(...), repo.pr.merge(...), etc.
The API client maps GH-style feature paths to Gitea REST endpoints and executes real HTTP requests through fetch.
Install
Install globally:
pnpm add -g @foundation0/gitRun MCP server through npm without a global install:
npx -y -p @foundation0/git f0-git-mcp --helpUsing pnpm:
pnpm dlx @foundation0/git f0-git-mcp --helpInstallation in this monorepo
From repo root:
pnpm installRun tests:
pnpm --filter @foundation0/git testQuick start
import { createGitServiceApi } from '@foundation0/git'
const api = createGitServiceApi({
config: {
platform: 'GITEA',
giteaHost: 'https://gitea.example.com',
giteaToken: process.env.GITEA_TOKEN,
},
defaultOwner: 'example-org',
defaultRepo: 'example-repo',
})You can also import the singleton:
import { gitServiceApi } from '@foundation0/git'All methods return:
type GitServiceApiExecutionResult = {
mapping: GitApiFeatureMapping
request: {
url: string
method: string
headers: Record<string, string>
query: string[]
body?: unknown
}
status: number
ok: boolean
body: unknown
}Core request shapes
Write methods accept request fields directly (recommended), or you can set the raw request body with data / json / payload / requestBody.
await api.repo.issue.create({
title: 'Bug report',
body: 'Describe issue details',
labels: ['bug'],
})
// equivalent explicit-body form
await api.repo.issue.create({
data: { title: 'Bug report', body: 'Describe issue details', labels: ['bug'] },
})Use headers to pass custom headers and query for additional URL params.
await api.repo.issue.list({
headers: { 'X-Request-Id': 'local' },
query: { state: 'open', limit: 20, page: 1 },
})Popular workflow examples
1) Issue lifecycle (most used)
// List issues in the default repo
await api.repo.issue.list()
// Create a new issue
await api.repo.issue.create({
data: {
title: 'Login fails with 401',
body: 'Repro: open login page and click submit.',
labels: ['bug'],
},
})
// View an issue
await api.repo.issue.view('17')
// Comment on an issue
await api.repo.issue.comment('17', {
data: { body: 'We are investigating this now.' },
})
// Edit title/body
await api.repo.issue.edit('17', {
data: {
title: 'Login fails with 401 (investigating)',
body: 'Updated with steps to reproduce.',
},
})
// Close and reopen
await api.repo.issue.close('17', { reason: 'not_planned' })
await api.repo.issue.reopen('17')2) Pull request workflow
// Open a pull request
await api.repo.pr.create({
data: {
title: 'chore: improve error handling',
body: 'Adds retries and better messaging.',
head: 'feature/error-handling',
base: 'main',
},
})
// List and inspect PRs
await api.repo.pr.list({ query: { state: 'open' } })
await api.repo.pr.view('42')
// Add a review comment
await api.repo.pr.comment('42', {
data: { body: 'Looks good, please add one test for this branch.' },
})
// Manage PR lifecycle
await api.repo.pr.close('42')
await api.repo.pr.reopen('42')
await api.repo.pr.merge('42')3) Release flow
await api.repo.release.create({
data: {
tag_name: 'v1.2.0',
name: 'v1.2.0',
body: 'Changelog and upgrade notes.',
},
})
await api.repo.release.list()
await api.repo.release.view('v1.2.0')
await api.repo.release.delete('v1.2.0')4) Repository setup and sync
// Read current repo
await api.repo.view()
// Create repository for authenticated user
await api.repo.create({ data: { name: 'new-repo', description: 'Automation workspace' } })
// Fork and sync mirror repo
await api.repo.fork()
await api.repo.sync()5) Team triage with labels
await api.repo.label.listManaged()
await api.repo.label.upsert('priority-high', {
color: '#d73a4a',
description: 'Initial escalation label',
})
await api.repo.label.getByName('priority-high')
await api.repo.label.upsert('priority-high', {
color: '#fbca04',
description: 'Escalated issue',
})
await api.repo.label.deleteByName('priority-high')Actions log helpers
Use repo.actions.jobs.resolveLog as the canonical Actions log entrypoint. It accepts { owner, repo, runNumber|runUrl, jobName }, resolves the concrete /actions/jobs/{job_id}/logs identifier for the requested sibling job, and returns structured metadata including taskId, jobLogId, resolution.strategy, and logText. This is the safest path for agents because it removes the ambiguity between workflow task ids and concrete job-log ids on Gitea instances where those ids differ.
Low-level repo.actions.jobs.logs and repo.actions.jobs.logsTail now support the named form { owner, repo, jobId }, while remaining backward-compatible with positional args. They reject taskId, runNumber, and runUrl inputs on purpose and point callers back to repo.actions.jobs.resolveLog.
For excerpted views, repo.actions.jobs.logsTail, repo.actions.jobs.logsForRunTail, repo.actions.diagnoseLatestFailure, and repo.actions.jobs.resolveLog prefer a failure-centered window when the fetched log contains clear failure markers such as Failure -, ELIFECYCLE, Command failed, runner checkout errors like Non-terminating error while running 'git clone', or lint rule violations. This avoids post-job cleanup output masking the real failure. If the caller explicitly requests full logs with fullLogs: true, the helper returns the raw job log instead of the excerpted window.
When a Gitea server exposes sibling workflow runs through /actions/tasks but requires a different global id for /actions/jobs/{id}/logs, repo.actions.jobs.resolveLog, repo.actions.jobs.logsForRunTail, and repo.actions.diagnoseLatestFailure scan recent job logs and match the embedded received task <id> marker to recover the correct sibling job log. If multiple sibling jobs exist and the caller does not provide a jobName, the helper now returns a conflict instead of silently choosing the wrong job. repo.actions.diagnoseLatestFailure also now distinguishes taskId from jobLogId in its structured response while keeping jobId as a compatibility alias for the resolved jobLogId.
6) Search and discovery
await api.search.issues({
query: {
q: 'repo:example-org/example-repo is:open',
sort: 'created',
},
})
await api.search.prs({
query: {
q: 'repo:example-org/example-repo is:pr is:open',
sort: 'updated',
},
})
await api.search.repos({
query: {
q: 'name:example-repo',
limit: 10,
},
})7) CI workflow dispatch
await api.workflow.list()
await api.workflow.view('ci.yml')
await api.workflow.run('ci.yml', {
data: {
ref: 'main',
},
})
await api.workflow.disable('ci.yml')
await api.workflow.enable('ci.yml')8) Security material for automation
await api.secret.list()
await api.variable.list()
await api.variable.set('CANARY', {
data: {
value: 'true',
},
})Targeting a specific repository
Defaults can be replaced per call by passing owner/repo arguments.
await api.repo.issue.create('acme', 'infra', {
data: {
title: 'Issue in another repo',
body: 'Cross-repo task',
},
})Notes
- Some endpoints expose top-level methods too (for example
api.issue.create(...)), butrepo.*is the canonical usage style for this package. - This package currently ships with Gitea mapping, with
PLATFORM/GITEA_*configuration ready for multi-platform expansion. - Use the generated feature matrix/spec to discover every mapped command before adding new workflows.
