core

package
v0.8.6 Latest Latest
Warning

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

Go to latest
Published: Apr 26, 2026 License: MIT Imports: 26 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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

func CoreToolDocs() []search.Doc

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

func RegisterBash(s *server.MCPServer)

RegisterBash adds the Bash tool to the given MCP server.

func RegisterEdit

func RegisterEdit(s *server.MCPServer)

RegisterEdit adds the Edit tool to the given MCP server.

func RegisterGlob

func RegisterGlob(s *server.MCPServer)

RegisterGlob adds the Glob tool to the given MCP server.

func RegisterGrep

func RegisterGrep(s *server.MCPServer)

RegisterGrep adds the Grep tool to the given MCP server.

func RegisterRead

func RegisterRead(s *server.MCPServer)

RegisterRead adds the Read tool to the given MCP server.

func RegisterToolSearch

func RegisterToolSearch(s *server.MCPServer, idx *search.Index)

RegisterToolSearch adds the ToolSearch tool to the given MCP server, closing over the index built at startup.

func RegisterWebFetch

func RegisterWebFetch(s *server.MCPServer)

RegisterWebFetch adds the WebFetch tool to the given MCP server.

func RegisterWebSearch

func RegisterWebSearch(s *server.MCPServer, store *secrets.Store)

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

func RegisterWrite(s *server.MCPServer)

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

func LookupEngine(name string) Engine

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.

Jump to

Keyboard shortcuts

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