Skip to main content
Version: Alpha

Read-only CLI

Provider and source authority

This portable contract describes its linked source snapshot. For current deployment instructions, use current installation, authentication and native Cloudflare guides. Historical receipts do not verify later builds.

The read-only golf CLI lets people, scripts and agents inspect the same repositories, native commit diffs, pull requests, native reviews, organization memberships, provider-visible teams, own-account activity and recorded repository-creation outcomes as the browser. It does not execute coding agents, modify Git, create issues or deploy projects. Its Go client is generated from the same Protobuf/Connect contract as the browser. Authentication accepts a source-session-bound Golf API credential or scoped Forgejo token; managed OAuth/device login remains planned.

This guide describes the current developer preview, including owner-issued read-only API credentials. The authoritative product implementation ledger records exact qualified revisions and remaining scope. This is a development contract, not a stable release.

Build​

From the product checkout, with the Go version specified by go.mod:

make cli
./bin/golf --help

The CLI is bin/golf; the server produced by make build is bin/golf-server. The Compose image continues to run the server normally.

Local MCP mode​

The same binary can serve 21 authorized read tools over local stdio:

# The owner supplies GOLF_FORGE_TOKEN through the process environment.
./bin/golf mcp

Your MCP client owns stdin/stdout; both token-stdin flags are rejected in this mode. The process captures one configured token and Golf origin at startup, and tool arguments cannot override them. PAT calls use fresh login/read/logout and add session activity; Golf API credentials delegate their source session without login/logout. Both modes emit golf.mcp.v1 tool output. Ordinary commands below retain golf.cli.v1 and their existing secret-input behavior. Remote MCP, ACP and individual coding-agent client interoperability remain unqualified.

Supply credentials​

For owner-issued read-only Golf credentials, configure GOLF_API_TOKEN or pipe the secret to ./bin/golf --api-token-stdin repo list. API mode delegates its existing source session, makes no login/logout calls and saves no cookies. Ending the source session invalidates it. API and Forgejo token sources cannot be mixed.

Alternatively configure GOLF_FORGE_TOKEN through your secret manager, or pipe one token into a command using the global --token-stdin flag:

# A secret-manager command supplies stdin; do not put a token literal in history.
./bin/golf --token-stdin repo list

Without a pipe, that command reads stdin until EOF. The CLI never saves the token or cookies and has no command-line token flag. In Forgejo PAT mode each invocation signs into Golf, performs its read, and deletes its temporary session. Cleanup also runs after an operation error or normal cancellation. A forcibly terminated process can leave the session until server expiry. A cleanup failure returns an error rather than a successful command result.

The default origin is http://localhost:8080. Override it with GOLF_URL or --url https://golf.example.com before the command. HTTP is permitted only for loopback origins. Remote installations require HTTPS, and redirects are rejected.

Use read:user and read:repository for code, commit diffs, pull requests and native reviews; add read:issue for issues. Organization and team reads require read:user and read:organization; opening repository source separately requires read:repository. Account activity and creation-record inspection need only read:user; returning current repository metadata additionally requires repository scope and native access. User access remains the upper bound regardless of token scopes.

Repository and issue commands​

These examples assume GOLF_FORGE_TOKEN is already available:

./bin/golf repo list --limit 20
./bin/golf repo view owner/repository
./bin/golf repo branches owner/repository --page 1
./bin/golf repo commits owner/repository --ref feature/example
./bin/golf repo contents owner/repository --ref COMMIT_ID --path README.md
./bin/golf issue list owner/repository --state closed
./bin/golf issue view owner/repository 1

Global flags precede repo, commit, issue, pr, org, team or audit; command flags follow all positional arguments.

Inspect repository creation​

Read one durable creation intent owned by your account:

./bin/golf --token-stdin repo creation 12345678-1234-4234-8234-123456789abc

Replace the example with the returned canonical lowercase UUID v4. This command accepts no repository name or pagination flags and calls only GetRepositoryCreation. It cannot create, retry a native write or resolve an unknown outcome. Its normal ephemeral login/read/logout lifecycle requires read:user; repository metadata remains subject to repository scope and current native access.

The response contains data.creation and optional data.repository. Preserve operation/native IDs as strings. data.creation.state is REPOSITORY_CREATION_STATE_OUTCOME_UNKNOWN, REPOSITORY_CREATION_STATE_SUCCEEDED or REPOSITORY_CREATION_STATE_REJECTED. A valid lookup returns exit 0 for any recorded state; that does not mean creation succeeded. A historical success can omit data.repository after deletion, rename, access loss or replacement with another native ID.

Missing/foreign keys produce the same not_found error and no record. Invalid UUID syntax exits 2 before the request. Authentication, lookup and session-cleanup failures exit 1; the CLI logs out before emitting a successful result. Different keys or accounts must not be treated as interchangeable retry authority.

Commit diff​

Select one exact commit with the same repository-read token:

./bin/golf commit diff owner/repository FULL_LOWERCASE_COMMIT_SHA

Replace the placeholder with a full lowercase 40- or 64-character hexadecimal ID from history. Branch names, abbreviated IDs, uppercase IDs and revision expressions are not accepted. This command has no page/base/path flags and does not resolve the current branch for you.

The golf.cli.v1 result contains data.commit, data.parentCommitIds, data.baseCommitId, data.base, data.rawDiff and numeric data.byteSize. The raw text is JSON-escaped, not a patch-file stdout mode; CLI serialization includes default empty strings, zero sizes and empty parent arrays. Root commits have no base, and merge commits compare only with their first parent. Full parent identities are preserved.

The 512 KiB UTF-8 bound rejects oversize and unsafe-control content visibly rather than returning a local truncation. Native binary notices and root commit metadata, including author email, remain as supplied. An empty result can be valid. The diff read contract explains why this is provider output, not independently validated patch completeness or merge eligibility.

PAT mode uses the normal ephemeral login/read/logout lifecycle; API mode delegates its issuing session. --url and --token-stdin precede commit; credentials are not stored. No issue scope or new browser OAuth grant is required.

Pull-request commands​

These read-only commands require read:user and read:repository; no additional read:issue scope is needed:

./bin/golf pr list owner/repository --state open --page 1 --limit 20
./bin/golf pr view owner/repository 12
./bin/golf pr reviews owner/repository 12 --page 1 --limit 20

pr list accepts open (default), closed or all states and returns data.pullRequests; pr view returns data.pullRequest; pr reviews returns data.reviews. Numbers must be canonical positive decimal integers through 2147483647. Detail accepts no pagination flags; both list commands return one explicit page.

Continue using data.page.nextPage even after an empty review page: native pending-review visibility can be filtered after pagination. Returned base/head refs, retained snapshots and native review flags do not establish source freshness, eligible approval or merge readiness. These commands neither submit reviews nor merge. Each uses the selected credential mode described above.

Organization and team commands​

The six commands use the same membership checks as the browser. Replace acme with an organization name returned by org list, and 123 with a team ID returned by org teams:

./bin/golf org list --page 1 --limit 20
./bin/golf org view acme
./bin/golf org teams acme --page 1 --limit 20
./bin/golf team view acme 123
./bin/golf team members acme 123 --page 1 --limit 20
./bin/golf team repos acme 123 --page 1 --limit 20

org list, org teams, team members and team repos return one page. Their collection fields are data.organizations, data.teams, data.members and data.repositories. Detail commands return data.organization or data.team and do not accept pagination flags.

Team IDs remain exact decimal strings, including IDs beyond JavaScript's safe integer range. Pass the returned ID unchanged; signs, leading zeroes, zero and values beyond 9223372036854775807 are rejected. An ID from a different organization does not authorize access through the selected one.

The returned visibility, permission, unitPermissions and includesAllRepositories fields describe native Forgejo settings. They do not calculate an effective Golf role or enable membership changes. A listed repository still needs its own code authorization before repo contents can read source.

Continue an explicit page​

Repository, issue, PR, organization and team collection commands return one page, not an automatic crawl. Pass a nonzero data.page.nextPage as --page to continue, keeping the same --limit; zero or omission means the end. --page accepts 1–10,000 and --limit accepts 1–100; defaults are 1 and 20.

For example, if the first team-repository response reports data.page.nextPage: 2, continue with:

./bin/golf team repos acme 123 --page 2 --limit 20

An empty data.repositories array can still have a nonzero next page because the provider filters inaccessible assignments after pagination. Follow data.page.nextPage, not the array length. Provider denials return an error rather than a successful empty collection. PAT page invocations start and close their own temporary sessions; API mode delegates its source session. Neither reuses a saved CLI cookie.

Account activity commands​

Read your recorded Golf session events with a token scoped to read:user:

./bin/golf audit list --limit 20
./bin/golf audit list --limit 20 --before-id BEFORE_ID --snapshot-id SNAPSHOT_ID

audit list returns data.events, data.nextBeforeId and data.snapshotId. It accepts no --page; omitted limit defaults to 20 and explicit limits are 1–100. Replace the cursor placeholders with exact returned strings. Keep the first snapshot on every continuation and pass each next cursor as --before-id; an empty next cursor means completion. Refresh by starting again without either cursor.

Cursors are canonical positive signed-int64 strings. A before cursor requires a snapshot, cannot exceed it and must reference an event in the caller's account. Future, missing and foreign cursor IDs are rejected. Do not round IDs through JavaScript numbers or derive a cursor from a timestamp.

Every PAT invocation creates and logs out its own ephemeral session, adding sign-in/sign-out records. Reusing the captured snapshot keeps those later CLI events outside continuation; repeatedly taking a new snapshot does not enumerate one stable history. Normal cleanup leaves no saved session/credential, but abrupt termination remains subject to server expiry. Browser OAuth is separate.

Events include enum action names such as AUDIT_ACTION_SIGN_IN, exact non-credential session handles, times, operation ID and decimal-string revokedCount. Sign-in alone includes optional expiresAt. The activity guide explains coverage: automatic expiry/refresh, failed attempts and provider writes are not recorded; no old login history is invented. This command is a bounded read, not a dedicated retention/export or organization-audit workflow.

Output and exit status​

Success writes a JSON envelope to stdout:

{"schema_version":"golf.cli.v1","data":{"repositories":[],"page":{"nextPage":0}}}

The data value is the corresponding Connect API response serialized as Protobuf JSON. The CLI emits default scalar values and empty arrays for predictable scripting; absent optional fields and absent nested messages remain absent. API JSON received directly may omit more defaults.

Use camelCase fields such as fullName, defaultBranch, commitId, createdAt, updatedAt and htmlUrl. IDs and uint64 byte sizes remain decimal strings, without rounding through JavaScript numbers. States/kinds are schema enum names, such as ISSUE_STATE_OPEN and SOURCE_ENTRY_TYPE_FILE.

Source results include data.commitId; reuse it as --ref when inspecting related files so moving branches cannot mix snapshots. File metadata is nested under data.file.entry, text under data.file.content, and exclusions under data.file.previewReason. Empty text has a present content: ""; a metadata-only preview has no content field. File content stays JSON-escaped. Errors go to stderr without upstream response bodies or credentials.

Exit codeMeaning
0Successful read or help; a creation-record read may describe rejection or uncertainty
1Authentication, operation, connection or session-cleanup failure
2Invalid command, arguments, origin or credential input

golf.cli.v1 identifies the generated-contract envelope in this development build; it does not announce a stable product release. Scripts written for the earlier bootstrap response format must update their fields and pagination. Remote MCP, ACP, write commands and managed credentials remain roadmap work; local stdio MCP reads are available.

Browser OAuth is available for the web UI when configured, but the CLI accepts a delegated Golf API credential or personal-token bootstrap. It does not save provider credentials or reuse browser cookies; managed CLI/device login remains future work.

Issue discussion reads​

./bin/golf issue comments OWNER/REPO 1 --page 1 --limit 20
./bin/golf issue operation UUID

Comments resolve current repository/issue IDs in the same authenticated call, then return one filtered native repository-wide page. Empty pages can advance; follow data.page.nextPage. Own-operation inspection never posts or adopts an object. Provisional receipt IDs remain evidence of an unknown outcome, not success. See issues and discussions. There are no CLI write commands for these operations.