cg

package
v0.0.0-...-de4b809 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 4, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Index

Constants

View Source
const CaptureDirName = "cg"

CaptureDirName is the subdirectory under $TMPDIR that holds per-run capture directories.

View Source
const DebugFilename = "debug.json"

DebugFilename is the file name within a run directory that holds start-failure diagnostics.

View Source
const DefaultFormat = "15:04:05 "

DefaultFormat is the default time prefix format: short time.

View Source
const MetaFilename = "meta.json"

MetaFilename is the file name within a run directory that holds the machine-readable run summary.

View Source
const PidFilename = "pid"

PidFilename is the file name within a run directory that holds the child's process-group ID while the run is in flight. The capture paths set Setpgid, so the pgid equals the child's pid. cg_cancel reads this to signal the group; it is removed once the run finishes so a completed run carries no stale pid.

View Source
const StartFilename = "start.json"

StartFilename is the file name within a run directory that records a capture's command and start time while it is in flight. It is written when the child starts and removed once the run finishes and meta.json supersedes it, so its presence, with neither meta.json nor debug.json, marks a run still running.

Variables

View Source
var ErrFailedRun = errors.New("failed run")

ErrFailedRun is returned by LookupRunDir when the run directory has a debug.json but no meta.json, indicating the child process failed to start.

View Source
var ErrIncompleteRun = errors.New("incomplete run")

ErrIncompleteRun is returned by LookupRunDir when the run directory exists but neither meta.json nor debug.json is present, i.e. the capture is still in flight.

View Source
var ErrUnknownRunID = errors.New("unknown run id")

ErrUnknownRunID is returned by LookupRunDir when id is malformed or the run directory does not exist under CaptureRoot.

Functions

func CaptureRoot

func CaptureRoot() string

CaptureRoot returns the parent directory that holds all per-run capture directories: $TMPDIR/cg.

func EscapeArgs

func EscapeArgs(args []string) string

EscapeArgs formats a command and its arguments in a shell-readable style. Arguments containing spaces, quotes, or shell metacharacters are quoted. The result is the canonical quoted-argv join the approval matcher patterns against; it is built only for display and matching and is never executed.

func ExitCodeFromError

func ExitCodeFromError(err error) int

ExitCodeFromError maps an error from exec.Cmd.Wait into a conventional exit code. Returns 127 for command-not-found, 126 for permission denied, and the child's actual exit code for exec.ExitError.

For any other error it returns 1 as a generic failure code.

func IsValidRunID

func IsValidRunID(id string) bool

IsValidRunID reports whether id has the right shape for a capture run ID: the Crockford base-32 alphabet, exactly runIDLen characters.

func LookupRunDir

func LookupRunDir(id string) (string, error)

LookupRunDir resolves the per-run capture directory for id under CaptureRoot. It returns ErrUnknownRunID for malformed IDs and absent directories, and ErrIncompleteRun when the directory exists but meta.json is not present. The returned directory is always the joined path, even on error, so callers can decide whether to surface it (for example, when a caller wants to peek at an in-flight run's stdout).

func NewCommand

func NewCommand() *cobra.Command

NewCommand creates the cg cobra command.

func NewErrCommand

func NewErrCommand() *cobra.Command

NewErrCommand returns the `cg err <ID>` subcommand. It prints the absolute path of the captured stderr file.

func NewLsCommand

func NewLsCommand() *cobra.Command

NewLsCommand returns the `cg ls` subcommand. It lists recent capture runs in most-recent-first order by directory mtime.

func NewOutCommand

func NewOutCommand() *cobra.Command

NewOutCommand returns the `cg out <ID>` subcommand. It prints the absolute path of the captured stdout file.

func NewPathsCommand

func NewPathsCommand() *cobra.Command

NewPathsCommand returns the `cg paths <ID>` subcommand. It prints the absolute paths of the captured stdout and stderr files, one per line, stdout first.

func NewPruneCommand

func NewPruneCommand() *cobra.Command

NewPruneCommand returns the `cg prune` subcommand. It removes capture run directories from $TMPDIR/cg/, keeping recent runs or evicting by age.

func ParsePruneDuration

func ParsePruneDuration(s string) (time.Duration, error)

ParsePruneDuration parses a duration string, accepting the Go time.ParseDuration grammar plus single-unit Nd (days) and Nw (weeks) suffixes. Mixed forms like "7d12h" are not supported; they fall through to time.ParseDuration and error.

func PruneRuns

func PruneRuns(opts PruneOptions) ([]string, error)

PruneRuns evicts capture runs from CaptureRoot() per opts. It returns the IDs that were removed (or, under DryRun, would have been removed) in eviction order. A missing CaptureRoot is not an error.

func ReadPidFile

func ReadPidFile(dir string) (int, error)

ReadPidFile reads and parses the pid recorded in dir/pid. A missing file surfaces as fs.ErrNotExist unwrapped, so callers can branch on it.

func RemovePidFile

func RemovePidFile(dir string)

RemovePidFile deletes dir/pid, ignoring a missing file. It is best-effort: the caller is finishing a run and a leftover pid file is harmless because cg_cancel checks for meta.json first.

func RemoveStartInfo

func RemoveStartInfo(dir string)

RemoveStartInfo deletes dir/start.json, ignoring a missing file. It is best-effort: a finished run is identified by meta.json, so a leftover start.json is harmless.

func WriteMeta

func WriteMeta(dir string, m *Meta) error

WriteMeta serialises m and writes it atomically to dir/meta.json via a temporary file and rename. The temporary file is created in the same directory so the rename stays on one filesystem.

func WritePidFile

func WritePidFile(dir string, pid int) error

WritePidFile records pid in dir/pid as a decimal string. The payload is small enough that a single write suffices; no temp-and-rename is needed.

func WriteStartDebug

func WriteStartDebug(dir string, d *StartDebug) error

WriteStartDebug serialises d and writes it to dir/debug.json.

func WriteStartInfo

func WriteStartInfo(dir string, s *StartInfo) error

WriteStartInfo serialises s and writes it to dir/start.json.

Types

type AnnotatedWriter

type AnnotatedWriter struct {
	// contains filtered or unexported fields
}

AnnotatedWriter writes lines with a prefix and stream indicator to an underlying writer. All writes are mutex-protected to prevent interleaving. When brief is true, the prefix is omitted entirely from output regardless of what the prefix function or per-line prefix overrides return.

func NewAnnotatedWriter

func NewAnnotatedWriter(dest io.Writer, prefix PrefixFunc, brief bool) *AnnotatedWriter

NewAnnotatedWriter creates an AnnotatedWriter that writes to dest, calling prefix before each line to obtain the current prefix string. When brief is true, the prefix is dropped entirely.

func (*AnnotatedWriter) SetProcessor

func (w *AnnotatedWriter) SetProcessor(proc LineProcessor)

SetProcessor sets the line processor for this writer.

func (*AnnotatedWriter) WriteLine

func (w *AnnotatedWriter) WriteLine(ind Indicator, line string) error

WriteLine writes a single annotated line to the destination. The line should not include a trailing newline; one will be appended. The write is atomic with respect to other WriteLine and WriteLines calls.

func (*AnnotatedWriter) WriteLineWithPrefix

func (w *AnnotatedWriter) WriteLineWithPrefix(prefix string, ind Indicator, line string) error

WriteLineWithPrefix writes a single annotated line using the given prefix instead of calling the prefix function. Used for replaying buffered lines with their original receive-time prefix. In brief mode the supplied prefix is ignored.

func (*AnnotatedWriter) WriteLines

func (w *AnnotatedWriter) WriteLines(r io.Reader, ind Indicator) error

WriteLines reads linewise from r, and writes each as an annotated line.

If the final line does not end with a newline, it is written without a trailing newline to preserve the child's exact output.

func (*AnnotatedWriter) WritePartialLine

func (w *AnnotatedWriter) WritePartialLine(ind Indicator, line string) error

WritePartialLine writes a single annotated line without a trailing newline. Used for the final line of output when it does not end with a newline.

func (*AnnotatedWriter) WritePartialLineWithPrefix

func (w *AnnotatedWriter) WritePartialLineWithPrefix(prefix string, ind Indicator, line string) error

WritePartialLineWithPrefix writes a single annotated line without a trailing newline, using the given prefix instead of calling the prefix function. In brief mode the supplied prefix is ignored.

type Capture

type Capture struct {
	ID     string
	Dir    string
	Stdout *os.File
	Stderr *os.File
}

Capture holds the open stdout and stderr files for a single capture run, along with the run's identifier and directory.

func NewCapture

func NewCapture() (*Capture, error)

NewCapture allocates a fresh run ID, creates $TMPDIR/cg/<ID>/, and opens stdout and stderr inside it.

func (*Capture) Close

func (c *Capture) Close() error

Close closes the captured stdout and stderr files. The first error encountered is returned.

type CaptureRun

type CaptureRun struct {
	ID   string
	Dir  string
	Done <-chan struct{}
}

CaptureRun is an in-flight or completed capture. The on-disk layout matches the shell --capture path: $TMPDIR/cg/<ID>/{stdout,stderr,meta.json}. Done closes when the child exits and meta.json has been written.

func RunCapture

func RunCapture(args []string, resolved *Resolution, cwd string, env map[string]string) (*CaptureRun, error)

RunCapture starts args[0] with args[1:] under capture. stdout and stderr are written to $TMPDIR/cg/<ID>/{stdout,stderr}. cwd is passed through; empty inherits the caller's working directory. env entries are appended to os.Environ, so MCP-supplied keys override the parent's.

resolved is the executable identity computed for args; when nil, RunCapture resolves it itself. The child execs resolved.ExecPath, the canonical path, while keeping args[0] as the child's argv[0], so a fresh PATH lookup at exec time cannot select a different file than the one the approval gate matched. An unresolved command falls back to args[0] so exec still surfaces the start failure.

The child runs in its own process group, so cancelling a caller's context does not kill it. A background goroutine waits for the child, writes meta.json, and closes Done.

type ExitError

type ExitError struct {
	Code int
}

ExitError represents a non-zero exit from a child process. It satisfies the error interface and carries the exit code for use by the entry point.

func (*ExitError) Error

func (e *ExitError) Error() string

type Indicator

type Indicator byte

Indicator identifies the source of an annotated output line.

const (
	// IndicatorInfo marks informational messages from cg itself.
	IndicatorInfo Indicator = 'I'
	// IndicatorOut marks lines from the child's stdout.
	IndicatorOut Indicator = 'O'
	// IndicatorErr marks lines from the child's stderr.
	IndicatorErr Indicator = 'E'
)

type JSONProcessorOptions

type JSONProcessorOptions struct {
	MessageKey   string
	TimestampKey string
	TimestampFmt string
	Fields       []string
	Format       string
}

JSONProcessorOptions configures the JSON log line processor.

type LineBuffer

type LineBuffer struct {
	// contains filtered or unexported fields
}

LineBuffer accumulates child output lines in memory, grouped by indicator. Lines are stored with the prefix captured at receive time so they can be replayed later with their original timestamps. When brief is true, the per-line prefix is dropped at replay time.

func NewLineBuffer

func NewLineBuffer(prefix PrefixFunc, brief bool) *LineBuffer

NewLineBuffer creates a LineBuffer that calls prefix to obtain the prefix string for each line as it arrives. When brief is true, Flush replays lines without their stored prefix.

func (*LineBuffer) Flush

func (b *LineBuffer) Flush(w *AnnotatedWriter) error

Flush writes all buffered lines to w, grouped by stream. Stdout lines are written first, then stderr. Each non-empty stream is preceded by a section header. Lines are replayed with their original receive-time prefix.

func (*LineBuffer) SetProcessor

func (b *LineBuffer) SetProcessor(proc LineProcessor)

SetProcessor sets the line processor for this buffer.

func (*LineBuffer) WriteLines

func (b *LineBuffer) WriteLines(r io.Reader, ind Indicator) error

WriteLines reads line-by-line from r, capturing each line with its receive-time prefix under the given indicator. Partial final lines (no trailing newline) are recorded with partial set to true.

type LineProcessor

type LineProcessor func(line string) *ProcessedLine

LineProcessor transforms raw output lines for display. Returns nil to pass the line through unchanged.

func NewJSONProcessor

func NewJSONProcessor(opts JSONProcessorOptions) LineProcessor

NewJSONProcessor returns a LineProcessor that extracts the message from JSON log lines. Non-JSON lines and lines missing the message key pass through unchanged.

func NewLogfmtProcessor

func NewLogfmtProcessor(opts LogfmtProcessorOptions) LineProcessor

NewLogfmtProcessor returns a LineProcessor that extracts the message from logfmt log lines. Non-logfmt lines and lines missing the message key pass through unchanged.

type LogfmtProcessorOptions

type LogfmtProcessorOptions struct {
	MessageKey   string
	TimestampKey string
	TimestampFmt string
	Fields       []string
	Format       string
}

LogfmtProcessorOptions configures the logfmt log line processor.

type Meta

type Meta struct {
	ID          string    `json:"id"`
	Command     []string  `json:"command"`
	StartedAt   time.Time `json:"started_at"`
	FinishedAt  time.Time `json:"finished_at"`
	DurationMs  int64     `json:"duration_ms"`
	ExitCode    int       `json:"exit_code"`
	Signal      *int      `json:"signal"`
	StdoutLines int64     `json:"stdout_lines"`
	StderrLines int64     `json:"stderr_lines"`
}

Meta is the per-run metadata persisted alongside the captured stdout and stderr.

func ReadMeta

func ReadMeta(dir string) (*Meta, error)

ReadMeta loads meta.json from dir.

type Options

type Options struct {
	Format   string
	Verbose  bool
	Capture  bool
	Buffered bool

	LogParse  string
	LogMsgKey string
	LogTSKey  string
	LogTSFmt  string
	LogFields string
}

type PrefixFunc

type PrefixFunc func() string

PrefixFunc returns the prefix string to prepend to each annotated line

type ProcessedLine

type ProcessedLine struct {
	// Prefix is an optional string to display before the message, e.g., timestamp
	Prefix string
	// Line is the main content to display
	Line string
}

ProcessedLine is the result of processing a raw output line for display.

type PruneOptions

type PruneOptions struct {
	// Keep is the number of most-recent (by mtime) runs to retain. Ignored
	// when UseOlderThan is true.
	Keep int
	// OlderThan, when UseOlderThan is true, evicts runs whose mtime is
	// before now-OlderThan.
	OlderThan time.Duration
	// UseOlderThan selects age-based eviction over count-based.
	UseOlderThan bool
	// DryRun returns the list of IDs that would be removed without
	// touching the filesystem.
	DryRun bool
}

PruneOptions controls which capture runs PruneRuns considers for eviction and whether the removal is actually performed.

type Resolution

type Resolution struct {
	// Argv is the original command, unchanged.
	Argv []string
	// Resolved is the absolute path after PATH lookup and cwd joining, before
	// symlink evaluation. Empty when argv[0] could not be located.
	Resolved string
	// Canonical is EvalSymlinks(Resolved). Empty when symlink evaluation fails;
	// the caller decides how to treat an uncanonicalizable command.
	Canonical string
}

Resolution is the executable identity cg derives from a command's argv[0] before it is approved and execed. The approval gate matches against the canonical form and RunCapture execs the same path, so the policy decision and the execution agree on which file runs.

func ResolveCommand

func ResolveCommand(argv []string, cwd string) (*Resolution, error)

ResolveCommand computes the resolved and canonical executable path for argv.

A bare program name is looked up against the server process PATH via exec.LookPath, before any caller-supplied env override is applied, so env cannot redirect the top-level executable. A slash-bearing absolute path is used as-is; a relative one is joined onto cwd when set and the server current directory otherwise. The resolved path is then canonicalized with EvalSymlinks.

It returns a best-effort Resolution populated as far as resolution got, alongside an error, so a caller can still build start-failure diagnostics when resolution or canonicalization fails.

func (*Resolution) CanonicalArgv

func (r *Resolution) CanonicalArgv() []string

CanonicalArgv is the argv the approval matcher evaluates against canonical rules: the symlink-evaluated executable path followed by the original argument tail. It is nil when canonicalization did not succeed, so a command with an unknown executable identity cannot be allowed by canonical policy.

func (*Resolution) ExecPath

func (r *Resolution) ExecPath() string

ExecPath is the path RunCapture execs: the canonical path when available, then the resolved path, falling back to the original argv[0] so a command that could not be resolved still surfaces its start failure through exec.

type StartDebug

type StartDebug struct {
	Command       []string `json:"command"`
	ResolvedPath  string   `json:"resolved_path,omitempty"`
	CanonicalPath string   `json:"canonical_path,omitempty"`
	Cwd           string   `json:"cwd,omitempty"`
	Path          string   `json:"path,omitempty"`
	StartError    string   `json:"start_error"`
}

StartDebug holds diagnostic information written when a child process fails to start. It is persisted to debug.json alongside stdout and stderr.

func ReadStartDebug

func ReadStartDebug(dir string) (*StartDebug, error)

ReadStartDebug loads debug.json from dir.

type StartFailure

type StartFailure struct {
	RunID string
	Dir   string
	Err   error
}

StartFailure is returned by RunCapture when the child process cannot be started. The run directory is preserved on disk with a debug.json for post-mortem inspection via cg_meta and the other cg tools.

func (*StartFailure) Error

func (e *StartFailure) Error() string

func (*StartFailure) Unwrap

func (e *StartFailure) Unwrap() error

type StartInfo

type StartInfo struct {
	Command   []string  `json:"command"`
	StartedAt time.Time `json:"started_at"`
}

StartInfo is the per-run record written when a capture begins, before the child has finished. It lets `cg ls` and cg_list surface the command and a precise elapsed time for a run that is still going.

func ReadStartInfo

func ReadStartInfo(dir string) (*StartInfo, error)

ReadStartInfo loads start.json from dir.

type TimestampFormat

type TimestampFormat int

TimestampFormat identifies a timestamp encoding.

type TimestampParser

type TimestampParser struct {
	// contains filtered or unexported fields
}

TimestampParser auto-detects and parses timestamps from JSON values. It locks on the first successfully detected format, avoiding repeated probing.

func NewTimestampParser

func NewTimestampParser(explicitFmt string, layout string) *TimestampParser

NewTimestampParser creates a parser. If explicitFmt is non-empty, it locks to that format immediately. layout is the Go time.Format layout used for rendering the prefix string.

func (*TimestampParser) Parse

func (tp *TimestampParser) Parse(val any) string

Parse attempts to extract a time from val and returns a formatted prefix string. Returns empty string on failure.

Directories

Path Synopsis
Package approve implements the cg_run approval gate: a layered YAML rules file, a load-once config store, and an argv-native matcher that decides whether a command runs, is refused, or needs an interactive prompt.
Package approve implements the cg_run approval gate: a layered YAML rules file, a load-once config store, and an argv-native matcher that decides whether a command runs, is refused, or needs an interactive prompt.
Package mcp implements `cg mcp`, a stdio MCP server that exposes cg's capture-run model as native MCP tools.
Package mcp implements `cg mcp`, a stdio MCP server that exposes cg's capture-run model as native MCP tools.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL