---
title: Git SDK
description: Use garage as a git SDK — write files and commit from any JavaScript runtime with no clone required.
---

`@thegarage/sdk` exposes git as a typed async API. Your agent writes files
directly from memory and commits them through the API — no local clone, no
`git` binary, no credential plumbing.

## Install

```bash
npm install @thegarage/sdk
```

## Open a repo

```ts
import { garage } from '@thegarage/sdk'

const git = await garage('my-repo', {
  token: process.env.GARAGE_API_KEY,
})
```

Repo names are bare — the server resolves them against your active
organization. The repo is created automatically if it does not exist.
The token is any `grg_` API key with `repos:write` scope — generate one
with `garage keys create <name> --scope repos:read --scope repos:write`.
Deprecated `rpme_` keys are rejected without compatibility fallback.

For an anonymous, no-account repo, use `garage.ephemeral()`. The returned
`GitRepo` exposes `remote` and `expiresAt` for those cases.

```ts
const git = await garage.ephemeral({ ttl: '24h' })

console.log(git.remote) // storage URL (when assigned)
console.log(git.expiresAt) // ISO timestamp
```

## Write files

`git.add(path, content)` stages a file. `content` can be a `string` or
`Uint8Array`. `git.remove(path)` stages a removal.

```ts
await git.add('src/main.ts', sourceCode)
await git.add('README.md', readmeText)
await git.add('data/output.json', JSON.stringify(result, null, 2))
await git.remove('legacy/old.ts')
```

Staged changes are held in memory until you call `commit`. Nothing is
written to the remote before that, and `commit()` will throw if no
`add`/`remove` calls were made first.

## Commit

```ts
const commit = await git.commit('feat: add generated output')

console.log(commit.sha) // a1b2c3d4e5f6...
console.log(commit.url) // https://api.thegarage.sh/my-repo/commit/a1b2c3d
```

`commit` writes all staged changes as a single atomic commit to `main`
(override with `options.branch`). The returned object contains the commit
SHA, author signature, and a display URL.

**Note:**   For anonymous repos created with `garage.ephemeral()`, `commit.url` is an empty string — no public
  web view exists yet.

## Read files

`read()` returns the file as both decoded text and raw bytes. Use `bytes`
for binary content.

```ts
const file = await git.read('src/main.ts')
console.log(file.content) // string (UTF-8 decoded)
console.log(file.bytes) // Uint8Array
console.log(file.sha) // blob SHA
```

## List files

`ls()` returns a flat, recursive listing by default. Pass
`{ recursive: false }` for a one-level directory walk, or `{ path: 'src' }`
to scope to a subtree.

```ts
const tree = await git.ls()
// [{ path: "src/main.ts", sha: "...", type: "blob", mode: "100644" }, ...]
```

## API reference

```ts
interface GitRepo {
  readonly remote?: string | null
  readonly expiresAt?: string | null
  add(path: string, content: string | Uint8Array): Promise<void>
  remove(path: string): Promise<void>
  commit(message: string, options?: CommitOptions): Promise<Commit>
  read(path: string, ref?: string): Promise<Blob>
  ls(ref?: string, options?: LsOptions): Promise<TreeEntry[]>
  log(options?: LogOptions): Promise<Commit[]>
}

interface Commit {
  sha: string
  url: string
  author: { name: string | null; email: string | null; date: string | null }
  message: string
}

interface CommitOptions {
  branch?: string // default: "main"
  author?: { name: string; email: string }
}

interface Blob {
  content: string
  bytes: Uint8Array
  sha: string
}

interface TreeEntry {
  path: string
  sha: string
  type: 'tree' | 'blob'
  mode: string
}

interface LsOptions {
  path?: string
  recursive?: boolean // default: true
}

interface LogOptions {
  ref?: string
  limit?: number // default: 50
}
```

## Low-level oRPC client

`createClient({ baseUrl, apiKey })` returns the typed oRPC client over the
raw contract. Use it when you need a procedure that isn't on `GitRepo` —
`keys.*`, `repos.list`, `orgs.*`, `members.*`, `tokens.*`, and the rest of
the public RPC surface.

```ts
import { createClient } from '@thegarage/sdk'

const client = createClient({
  baseUrl: 'https://api.thegarage.sh',
  apiKey: process.env.GARAGE_API_KEY!,
})

const { items } = await client.repos.list({})
```

API keys must use the `grg_` prefix and are sent as
`Authorization: Bearer <key>`.

---

## Example: commit AI-generated code

The SDK composes directly with the [AI SDK](https://sdk.vercel.ai). Generate
text with your model, write it to a repo, and commit — in a single async
chain.

```ts
import { generateText } from 'ai'
import { garage } from '@thegarage/sdk'

const git = await garage('agent-output', {
  token: process.env.GARAGE_API_KEY!,
})

const { text } = await generateText({
  model: yourModel,
  system: 'You are an expert TypeScript engineer.',
  prompt: 'Write a function that parses markdown frontmatter',
})

await git.add('src/parse-frontmatter.ts', text)
const commit = await git.commit('feat: add frontmatter parser')

console.log(commit.url)
// → https://api.thegarage.sh/agent-output/commit/a1b2c3d
```

## Example: save agent outputs across a loop

```ts
import { generateText } from 'ai'
import { garage } from '@thegarage/sdk'

const git = await garage('research', {
  token: process.env.GARAGE_API_KEY!,
})

const topics = ['transformers', 'diffusion models', 'rl from human feedback']

for (const topic of topics) {
  const { text } = await generateText({
    model: yourModel,
    prompt: `Write a concise technical summary of: ${topic}`,
  })

  await git.add(`summaries/${topic.replaceAll(' ', '-')}.md`, text)
}

const commit = await git.commit('chore: add research summaries')
console.log(`${topics.length} files committed → ${commit.url}`)
```

## Example: anonymous ephemeral repo

No token, no account — repo expires after 24 hours by default (max 72h).
Useful for one-shot agent runs that write through the SDK.

```ts
import { garage } from '@thegarage/sdk'

const git = await garage.ephemeral({ ttl: '24h' })

await git.add('output.md', markdownReport)
await git.commit('initial')

console.log(git.remote)
console.log(git.expiresAt)
```

Pass `configDir` to persist the anonymous identifier across runs (so a
later call resumes the same repo instead of creating a new one):

```ts
const git = await garage.ephemeral({
  ttl: '24h',
  configDir: `${process.env.HOME}/.config/garage`,
})
```

**Note:**   Anonymous repo claiming is not available yet. Create a signed-in repo when you need permanent
  retention.

  - [Anonymous SDK repos](/docs/anon-push): Create ephemeral repos from the SDK. Plain git push is not available yet.
  - [CLI reference](/docs/cli): Manage repos from the command line.
