code.oscarkilo.com/okg/klee/klee.go

..
klee.go
klee_test.go
// Package klee provides a Go client for the Klee
// git server API (code.oscarkilo.com).
package klee

import "fmt"
import "strings"
import "time"

import "oscarkilo.com/okg/internal/rest"

// Client talks to a Klee git server. Embeds the shared rest
// helpers (GetJSON, PostJSON, PatchJSON, Do); klee-specific
// methods live below.
type Client struct {
  *rest.Client
}

// NewClient creates a Klee client.
func NewClient(host, api_key string) *Client {
  return &Client{Client: rest.NewClient(host, api_key)}
}

// PR is a pull request on Klee.
type PR struct {
  Number   int       `json:"number"`
  Title    string    `json:"title"`
  Body     string    `json:"body"`
  State    string    `json:"state"`
  Merged   bool      `json:"merged"`
  Head     string    `json:"head"`
  Base     string    `json:"base"`
  Author   string    `json:"author"`
  Files    []string  `json:"files"`
  Created  time.Time `json:"created"`
  Updated  time.Time `json:"updated"`
  MergedBy string    `json:"merged_by,omitempty"`
  MergedAt time.Time `json:"merged_at,omitempty"`
}

// Comment is a PR comment on Klee.
type Comment struct {
  ID      int       `json:"id"`
  Author  string    `json:"author"`
  Body    string    `json:"body"`
  Verdict string    `json:"verdict,omitempty"`
  File    string    `json:"file,omitempty"`
  Line    int       `json:"line,omitempty"`
  Created time.Time `json:"created"`
}

// CreatePRRequest is the body for creating a PR.
type CreatePRRequest struct {
  Head  string `json:"head"`
  Base  string `json:"base"`
  Title string `json:"title"`
  Body  string `json:"body"`
}

// AddCommentRequest is the body for adding a
// comment to a PR.
type AddCommentRequest struct {
  Body    string `json:"body"`
  Verdict string `json:"verdict,omitempty"`
}

// CreateRepoRequest is the body for creating a
// repo on Klee.
type CreateRepoRequest struct {
  RepoName       string `json:"repo_name"`
  ReaderUsername  string `json:"reader_username"`
}

// RepoInfo describes a repository.
type RepoInfo struct {
  Name     string         `json:"name"`
  IsPublic bool           `json:"is_public"`
  Authz    *RepoInfoAuthz `json:"authz"`
}

// RepoInfoAuthz describes repo access.
type RepoInfoAuthz struct {
  IsOwner        bool   `json:"is_owner"`
  IsReader       bool   `json:"is_reader"`
  OwnerUsername  string  `json:"owner_username"`
  ReaderUsername string  `json:"reader_username"`
}

// LsResponse is the response from /.ls.
type LsResponse struct {
  Repos          []RepoInfo `json:"repos"`
  CanCreateRepos bool       `json:"can_create_repos"`
}

// --- PR operations ---

// ListPRs lists PRs for a repo by state.
func (c *Client) ListPRs(
  repo, state string,
) ([]PR, error) {
  path := fmt.Sprintf(
    "/%s/prs?state=%s", repo, state)
  var prs []PR
  if err := c.GetJSON(path, &prs); err != nil {
    return nil, err
  }
  return prs, nil
}

// GetPR fetches a single PR.
func (c *Client) GetPR(
  repo string, number int,
) (*PR, error) {
  path := fmt.Sprintf(
    "/%s/pr/%d", repo, number)
  var pr PR
  if err := c.GetJSON(path, &pr); err != nil {
    return nil, err
  }
  return &pr, nil
}

// CreatePR creates a new PR.
func (c *Client) CreatePR(
  repo string, req CreatePRRequest,
) (*PR, error) {
  path := fmt.Sprintf("/%s/prs", repo)
  var pr PR
  if err := c.PostJSON(path, req, &pr); err != nil {
    return nil, err
  }
  return &pr, nil
}

// GetComments fetches comments on a PR.
func (c *Client) GetComments(
  repo string, number int,
) ([]Comment, error) {
  path := fmt.Sprintf(
    "/%s/pr/%d/comments", repo, number)
  var comments []Comment
  if err := c.GetJSON(path, &comments); err != nil {
    return nil, err
  }
  return comments, nil
}

// AddComment adds a comment to a PR.
func (c *Client) AddComment(
  repo string, number int, req AddCommentRequest,
) (*Comment, error) {
  path := fmt.Sprintf(
    "/%s/pr/%d/comments", repo, number)
  var comment Comment
  if err := c.PostJSON(
    path, req, &comment); err != nil {
    return nil, err
  }
  return &comment, nil
}

// MergePR merges a PR.
func (c *Client) MergePR(
  repo string, number int,
) (*PR, error) {
  path := fmt.Sprintf(
    "/%s/pr/%d/merge", repo, number)
  var pr PR
  if err := c.PostJSON(path, nil, &pr); err != nil {
    return nil, err
  }
  return &pr, nil
}

// SetPRState changes a PR's state (open/closed).
func (c *Client) SetPRState(
  repo string, number int, state string,
) (*PR, error) {
  path := fmt.Sprintf(
    "/%s/pr/%d", repo, number)
  payload := map[string]string{"state": state}
  var pr PR
  if err := c.PatchJSON(
    path, payload, &pr); err != nil {
    return nil, err
  }
  return &pr, nil
}

// --- Repo operations ---

// ListRepos lists repos accessible to the caller.
func (c *Client) ListRepos() (
  *LsResponse, error,
) {
  var res LsResponse
  if err := c.GetJSON("/.ls", &res); err != nil {
    return nil, err
  }
  return &res, nil
}

// CreateRepo creates a new repo on Klee.
func (c *Client) CreateRepo(
  req CreateRepoRequest,
) error {
  return c.PostJSON("/.add-repo", req, nil)
}

// AgentName extracts the agent name from a Klee
// sub-user username. Given "operator.claude",
// returns "claude". If no dot, returns the input.
func AgentName(klee_user string) string {
  i := strings.LastIndex(klee_user, ".")
  if i < 0 {
    return klee_user
  }
  return klee_user[i+1:]
}