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 — 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 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 EditResult
- type Engine
- type GlobResult
- type GrepMatch
- type GrepResult
- type LineEndings
- type ReadResult
- 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 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 EditResult ¶
type EditResult struct {
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"`
DurationMs int64 `json:"duration_ms"`
ErrorReason string `json:"error_reason,omitempty"`
}
EditResult is the uniform shape returned to the agent.
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 {
Matches []string `json:"matches"`
MatchesCount int `json:"matches_count"`
Truncated bool `json:"truncated"`
Engine string `json:"engine"`
DurationMs int64 `json:"duration_ms"`
Cwd string `json:"cwd"`
Pattern string `json:"pattern"`
}
GlobResult is the uniform shape returned to the agent.
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 {
Matches []GrepMatch `json:"matches"`
MatchesCount int `json:"matches_count"`
Truncated bool `json:"truncated"`
Engine string `json:"engine"`
DurationMs int64 `json:"duration_ms"`
Cwd string `json:"cwd"`
Pattern string `json:"pattern"`
}
GrepResult is the uniform shape returned regardless of engine.
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 {
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"`
Engine string `json:"engine"`
Truncated bool `json:"truncated"`
DurationMs int64 `json:"duration_ms"`
ErrorReason string `json:"error_reason,omitempty"`
// 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.
type ToolSearchResult ¶
type ToolSearchResult struct {
Query string `json:"query"`
Results []search.Hit `json:"results"`
TotalIndexed int `json:"total_indexed"`
TypeFilter string `json:"type_filter,omitempty"`
Engine string `json:"engine"`
DurationMs int64 `json:"duration_ms"`
}
ToolSearchResult is the JSON envelope returned to the agent.
type WebFetchResult ¶
type WebFetchResult struct {
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"
Engine string `json:"engine"` // "go-readability" | "stdlib"
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"`
DurationMs int64 `json:"duration_ms"`
Truncated bool `json:"truncated"`
ErrorReason string `json:"error_reason,omitempty"`
}
WebFetchResult is the uniform shape returned to the agent.
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 {
Query string `json:"query"`
Results []WebSearchHit `json:"results"`
ResultsCount int `json:"results_count"`
Backend string `json:"backend"`
DurationMs int64 `json:"duration_ms"`
Truncated bool `json:"truncated"`
ErrorReason string `json:"error_reason,omitempty"`
}
WebSearchResult is the uniform result envelope the agent receives.
type WriteResult ¶
type WriteResult struct {
Path string `json:"path"`
BytesWritten int64 `json:"bytes_written"`
Created bool `json:"created"`
LineEndings string `json:"line_endings"`
DurationMs int64 `json:"duration_ms"`
ErrorReason string `json:"error_reason,omitempty"`
}
WriteResult is the uniform shape returned to the agent.