Phorge documentation
Phorge gives coding agents a clone-free repository API while preserving standard Git over HTTPS. Each tenant uses its own namespace and customer-signed credentials.
Contents
- Install
- Authentication
- Repositories
- Commits
- Git over HTTPS
- Read without cloning
- Errors
- SDK symbol reference
- HTTP API reference
Install
Create a namespace. Its private signing key is generated in your browser and downloaded once. Store the PEM as a secret; do not commit it.
npm install phorge
Load the downloaded key and create a storage client:
import { readFile } from "node:fs/promises";
import { GitStorage } from "phorge";
const privateKey = await readFile(
"./personal-phorge-private-key.pem",
"utf8",
);
const store = new GitStorage({
name: "personal",
key: privateKey,
});
const repo = await store.createRepo({
id: "workspace",
ttlSeconds: 86_400,
});
const result = await repo
.createCommit({
targetBranch: "main",
commitMessage: "Initialize workspace",
author: {
name: "Agent",
email: "agent@example.com",
},
})
.addFileFromString("README.md", "# Workspace\n")
.send();
console.log(result.commitSha);
The supplied author is also the Git committer by default. Pass a separate committer identity only when they differ. An optional actor on GitStorage adds an actor claim for request auditing; it does not change Git authorship.
Authentication
Each namespace can trust multiple ES256 or RS256 public signing keys. The SDK signs narrowly scoped JWTs containing the namespace, repository, permissions, issue time, expiry, and an optional actor. Phorge accepts the credential only when its signature matches an active public key. The private key never leaves your process.
Credentials can be limited to one repository, short validity windows, selected branch patterns, and allowed operations. Phorge verifies those limits on each request.
Add a separate key for each person, service, or agent from the dashboard. Invited members enroll their own key after accepting the shared invitation link. The rotation wizard generates and downloads a replacement, activates it, and then revokes the old key. If a private key is lost, sign in to the dashboard, add a replacement, and revoke the lost key. At least one active key must remain.
Each namespace has one owner. Owners can promote members to admins. Admins can manage repositories, invitations, and signing keys, while role changes and namespace deletion remain owner-only.
Repositories
Repository creation is idempotent. Calling createRepo for a repository you already own returns the existing repository.
const repo = await store.createRepo({
id: "workspace",
ttlSeconds: 86_400,
private: true,
});
idis the repository slug.- Omit
ttlSeconds, or set it to0, for a repository that does not expire. - Set a positive
ttlSecondsto schedule automatic deletion. privaterequires signed reads and defaults totrue.
Commits
Build commits from strings, bytes, deletions, or unified diffs. Set expectedHeadSha when agents may race; a moved ref returns HTTP 412 without changing the branch.
const result = await repo
.createCommit({
targetBranch: "main",
commitMessage: "Add worker",
expectedHeadSha: current.sha,
author: { name: "Agent", email: "agent@example.com" },
})
.addFileFromString("src/worker.ts", source)
.deletePath("src/old-worker.ts")
.send();
console.log(result.commitSha);
Use createCommitFromDiff to apply a unified diff and create its commit in one request.
Git over HTTPS
Stock Git works without plugins. The SDK clone helper signs its own short-lived credential and defaults to a fast shallow, blob-filtered clone:
const repo = store.repo("workspace");
await repo.clone("./workspace");
To use the stock Git client directly, generate a remote URL and use it before it expires:
const url = await repo.getRemoteURL({
scope: "read",
ttl: 600,
});
git clone '<generated-url>' workspace
The dashboard also has Generate clone link beside each repository. It creates a read-only link for the selected repository that expires after 10 minutes. The dashboard credential is authorized by your signed-in membership; it does not require or upload a namespace private key.
A generated URL contains a credential. Do not log, commit, or share it. Git records the URL as the remote, but its embedded credential stops working when it expires.
For push access, create a customer-signed URL with scope: "write". Use operations and refPolicies to restrict it:
const writeURL = await repo.getRemoteURL({
scope: "write",
ttl: 600,
operations: ["no-force-push"],
refPolicies: ["refs/heads/agent-*"],
});
Clone, fetch, and push work with the stock Git client and an appropriately scoped remote URL.
Read without cloning
Repository methods include:
listFilesandgetFileStream.listCommits,getCommit,getCommitDiff, andgetBranchDiff.grep,blame, andgetArchiveStream.
Reads accept a branch, tag, or commit SHA. File streams support byte ranges and ETags.
Errors
Failures use stable machine-readable codes. SDK methods throw typed errors containing the HTTP status, code, message, and structured details.
{
"code": "HEAD_MOVED",
"message": "ref has moved",
"details": { "expected": "…", "actual": "…" }
}