Documentation
¶
Overview ¶
Package core — atomic.go: shared file-mutation primitives for Edit and Write. These helpers guarantee no partial-write artifacts on crash and give us a single place to add safety polish (line-ending preservation, BOM handling) consistently across both tools.
Per ADR-007 we do not use a third-party "atomic write" library — Go's stdlib (`os.WriteFile` + `os.Rename`) is enough when used correctly. The platform invariant we rely on: rename(2) on the same filesystem is atomic, so writers never see a half-written file at the target path.
Package core implements clawtool's canonical core tools per ADR-005.
Quality bar reminder: each core tool must measurably beat the corresponding native built-in across the major agents. For Bash:
- timeout-safe: output is captured even when the process is killed
- predictable cwd: defaults to $HOME, never the daemon's cwd
- structured result: stdout/stderr/exit_code/duration_ms/timed_out returned as JSON even on timeout
v0.1 implements timeout-safe + structured result + cwd. Secret redaction and per-session command history land in v0.2.
Package core — Edit performs a precise search-and-replace on an existing file with safety polish:
- uniqueness check by default (refuses ambiguous edits)
- atomic temp+rename so a crash never leaves a half-written file
- line-ending preserve (LF / CRLF / CR detected from current content)
- BOM preserve
- binary refusal (symmetric with Read)
Per ADR-007 we wrap stdlib `os` for I/O and add our own polish layer. The search-and-replace shape mirrors what Claude Code's native Edit uses today (old_string / new_string / replace_all) — agents that learnt that interface get the same affordances against clawtool's stronger invariants.
Package core — Glob is the canonical wrapper around bmatcuk/doublestar.
Per ADR-007 we don't write a glob engine. doublestar is the de-facto double-star (`**`) glob library in Go, used by GoReleaser, k6, etc. This file's value is the polish layer: cwd-aware path resolution, uniform structured output, hard cap to protect agent context, and platform-stable separators (the wrapper always returns forward-slash paths regardless of OS — agents expect that).
Package core — Grep wraps ripgrep when present, falls back to system grep.
Per ADR-007 we curate ripgrep as the default engine: it has the correct .gitignore semantics, --type aliases, fast Aho-Corasick matching, and a stable JSON output that is straightforward to parse. The fallback is system grep (-rn) so clawtool's Grep is never unavailable.
Output is uniform across engines so the agent never has to know which one ran. The `engine` field in the result lets users / tests verify.
Package core — Read returns file content with stable line cursors, deterministic line counts, and per-format dispatch.
Per ADR-007 we wrap mature engines instead of writing parsers:
- text → stdlib bufio (line-walked, single-pass)
- pdf → pdftotext (poppler-utils) shell-out
- ipynb → native JSON cell parse
- docx → pandoc shell-out (universal office converter)
- xlsx → github.com/xuri/excelize/v2 (Microsoft/Alibaba/Oracle in prod)
- csv / tsv → stdlib encoding/csv (header + bounded preview)
- html → github.com/go-shiori/go-readability (Mozilla Readability port)
- json / yaml / toml / xml → text passthrough with format tag
- binary → refused with structured error
Adding a new format means: extend detectFormat, add a reader function, update CoreToolDocs's description, ship tests for the format. The readResult shape stays uniform so the agent never has to branch on which engine ran.
Package core — Recipe* MCP tools mirror the `clawtool recipe …` CLI surface so a model can run setup tasks from inside a conversation. Same registry, same Detect/Apply/Verify cycle.
Three tools per ADR-013:
RecipeList — enumerate recipes, optionally filter by category.
RecipeStatus — Detect output for one recipe or all of them.
RecipeApply — full Detect→Prereqs→Apply→Verify against a repo,
with structured options.
All three return BaseResult-shaped output (pretty text + structured JSON) so chat UIs render them the same way as Bash/Read/etc.
Package core — SkillNew MCP tool. Mirrors `clawtool skill new` so a model can scaffold a Claude Code skill from inside a conversation, without the user dropping to a shell. Both surfaces share the same template renderer (passed in at registration so we don't reach across packages — this file stays a leaf).
Spec compliance: agentskills.io. SKILL.md gets YAML frontmatter with `name` + `description` (required) plus optional `triggers`, followed by the body template. Same shape as the CLI emits.
Package core — ToolSearch is the discovery primitive that makes a 50+ tool catalog usable per ADR-005. The agent calls ToolSearch with a natural-language query, gets ranked candidates, then binds to the right tool with its full schema via the regular tools/list output.
Package core — WebFetch retrieves a URL and renders the body in the best shape for an AI agent: clean article text for HTML, raw text for plain-text MIME types, structured rejection for binaries.
Per ADR-007 we wrap two mature engines:
- net/http stdlib client for transport (proxy, TLS, redirect handling are all stock — battle-tested across Go's user base);
- github.com/go-shiori/go-readability for HTML extraction (Mozilla Readability port, the same algorithm Firefox Reader View ships).
What clawtool adds: agent-friendly polish — a hard body cap so a runaway page can't blow context, structured result with content-type-aware `format`, citation metadata (final URL after redirects, fetched_at).
Package core — WebSearch is a pluggable web-search primitive. clawtool itself does no crawling or ranking; it adapts whichever search backend the user has configured (Brave today; Tavily / SearXNG planned) into a uniform `{results, backend, …}` shape.
Per ADR-007 we wrap, never reinvent. The backend interface is small on purpose so adding a new provider is one file (see websearch_brave.go for the canonical example).
Package core — Write creates or overwrites a whole file with given content. Sister tool to Edit; the boundary is intentional:
- Edit modifies a substring of an existing file. Refuses if the file is missing.
- Write replaces or creates the whole file. If the file already exists and has detectable line endings, those endings are preserved by default so existing tooling (CR LF on Windows, etc.) keeps working.
Per ADR-007 we wrap stdlib `os` for I/O. The polish layer is the same atomic-write primitive Edit uses, plus a parent-directory auto-create so agents don't need a separate `mkdir` step before writing.
Index ¶
- Variables
- func CoreToolDocs() []search.Doc
- func RegisterBash(s *server.MCPServer)
- func RegisterEdit(s *server.MCPServer)
- func RegisterGlob(s *server.MCPServer)
- func RegisterGrep(s *server.MCPServer)
- func RegisterRead(s *server.MCPServer)
- func RegisterRecipeTools(s *server.MCPServer)
- func RegisterSkillNew(s *server.MCPServer)
- func RegisterToolSearch(s *server.MCPServer, idx *search.Index)
- func RegisterWebFetch(s *server.MCPServer)
- func RegisterWebSearch(s *server.MCPServer, store *secrets.Store)
- func RegisterWrite(s *server.MCPServer)
- func ResetEngineCache()
- type Backend
- type BaseResult
- type EditResult
- type Engine
- type GlobResult
- type GrepMatch
- type GrepResult
- type LineEndings
- type ReadResult
- type Renderer
- type ToolSearchResult
- type WebFetchResult
- type WebSearchHit
- type WebSearchResult
- type WriteResult
Constants ¶
This section is empty.
Variables ¶
var ErrMissingAPIKey = errors.New("missing API key")
ErrMissingAPIKey is returned by backends when their required API key is not present in either the secrets store or process env.
Functions ¶
func CoreToolDocs ¶
CoreToolDocs returns search.Doc descriptors for every clawtool core tool. Centralised so the index-builder in server/server.go stays a one-liner and there's a single source of truth for what each core tool's description says — same string the user sees in tools/list.
func RegisterBash ¶
RegisterBash adds the Bash tool to the given MCP server.
func RegisterEdit ¶
RegisterEdit adds the Edit tool to the given MCP server.
func RegisterGlob ¶
RegisterGlob adds the Glob tool to the given MCP server.
func RegisterGrep ¶
RegisterGrep adds the Grep tool to the given MCP server.
func RegisterRead ¶
RegisterRead adds the Read tool to the given MCP server.
func RegisterRecipeTools ¶ added in v0.9.0
RegisterRecipeTools adds the three Recipe* MCP tools to s. The registry must already be populated (typically via blank import of internal/setup/recipes).
func RegisterSkillNew ¶ added in v0.9.0
RegisterSkillNew adds the SkillNew tool to s. Template + helpers come from internal/skillgen so this MCP surface and the `clawtool skill new` CLI emit byte-identical output.
func RegisterToolSearch ¶
RegisterToolSearch adds the ToolSearch tool to the given MCP server, closing over the index built at startup.
func RegisterWebFetch ¶
RegisterWebFetch adds the WebFetch tool to the given MCP server.
func RegisterWebSearch ¶
RegisterWebSearch adds the WebSearch tool to the given MCP server. The secrets-store reference is captured so per-call backend resolution can pick up updated API keys without restart.
func RegisterWrite ¶
RegisterWrite adds the Write tool to the given MCP server.
func ResetEngineCache ¶
func ResetEngineCache()
ResetEngineCache forces a re-detection on next LookupEngine call. Used by tests that manipulate $PATH.
Types ¶
type Backend ¶
type Backend interface {
Name() string
Search(ctx context.Context, query string, limit int) ([]WebSearchHit, error)
}
Backend abstracts a web-search provider. Implementations must be safe to invoke from multiple goroutines and complete within the supplied context's deadline.
type BaseResult ¶ added in v0.9.0
type BaseResult struct {
Operation string `json:"-"`
Engine string `json:"engine,omitempty"`
DurationMs int64 `json:"duration_ms,omitempty"`
ErrorReason string `json:"error_reason,omitempty"`
}
BaseResult holds the fields and rendering helpers every tool result shares. Each tool's *Result struct embeds this so the JSON shape stays uniform (timing, error, engine surfaced the same way everywhere) and Render() implementations stay short — each one is just "header + body + footer" composed from BaseResult helpers and the tool-specific data.
Operation is JSON-omitted; it's a presentation concern (the header verb), not a wire field.
func (BaseResult) ErrorLine ¶ added in v0.9.0
func (b BaseResult) ErrorLine(target string) string
ErrorLine renders the canonical failure one-liner. Every tool that fails uses this — keeps "✗ <verb> — <reason>" consistent across the whole catalog.
func (BaseResult) FooterLine ¶ added in v0.9.0
func (b BaseResult) FooterLine(extras ...string) string
FooterLine joins extras with " · " and appends the duration. Used at the bottom of multi-line results (after content).
func (BaseResult) HeaderLine ¶ added in v0.9.0
func (b BaseResult) HeaderLine(title string) string
HeaderLine renders the canonical multi-line header used by tools that return content (Bash, Read, Grep). Engine — when set — is shown in brackets so the caller always knows which backend ran.
func (BaseResult) IsError ¶ added in v0.9.0
func (b BaseResult) IsError() bool
IsError is the universal failure predicate.
func (BaseResult) SuccessLine ¶ added in v0.9.0
func (b BaseResult) SuccessLine(target string, extras ...string) string
SuccessLine is the canonical single-line success format used by stateless tools (Edit, Write). Variadic extras are joined with " · " and the duration is appended automatically.
type EditResult ¶
type EditResult struct {
BaseResult
Path string `json:"path"`
Replaced bool `json:"replaced"`
OccurrencesReplaced int `json:"occurrences_replaced"`
SizeBytesBefore int64 `json:"size_bytes_before"`
SizeBytesAfter int64 `json:"size_bytes_after"`
LineEndings string `json:"line_endings"`
}
EditResult is the uniform shape returned to the agent.
func (EditResult) Render ¶ added in v0.9.0
func (r EditResult) Render() string
Render satisfies the Renderer contract. Single-line success/failure; stateless tools don't need a multi-line body.
type Engine ¶
type Engine struct {
Name string // canonical name, e.g. "ripgrep"
Bin string // resolved absolute path, empty if absent
}
Engine describes one detected upstream binary that clawtool wraps.
Per ADR-007, clawtool curates and wraps best-in-class engines. Detection runs once on startup so wrapper tools can pick the best available engine without re-shelling-out for every call.
func LookupEngine ¶
LookupEngine returns the cached engine entry for a given binary name. The Bin field is empty if the engine is not present on this system.
type GlobResult ¶
type GlobResult struct {
BaseResult
Matches []string `json:"matches"`
MatchesCount int `json:"matches_count"`
Truncated bool `json:"truncated"`
Cwd string `json:"cwd"`
Pattern string `json:"pattern"`
}
GlobResult is the uniform shape returned to the agent.
func (GlobResult) Render ¶ added in v0.9.0
func (r GlobResult) Render() string
Render satisfies the Renderer contract. One match per line so the chat looks like running `find` or `fd` in a terminal.
type GrepMatch ¶
type GrepMatch struct {
Path string `json:"path"`
Line int `json:"line"`
Column int `json:"column"`
Text string `json:"text"`
}
GrepMatch is a single hit. Line and column are 1-indexed for human readability and to match conventional editor jumping.
type GrepResult ¶
type GrepResult struct {
BaseResult
Matches []GrepMatch `json:"matches"`
MatchesCount int `json:"matches_count"`
Truncated bool `json:"truncated"`
Cwd string `json:"cwd"`
Pattern string `json:"pattern"`
}
GrepResult is the uniform shape returned regardless of engine.
func (GrepResult) Render ¶ added in v0.9.0
func (r GrepResult) Render() string
Render satisfies the Renderer contract. Output mirrors ripgrep's standard `path:line:col: text` so a developer reading the chat sees the same shape they'd see in a terminal.
type LineEndings ¶
type LineEndings string
LineEndings identifies the dominant line-ending convention of a file.
const ( LineEndingsLF LineEndings = "lf" LineEndingsCRLF LineEndings = "crlf" LineEndingsCR LineEndings = "cr" // ancient Mac LineEndingsUnknown LineEndings = "unknown" )
type ReadResult ¶
type ReadResult struct {
BaseResult
Path string `json:"path"`
Content string `json:"content"`
LineStart int `json:"line_start"`
LineEnd int `json:"line_end"`
TotalLines int `json:"total_lines"`
SizeBytes int64 `json:"size_bytes"`
Format string `json:"format"`
Truncated bool `json:"truncated"`
// Sheets is populated only for spreadsheet formats; lets the agent
// page through workbook structure without re-reading the file.
Sheets []string `json:"sheets,omitempty"`
}
ReadResult is the uniform shape across all formats. Embeds BaseResult so common fields (engine, duration, error) and their rendering helpers are inherited.
func (ReadResult) Render ¶ added in v0.9.0
func (r ReadResult) Render() string
Render satisfies the Renderer contract. The body is the file content framed by horizontal rules; header carries path and engine, footer carries cursor + size.
type Renderer ¶ added in v0.9.0
type Renderer interface {
Render() string
}
Renderer is the contract every tool result implements. The MCP dispatch helper below relies on it exclusively; tools that don't implement it would fall through to JSON marshaling, but every core tool overrides it.
type ToolSearchResult ¶
type ToolSearchResult struct {
BaseResult
Query string `json:"query"`
Results []search.Hit `json:"results"`
TotalIndexed int `json:"total_indexed"`
TypeFilter string `json:"type_filter,omitempty"`
}
ToolSearchResult is the JSON envelope returned to the agent.
func (ToolSearchResult) Render ¶ added in v0.9.0
func (r ToolSearchResult) Render() string
Render satisfies the Renderer contract. Each hit is rendered as "[score] name (type) — description" so the agent and the user see a plausible top-N list rather than raw JSON.
type WebFetchResult ¶
type WebFetchResult struct {
BaseResult
URL string `json:"url"`
FinalURL string `json:"final_url"`
Status int `json:"status"`
ContentType string `json:"content_type"`
Format string `json:"format"` // "html" | "text" | "binary-rejected"
Title string `json:"title,omitempty"`
Byline string `json:"byline,omitempty"`
SiteName string `json:"site_name,omitempty"`
Content string `json:"content"`
SizeBytes int `json:"size_bytes"`
FetchedAt string `json:"fetched_at"`
Truncated bool `json:"truncated"`
}
WebFetchResult is the uniform shape returned to the agent.
func (WebFetchResult) Render ¶ added in v0.9.0
func (r WebFetchResult) Render() string
Render satisfies the Renderer contract. Header carries the URL + status + format; body is the extracted article (or raw text); footer notes truncation when applicable.
type WebSearchHit ¶
type WebSearchHit struct {
Title string `json:"title"`
URL string `json:"url"`
Snippet string `json:"snippet,omitempty"`
}
WebSearchHit is one ranked result from any backend.
type WebSearchResult ¶
type WebSearchResult struct {
BaseResult
Query string `json:"query"`
Results []WebSearchHit `json:"results"`
ResultsCount int `json:"results_count"`
Truncated bool `json:"truncated"`
}
WebSearchResult is the uniform result envelope the agent receives. Backend lives in BaseResult.Engine because the engine concept is the same — which backend ran this query — and consolidating in the embedded struct keeps every tool's "who did the work" field in one place across the catalog.
func (WebSearchResult) Render ¶ added in v0.9.0
func (r WebSearchResult) Render() string
Render satisfies the Renderer contract. Header carries query + backend; body lists `[N] title — url` rows so a developer scans results the same way they would in a browser results page.
type WriteResult ¶
type WriteResult struct {
BaseResult
Path string `json:"path"`
BytesWritten int64 `json:"bytes_written"`
Created bool `json:"created"`
LineEndings string `json:"line_endings"`
}
WriteResult is the uniform shape returned to the agent.
func (WriteResult) Render ¶ added in v0.9.0
func (r WriteResult) Render() string
Render satisfies the Renderer contract. The Operation field switches between "Write" and "Create" based on whether the file existed beforehand — agents glance-read whether something was clobbered.