Files
flynn/docs/api/TOOLS.md
T
2026-02-16 14:45:45 -08:00

29 KiB

Tools API Documentation

Flynn's tool framework provides a standardized interface for capabilities that can be invoked by AI agents. This document describes the tool contracts, schemas, and implementation patterns.

Table of Contents

Overview

Tools are executable capabilities that the AI agent can call to perform actions beyond text generation.

Tool Categories

  • File System: file.read, file.write, file.edit, file.list
  • Shell/Process: shell.exec, process.start, process.kill
  • Web: web.fetch, web.search
  • Browser: browser.navigate, browser.screenshot
  • Memory: memory.read, memory.write, memory.search
  • MinIO: minio.share, minio.ingest, minio.sync
  • Kubernetes: k8s.pods, k8s.deployments, k8s.logs
  • Media: media.send, image.analyze, audio.transcribe
  • System: system.info
  • Session: sessions.list, sessions.delete

Tool Benefits

  • Modular: Easy to add, remove, or modify tools
  • Secure: Policy-based access control with hooks
  • Sandboxed: Optional Docker isolation
  • Observable: Tool execution events streamed to clients
  • Type-safe: JSON Schema validation for inputs

Tool Interface

Definition

export interface Tool {
  /** Unique tool name (e.g., 'shell.exec', 'file.read'). */
  name: string;

  /** Human-readable description (shown to AI model). */
  description: string;

  /** JSON Schema for input validation. */
  inputSchema: JSONSchema;

  /** Secret scopes required to execute this tool (optional). */
  requiredSecretScopes?: string[];

  /** Optional execution context (abort signal, runtime metadata). */
  execute: (args: unknown, context?: ToolExecutionContext) => Promise<ToolResult>;
}

export interface ToolExecutionContext {
  signal?: AbortSignal;
}

ToolResult

export interface ToolResult {
  /** Whether execution succeeded. */
  success: boolean;

  /** Text output (shown to model and user). */
  output: string;

  /** Error message if execution failed. */
  error?: string;
}

Input Schema Format

Tools use JSON Schema for input validation. This schema is both:

  1. Validated by Flynn before tool execution
  2. Sent to the AI model for automatic parameter generation

Schema Structure

export interface JSONSchema {
  type: 'object' | 'array' | 'string' | 'number' | 'boolean';
  properties?: Record<string, JSONSchema>;
  required?: string[];
  additionalProperties?: boolean;
  description?: string;
  enum?: unknown[];
  items?: JSONSchema;
}

Example Schemas

Simple String Parameter

{
  type: 'object',
  properties: {
    message: {
      type: 'string',
      description: 'The message to display'
    }
  },
  required: ['message']
}

Multiple Parameters with Options

{
  type: 'object',
  properties: {
    path: {
      type: 'string',
      description: 'File path to read'
    },
    encoding: {
      type: 'string',
      enum: ['utf8', 'base64'],
      description: 'File encoding'
    }
  },
  required: ['path']
}

Nested Object

{
  type: 'object',
  properties: {
    command: {
      type: 'object',
      properties: {
        program: {
          type: 'string',
          description: 'Program to execute'
        },
        args: {
          type: 'array',
          items: { type: 'string' },
          description: 'Command arguments'
        }
      },
      required: ['program']
    }
  },
  required: ['command']
}

Schema Best Practices

  1. Use descriptions: The AI model uses descriptions to understand parameters
  2. Be specific: Narrow types (e.g., enum instead of string)
  3. Provide defaults: Use optional parameters with sensible defaults
  4. Document constraints: Include min/max, patterns, or enum values

Result Format

Success Result

{
  success: true,
  output: "Tool executed successfully. Result: ..."
}

Error Result

{
  success: false,
  output: "",
  error: "File not found: /path/to/file"
}

Output Guidelines

  • Keep output concise: The AI model has limited context
  • Use structured data: JSON or table format for complex results
  • Include metadata: Timestamps, counts, file paths
  • Handle large outputs: Truncate or use file operations

Tool Patterns

Flynn supports three tool implementation patterns:

Pattern 1: Static Tool (No Dependencies)

Use for tools that don't need external dependencies.

// src/tools/builtin/shell.ts
import type { Tool, ToolResult } from '../types.js';

interface ShellExecArgs {
  command: string;
  cwd?: string;
  timeout?: number;
}

export const shellExecTool: Tool = {
  name: 'shell.exec',
  description: 'Execute a shell command and return stdout/stderr',
  inputSchema: {
    type: 'object',
    properties: {
      command: {
        type: 'string',
        description: 'The shell command to execute'
      },
      cwd: {
        type: 'string',
        description: 'Working directory (optional)'
      },
      timeout: {
        type: 'number',
        description: 'Timeout in milliseconds (default 30000)'
      }
    },
    required: ['command']
  },
  execute: async (rawArgs: unknown): Promise<ToolResult> => {
    const args = rawArgs as ShellExecArgs;
    const timeout = args.timeout ?? 30_000;

    // Implementation
    const result = await executeShell(args.command, args.cwd, timeout);

    return {
      success: true,
      output: result.stdout
    };
  }
};

Registration:

// src/tools/builtin/index.ts
export const allBuiltinTools: Tool[] = [
  shellExecTool,
  // ... other static tools
];

Pattern 2: Factory Tool (Single Tool with Dependencies)

Use for tools that need injected dependencies (e.g., memory store, HTTP client).

// src/tools/builtin/memory-read.ts
import type { Tool, ToolResult } from '../types.js';
import type { MemoryStore } from '../../memory/store.js';

interface MemoryReadArgs {
  namespace: string;
}

export function createMemoryReadTool(store: MemoryStore): Tool {
  return {
    name: 'memory.read',
    description: 'Read memory from a namespace',
    inputSchema: {
      type: 'object',
      properties: {
        namespace: {
          type: 'string',
          description: 'Memory namespace to read from'
        }
      },
      required: ['namespace']
    },
    execute: async (rawArgs: unknown): Promise<ToolResult> => {
      const args = rawArgs as MemoryReadArgs;

      try {
        const content = store.read(args.namespace);
        return {
          success: true,
          output: content
        };
      } catch (error) {
        return {
          success: false,
          output: '',
          error: error instanceof Error ? error.message : String(error)
        };
      }
    }
  };
}

Registration:

// src/daemon/index.ts
const memoryReadTool = createMemoryReadTool(memoryStore);
toolRegistry.register(memoryReadTool);

Use for tools that share a common dependency or manager.

// src/tools/builtin/browser/tools.ts
import type { Tool, ToolResult } from '../../types.js';
import type { BrowserManager } from './manager.js';

export function createBrowserTools(manager: BrowserManager): Tool[] {
  return [
    {
      name: 'browser.navigate',
      description: 'Navigate to a URL in the browser',
      inputSchema: {
        type: 'object',
        properties: {
          url: {
            type: 'string',
            description: 'URL to navigate to'
          }
        },
        required: ['url']
      },
      execute: async (rawArgs: unknown): Promise<ToolResult> => {
        const args = rawArgs as { url: string };
        await manager.navigate(args.url);
        return {
          success: true,
          output: `Navigated to ${args.url}`
        };
      }
    },
    {
      name: 'browser.screenshot',
      description: 'Take a screenshot of the current page',
      inputSchema: {
        type: 'object',
        properties: {}
      },
      execute: async (): Promise<ToolResult> => {
        const screenshot = await manager.screenshot();
        return {
          success: true,
          output: 'Screenshot taken'
        };
      }
    }
  ];
}

Registration:

// src/daemon/index.ts
const browserTools = createBrowserTools(browserManager);
browserTools.forEach(tool => toolRegistry.register(tool));

Tool Registration

Registration Chain

  1. Tool implementation file
  2. src/tools/builtin/index.ts (exports for static tools)
  3. src/tools/index.ts (re-exports)
  4. src/daemon/index.ts (registers with ToolRegistry)

ToolRegistry

class ToolRegistry {
  /** Register a tool. Throws if tool name already exists. */
  register(tool: Tool): void;

  /** Unregister a tool by name. */
  unregister(name: string): void;

  /** Get a tool by name. */
  get(name: string): Tool | undefined;

  /** Get tool by API name (handles MCP prefixing). */
  getByApiName(name: string): Tool | undefined;

  /** List all registered tools. */
  list(): Tool[];

  /** Check if a tool is registered. */
  has(name: string): boolean;

  /** Set tool policy. */
  setPolicy(policy: ToolPolicy): void;

  /** Get current tool policy. */
  getPolicy(): ToolPolicy | undefined;
}

Registration Example

// src/daemon/index.ts
import { ToolRegistry } from './tools/registry.js';
import { allBuiltinTools } from './tools/builtin/index.js';
import { createMemoryReadTool } from './tools/builtin/memory-read.js';

// Create registry
const toolRegistry = new ToolRegistry();

// Register static tools
for (const tool of allBuiltinTools) {
  toolRegistry.register(tool);
}

// Register factory tools
const memoryReadTool = createMemoryReadTool(memoryStore);
toolRegistry.register(memoryReadTool);

// Register multi-factory tools
const browserTools = createBrowserTools(browserManager);
for (const tool of browserTools) {
  toolRegistry.register(tool);
}

Tool Policy

Tool policy controls which tools are available to agents based on profiles and patterns.

Profiles

Flynn ships 4 built-in profiles:

  • minimal: read-only (file read/list + web.fetch + system.info)
  • messaging: read-only + web search + memory + connected read APIs (gmail/gcal/gdocs/gdrive/gtasks)
  • coding: adds filesystem writes, shell/process, and browser automation
  • full: all registered tools

The authoritative profile tool sets live in src/tools/policy.ts.

Groups

Tools are organized into groups:

  • group:fs: File system tools
  • group:runtime: Process/execution tools
  • group:web: Web and browser tools
  • group:memory: Memory and search tools

There are additional groups for specific integrations (gmail/gcal/gdocs/gdrive/gtasks/cron/minio/k8s). See TOOL_GROUPS in src/tools/policy.ts.

Policy Resolution

When resolving tools for an execution context:

  1. Start with global tools.profile
  2. Apply global tools.allow (adds tools back in)
  3. Apply global tools.deny (deny always wins)
  4. If context.agent override exists, intersect with agent override resolution
  5. If context.provider override exists, intersect with provider override resolution
  6. If context.skillName is set, intersect with skill capability allowlist (deny-by-default for skills)

Hooks/autonomy are enforced at execution-time (ToolExecutor), not during list resolution.

Example Policy Config

tools:
  profile: messaging
  allow: []
  deny: ["browser.*"]

  # Per-agent overrides
  agents:
    fast:
      profile: minimal
      allow: []
      deny: []

  # Per-provider overrides
  providers:
    ollama:
      profile: messaging
      allow: []
      deny: ["web.search"]

Skill Capabilities (Skill Context)

If a request is routed into a skill context (via intents), Flynn applies an additional restriction layer using the skill's manifest.json.permissions.

  • A skill with no permissions manifest has no tool access.
  • permissions.tools (explicit allowlist) overrides permissions.tool_groups.

See docs/security/SAFE_PERSONAL_AGENT.md.

Tool Execution Flow

Execution Pipeline

Agent Request
    ↓
Model Generates Tool Call
    ↓
ToolExecutor.execute()
    ↓
Policy Check (is tool allowed?)
    ↓
Hook Check (confirm/log/silent)
    ↓
Tool.execute(args)
    ↓
Result Returned
    ↓
Agent Continues

Policy Check

const policy = this.registry.getPolicy();
if (policy) {
  const allNames = this.registry.list().map(t => t.name);
  if (!policy.isAllowed(toolName, allNames, context)) {
    return {
      success: false,
      output: '',
      error: `Tool '${toolName}' is not allowed by tool policy`
    };
  }
}

Hook Check

const baseAction = this.hooks.getAction(toolName);
const autonomyLevel = context?.autonomyLevel ?? 'standard';
const autonomyDecision = resolveAutonomy(toolName, baseAction, autonomyLevel);

if (autonomyDecision.action === 'confirm') {
  // Emit confirmation event, wait for user response
  const confirmed = await waitForConfirmation(toolName, args);
  if (!confirmed) {
    return {
      success: false,
      output: '',
      error: 'Tool execution cancelled by user'
    };
  }
}

Timeout Handling

const timeout = args.timeout ?? this.defaultTimeoutMs;

const result = await Promise.race([
  tool.execute(args),
  new Promise<ToolResult>((_, reject) => {
    setTimeout(() => {
      reject(new Error(`Tool '${toolName}' timed out after ${timeout}ms`));
    }, timeout);
  })
]);

Output Truncation

if (result.output.length > this.maxOutputBytes) {
  result.output = result.output.slice(0, this.maxOutputBytes) +
    `\n[Output truncated - ${result.output.length - this.maxOutputBytes} bytes hidden]`;
}

Builtin Tools Reference

File System Tools

file.read

Read a file's contents.

{
  "name": "file.read",
  "description": "Read the contents of a file",
  "inputSchema": {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "Path to the file to read"
      },
      "encoding": {
        "type": "string",
        "enum": ["utf8", "base64"],
        "description": "File encoding (default: utf8)"
      }
    },
    "required": ["path"]
  }
}

file.write

Write or overwrite a file.

{
  "name": "file.write",
  "description": "Write content to a file (creates if not exists)",
  "inputSchema": {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "Path to the file to write"
      },
      "content": {
        "type": "string",
        "description": "Content to write"
      }
    },
    "required": ["path", "content"]
  }
}

file.edit

Apply a patch to a file.

{
  "name": "file.edit",
  "description": "Apply an edit to a file using search/replace",
  "inputSchema": {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "Path to the file to edit"
      },
      "search": {
        "type": "string",
        "description": "Text to search for"
      },
      "replace": {
        "type": "string",
        "description": "Replacement text"
      }
    },
    "required": ["path", "search", "replace"]
  }
}

file.list

List files in a directory.

{
  "name": "file.list",
  "description": "List files and directories in a path",
  "inputSchema": {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "Directory path to list (default: current directory)"
      },
      "recursive": {
        "type": "boolean",
        "description": "List recursively (default: false)"
      }
    },
    "required": []
  }
}

Shell/Process Tools

shell.exec

Execute a shell command.

{
  "name": "shell.exec",
  "description": "Execute a shell command and return stdout/stderr",
  "inputSchema": {
    "type": "object",
    "properties": {
      "command": {
        "type": "string",
        "description": "The shell command to execute"
      },
      "cwd": {
        "type": "string",
        "description": "Working directory (optional)"
      },
      "timeout": {
        "type": "number",
        "description": "Timeout in milliseconds (default 30000)"
      }
    },
    "required": ["command"]
  }
}

process.start

Start a background process.

{
  "name": "process.start",
  "description": "Start a background process",
  "inputSchema": {
    "type": "object",
    "properties": {
      "command": {
        "type": "string",
        "description": "Command to execute"
      },
      "args": {
        "type": "array",
        "items": { "type": "string" },
        "description": "Command arguments"
      },
      "cwd": {
        "type": "string",
        "description": "Working directory"
      }
    },
    "required": ["command"]
  }
}

process.status

Check status of a background process.

{
  "name": "process.status",
  "description": "Get the status of a background process",
  "inputSchema": {
    "type": "object",
    "properties": {
      "pid": {
        "type": "number",
        "description": "Process ID"
      }
    },
    "required": ["pid"]
  }
}

process.kill

Kill a background process.

{
  "name": "process.kill",
  "description": "Kill a background process",
  "inputSchema": {
    "type": "object",
    "properties": {
      "pid": {
        "type": "number",
        "description": "Process ID"
      },
      "signal": {
        "type": "string",
        "enum": ["SIGTERM", "SIGKILL"],
        "description": "Signal to send (default: SIGTERM)"
      }
    },
    "required": ["pid"]
  }
}

Web Tools

web.fetch

Fetch a webpage or API endpoint.

{
  "name": "web.fetch",
  "description": "Fetch content from a URL",
  "inputSchema": {
    "type": "object",
    "properties": {
      "url": {
        "type": "string",
        "description": "URL to fetch"
      },
      "method": {
        "type": "string",
        "enum": ["GET", "POST", "PUT", "DELETE"],
        "description": "HTTP method (default: GET)"
      },
      "headers": {
        "type": "object",
        "description": "HTTP headers"
      },
      "body": {
        "type": "string",
        "description": "Request body"
      },
      "timeout": {
        "type": "number",
        "description": "Timeout in milliseconds (default 10000)"
      }
    },
    "required": ["url"]
  }
}

web.search

Search the web.

{
  "name": "web.search",
  "description": "Search the web and return results",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "Search query"
      },
      "limit": {
        "type": "number",
        "description": "Number of results to return (default: 10)"
      }
    },
    "required": ["query"]
  }
}

Memory Tools

memory.read

Read memory from a namespace.

{
  "name": "memory.read",
  "description": "Read memory from a namespace",
  "inputSchema": {
    "type": "object",
    "properties": {
      "namespace": {
        "type": "string",
        "description": "Memory namespace to read from"
      }
    },
    "required": ["namespace"]
  }
}

memory.write

Write memory to a namespace.

{
  "name": "memory.write",
  "description": "Write memory to a namespace",
  "inputSchema": {
    "type": "object",
    "properties": {
      "namespace": {
        "type": "string",
        "description": "Memory namespace to write to"
      },
      "content": {
        "type": "string",
        "description": "Content to write"
      }
    },
    "required": ["namespace", "content"]
  }
}

memory.search

Search memory using hybrid (keyword + vector) search.

{
  "name": "memory.search",
  "description": "Search memory using hybrid search",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "Search query"
      },
      "namespace": {
        "type": "string",
        "description": "Namespace to search in (optional)"
      },
      "limit": {
        "type": "number",
        "description": "Number of results to return (default: 10)"
      },
      "keywordOnly": {
        "type": "boolean",
        "description": "Use keyword search only (no vector search)"
      }
    },
    "required": ["query"]
  }
}

MinIO Tools

minio.share

Upload a local file to MinIO and return a temporary presigned download URL.

minio.ingest

Read a text-like object from MinIO and write it into a memory namespace.

{
  "name": "minio.ingest",
  "description": "Read a text-like object from MinIO and ingest it into memory namespace for later retrieval/search.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "object_key": {
        "type": "string",
        "description": "Object key in MinIO bucket"
      },
      "bucket": {
        "type": "string",
        "description": "Optional bucket override"
      },
      "namespace": {
        "type": "string",
        "description": "Memory namespace (default: global/knowledge)"
      },
      "mode": {
        "type": "string",
        "enum": ["append", "replace"],
        "description": "Write mode"
      },
      "max_chars": {
        "type": "number",
        "description": "Maximum characters to ingest"
      },
      "force": {
        "type": "boolean",
        "description": "Override non-text extension/content safety checks"
      }
    },
    "required": ["object_key"]
  }
}

minio.sync

Sync text-like objects from a MinIO prefix into nested memory namespaces.

{
  "name": "minio.sync",
  "description": "Sync text-like objects from a MinIO prefix into memory namespaces.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "prefix": {
        "type": "string",
        "description": "MinIO object prefix to sync recursively"
      },
      "bucket": {
        "type": "string",
        "description": "Optional bucket override"
      },
      "namespace_base": {
        "type": "string",
        "description": "Base memory namespace"
      },
      "mode": {
        "type": "string",
        "enum": ["append", "replace"],
        "description": "Write mode per object namespace"
      },
      "max_objects": {
        "type": "number",
        "description": "Maximum objects to ingest in one run"
      },
      "max_chars_per_object": {
        "type": "number",
        "description": "Maximum characters per object"
      },
      "force": {
        "type": "boolean",
        "description": "Override non-text extension/content safety checks"
      }
    },
    "required": ["prefix"]
  }
}

Kubernetes Tools

k8s.pods

List Kubernetes pods with status summary.

k8s.deployments

List Kubernetes deployments with replica readiness summary.

k8s.logs

Fetch recent logs for a pod.

{
  "name": "k8s.logs",
  "description": "Fetch recent logs for a Kubernetes pod.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "pod": {
        "type": "string",
        "description": "Pod name"
      },
      "namespace": {
        "type": "string",
        "description": "Namespace"
      },
      "container": {
        "type": "string",
        "description": "Container name (optional)"
      },
      "lines": {
        "type": "number",
        "description": "Tail line count"
      },
      "since": {
        "type": "string",
        "description": "Lookback duration (e.g. 10m, 1h)"
      }
    },
    "required": ["pod"]
  }
}

Media Tools

media.send

Send a media attachment (image, audio, file) to the user.

{
  "name": "media.send",
  "description": "Send a media attachment to the user",
  "inputSchema": {
    "type": "object",
    "properties": {
      "data": {
        "type": "string",
        "description": "Base64-encoded data"
      },
      "mimeType": {
        "type": "string",
        "description": "MIME type (e.g., 'image/jpeg', 'audio/ogg')"
      },
      "filename": {
        "type": "string",
        "description": "Filename (optional)"
      }
    },
    "required": ["data", "mimeType"]
  }
}

image.analyze

Analyze an image.

{
  "name": "image.analyze",
  "description": "Analyze an image and describe its contents",
  "inputSchema": {
    "type": "object",
    "properties": {
      "data": {
        "type": "string",
        "description": "Base64-encoded image data"
      },
      "mimeType": {
        "type": "string",
        "enum": ["image/jpeg", "image/png", "image/gif", "image/webp"],
        "description": "Image MIME type"
      },
      "prompt": {
        "type": "string",
        "description": "Specific analysis request (optional)"
      }
    },
    "required": ["data", "mimeType"]
  }
}

audio.transcribe

Transcribe audio to text.

{
  "name": "audio.transcribe",
  "description": "Transcribe audio to text",
  "inputSchema": {
    "type": "object",
    "properties": {
      "data": {
        "type": "string",
        "description": "Base64-encoded audio data"
      },
      "mimeType": {
        "type": "string",
        "enum": ["audio/ogg", "audio/wav", "audio/mpeg", "audio/webm"],
        "description": "Audio MIME type"
      }
    },
    "required": ["data", "mimeType"]
  }
}

MCP Tools

Flynn supports external tools via the Model Context Protocol (MCP).

MCP Tool Names

MCP tools are prefixed with mcp::

'mcp:filesystem:read_file'
'mcp:filesystem:write_file'
'mcp:brave_search:search'

MCP Registration

// src/mcp/manager.ts
export class McpManager {
  async startServer(config: McpServerConfig): Promise<void> {
    const client = new McpClient(config);
    await client.connect();

    // Bridge MCP tools to Flynn's tool registry
    const tools = client.listTools();
    for (const tool of tools) {
      const bridgedTool = bridgeMcpTool(tool, client);
      this.toolRegistry.register(bridgedTool);
    }
  }
}

MCP Tool Format

MCP tools are converted to Flynn's Tool format:

function bridgeMcpTool(mcpTool: McpTool, client: McpClient): Tool {
  return {
    name: `mcp:${mcpTool.name}`,
    description: mcpTool.description,
    inputSchema: mcpTool.inputSchema,
    execute: async (rawArgs: unknown) => {
      const result = await client.callTool(mcpTool.name, rawArgs);
      return {
        success: true,
        output: JSON.stringify(result.content)
      };
    }
  };
}

Example Implementation

Complete Example: Custom Git Tool

// src/tools/builtin/git-status.ts
import { execFile } from 'child_process';
import type { Tool, ToolResult } from '../types.js';

interface GitStatusArgs {
  path?: string;
}

export const gitStatusTool: Tool = {
  name: 'git.status',
  description: 'Get the status of a Git repository',
  inputSchema: {
    type: 'object',
    properties: {
      path: {
        type: 'string',
        description: 'Path to the repository (default: current directory)'
      }
    },
    required: []
  },
  execute: async (rawArgs: unknown): Promise<ToolResult> => {
    const args = rawArgs as GitStatusArgs;
    const cwd = args.path || process.cwd();

    try {
      const result = await new Promise<{ stdout: string; stderr: string }>(
        (resolve, reject) => {
          execFile('git', ['status', '--short'], { cwd }, (error, stdout, stderr) => {
            if (error) {
              reject(error);
            } else {
              resolve({ stdout, stderr });
            }
          });
        }
      );

      return {
        success: true,
        output: result.stdout || 'No changes'
      };
    } catch (error) {
      return {
        success: false,
        output: '',
        error: error instanceof Error
          ? `Git status failed: ${error.message}`
          : String(error)
      };
    }
  }
};

Register the Tool

// src/tools/builtin/index.ts
import { gitStatusTool } from './git-status.js';

export const allBuiltinTools: Tool[] = [
  // ... existing tools
  gitStatusTool,
];

Testing

// src/tools/builtin/git-status.test.ts
import { describe, it, expect } from 'vitest';
import { gitStatusTool } from './git-status.js';

describe('git.status tool', () => {
  it('should return git status', async () => {
    const result = await gitStatusTool.execute({});
    expect(result.success).toBe(true);
    expect(result.output).toBeDefined();
  });

  it('should handle non-git directory', async () => {
    const result = await gitStatusTool.execute({ path: '/tmp' });
    expect(result.success).toBe(false);
    expect(result.error).toContain('Git status failed');
  });
});

For more information:

  • Tool types: src/tools/types.ts
  • Builtin tools: src/tools/builtin/*.ts
  • Tool policy: src/tools/policy.ts
  • Tool executor: src/tools/executor.ts