Documentation
¶
Overview ¶
Package runner is the entire ccgate PermissionRequest hook orchestration. There is no per-target adapter layer: stdin/stdout shapes are shared across Claude Code and Codex CLI (both deliver session_id / transcript_path / cwd / hook_event_name / tool_name / tool_input on stdin and the same hookSpecificOutput.decision shape on stdout). Per-target differences are handled here directly:
- Claude-only fields: permission_mode, permission_suggestions
- Codex-only fields: model, turn_id
cmd/<target>/ packages stay tiny -- they only hand a config.LoadOptions (where to read the per-user config / write the per-target log+metrics) and call Run.
Index ¶
- Constants
- func Run(stdin io.Reader, stdout io.Writer, opts config.LoadOptions, runOpts ...Option) int
- type Context
- type HookContentUpdate
- type HookInput
- type HookOutput
- type HookToolInput
- type Option
- func WithCacheTarget(name string) Option
- func WithHasRecentTranscript(has bool) Option
- func WithPromptSection(fn func(cfg config.Config) string) Option
- func WithProviderFactory(...) Option
- func WithRecentTranscript(fn func(transcriptPath string) any) Option
- func WithStaticPermissions(fn func(cwd string) any) Option
- func WithTargetName(name string) Option
- type PromptInput
Constants ¶
const PermissionModePlan = "plan"
PermissionModePlan is the Claude Code permission_mode value that puts ccgate into plan-mode evaluation.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Context ¶
type Context struct {
gitutil.Context
ReferencedPaths []string `json:"referenced_paths,omitempty"`
}
Context bundles the working-directory + git information ccgate surfaces to the LLM together with the tool-derived referenced_paths. Wraps gitutil.Context with the path-extraction output so both live in one nested object the LLM can navigate by name.
type HookContentUpdate ¶
type HookContentUpdate struct {
OldString string `json:"old_str"`
NewString string `json:"new_str"`
}
HookContentUpdate is the per-hunk Edit / apply_patch payload.
type HookInput ¶
type HookInput struct {
SessionID string `json:"session_id"`
TranscriptPath string `json:"transcript_path"`
Cwd string `json:"cwd"`
HookEventName string `json:"hook_event_name"`
ToolName string `json:"tool_name"`
ToolInput HookToolInput `json:"-"`
ToolInputRaw json.RawMessage `json:"-"`
// Claude-only
PermissionMode string `json:"permission_mode,omitempty"`
PermissionSuggestions []json.RawMessage `json:"permission_suggestions,omitempty"`
// Codex-only
Model string `json:"model,omitempty"`
TurnID string `json:"turn_id,omitempty"`
}
HookInput is the on-the-wire JSON Claude Code and Codex CLI both deliver to the PermissionRequest hook. Fields that only one of the two surfaces stay omitempty so the user payload sent to the LLM only contains what actually arrived.
func (*HookInput) UnmarshalJSON ¶
UnmarshalJSON keeps the raw tool_input bytes around so the LLM sees every field the upstream hook delivered (including tool-specific shapes ccgate doesn't yet model) while also surfacing the parsed fields metrics relies on.
type HookOutput ¶
type HookOutput struct {
HookSpecificOutput hookSpecificOutput `json:"hookSpecificOutput"`
}
HookOutput is the JSON response shape Claude Code and Codex both expect.
type HookToolInput ¶
type HookToolInput struct {
Command string `json:"command"`
Description string `json:"description"`
FilePath string `json:"file_path"`
Path string `json:"path"`
Pattern string `json:"pattern"`
Content string `json:"content"`
ContentUpdates []HookContentUpdate `json:"content_updates"`
}
HookToolInput is the canonical parsed view of tool_input shared by all targets. Fields are emitted unconditionally (no omitempty) so the LLM sees the full schema and can address fields by name even when the upstream tool left them empty -- the JSON shape itself documents what ccgate models. ToolInputRaw carries the verbatim upstream bytes alongside this view for tool shapes ccgate has not canonicalized yet.
type Option ¶
type Option func(*runtimeOptions)
Option customises the runner with target-specific extra inputs the LLM payload + system prompt should carry. Pass them via Run's variadic.
func WithCacheTarget ¶ added in v0.8.0
WithCacheTarget sets the per-target subdirectory name used for the keystore cache layout ("claude" / "codex" / ...). It must match the subdir cmd/<target>/ already uses for log_path / metrics_path so a user looking at one place sees them all together. An empty string is fed straight to keystore.Options.TargetName, which means the cache file lands one level up under $XDG_CACHE_HOME/ccgate/ and is shared across targets — fine for Run callers that never configure provider.auth (env-var path only), since nothing is written there.
func WithHasRecentTranscript ¶
WithHasRecentTranscript declares that the user payload carries a `recent_transcript` field. The decision-rule wording adjusts so the LLM is not told to consult a field that isn't there. Targets that have no equivalent (Codex today) leave this false.
func WithPromptSection ¶
WithPromptSection injects target-specific guidance about which payload fields the target delivers and how the LLM should interpret them. Inserted between the decision rules and the allow/deny lists. The callback receives the loaded Config so the section can drop or alter paragraphs whose underlying payload field is suppressed (e.g. settings_permissions when include_settings_permissions_in_prompt is false). Targets that have nothing target-specific to say (Codex today) simply do not pass this option.
func WithProviderFactory ¶ added in v0.8.0
func WithProviderFactory(fn func(providerName string, p config.ProviderConfig, apiKey, baseURL string) llm.Provider) Option
WithProviderFactory replaces the LLM-client constructor used by decide(). Test-only: production callers leave it unset and the runner uses newProviderClient. The signature mirrors newProviderClient so a fake can implement llm.Provider with no awareness of the underlying SDK shape; the ProviderConfig argument lets fakes observe auth.type=profile + the profile name without re-parsing the merged config.
func WithRecentTranscript ¶
WithRecentTranscript injects a target-specific transcript reader. Returning nil omits the payload entry. Targets whose transcript format ccgate does not yet model (Codex today) do not pass this option.
func WithStaticPermissions ¶
WithStaticPermissions injects a target-specific reader for the host tool's static allow/deny patterns (e.g. Claude Code's `~/.claude/settings.json` permissions). Returning nil omits the payload entry. Targets that have no equivalent (Codex today -- its `~/.codex/config.toml` prefix_rules ingestion is a separate piece of work) simply do not pass this option.
func WithTargetName ¶
WithTargetName labels the host tool in the system prompt header (e.g. "Claude Code", "Codex CLI"). The default header text falls back to a generic phrasing if unset.
type PromptInput ¶
type PromptInput struct {
ToolName string `json:"tool_name"`
ToolInput HookToolInput `json:"tool_input"`
ToolInputRaw json.RawMessage `json:"tool_input_raw,omitempty"`
PermissionMode string `json:"permission_mode,omitempty"`
PermissionSuggestions []json.RawMessage `json:"permission_suggestions,omitempty"`
Context Context `json:"context"`
SettingsPermissions any `json:"settings_permissions,omitempty"`
RecentTranscript any `json:"recent_transcript,omitempty"`
Model string `json:"model,omitempty"`
TurnID string `json:"turn_id,omitempty"`
}
PromptInput is the user-message JSON the LLM sees. It is the shared payload shape across targets: fields that one of the two targets does not deliver carry omitempty so they disappear from the JSON instead of showing up as empty values. The prompt's TargetSection (set via WithPromptSection) tells the LLM which fields are / are not present.