thinkwell
npx skills add https://github.com/dherman/thinkwell --skill thinkwell
Agent 安装分布
Skill 文档
Thinkwell
Thinkwell is a TypeScript framework for blending deterministic code with LLM-powered reasoning. It provides a fluent Plan API for composing prompts, attaching tools, and getting structured JSON responses.
For detailed API signatures, see references/api-reference.md.
For @JSONSchema details, see references/schema-guide.md.
For complete working examples, see references/examples.md.
Core Concepts
1. The @JSONSchema Pattern
Annotate an interface with @JSONSchema in a JSDoc comment. Thinkwell auto-generates a TypeName.Schema namespace that provides the JSON schema at runtime:
/**
* A summary of content.
* @JSONSchema
*/
export interface Summary {
/** A brief title */
title: string;
/** Key points from the content */
points: string[];
/**
* Word count of the original
* @minimum 0
*/
wordCount: number;
}
// Summary.Schema is auto-generated â use it with agent.think()
Works with interfaces, type aliases, enums, and classes. JSDoc comments on properties become descriptions in the generated schema. Annotations like @minimum, @maximum, @minLength, @maxLength, @pattern, and @format map to JSON Schema validation keywords.
2. Agent Lifecycle
import { open } from "thinkwell";
const agent = await open('claude'); // Or: 'codex', 'gemini', 'kiro', 'opencode', 'auggie'
try {
const result = await agent
.think(Summary.Schema) // Start builder with output schema
.text("Summarize this:") // Add prompt text
.quote(content) // Add quoted content
.run(); // Execute â returns typed Summary
console.log(result.title);
} finally {
agent.close(); // Always close when done
}
The pattern is always: open â think â plan â run â close.
3. Plan Fluent API
agent.think(schema) returns a Plan. Chain methods to compose the prompt, then call .run() or .stream():
Content methods:
.text(content)â Add literal text.textln(content)â Add text with trailing newline.quote(content, label?)â Add content in XML-style tags (e.g.,<feedback>...</feedback>).code(content, language?)â Add content as a fenced code block
Tool methods:
.tool(name, description, handler)â Register a tool (no input schema).tool(name, description, inputSchema, handler)â Register a tool with typed input.tool(name, description, inputSchema, outputSchema, handler)â Full form with both schemas.defineTool(...)â Same overloads, but hidden from the prompt text
Skill methods:
.skill(path)â Attach a stored skill from a SKILL.md directory.skill({ name, description, body, tools? })â Attach a virtual skill
Configuration:
.cwd(path)â Set the working directory for the session
Execution:
.run()â Execute and return the typed result.stream()â Execute and return aThoughtStreamfor streaming events + result
Note:
Planwas previously namedThinkBuilder. The old name still works as a deprecated alias.
4. Tools
Tools let the agent call back into your code. Three overloads:
Simple tool (no input schema):
.tool(
"current_time",
"Returns the current date and time.",
async () => ({
time: new Date().toLocaleTimeString(),
date: new Date().toLocaleDateString(),
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
})
)
Tool with typed input:
/** @JSONSchema */
interface SearchInput {
/** Glob pattern to match files */
pattern: string;
/** Maximum results to return */
limit?: number;
}
.tool(
"search_files",
"Search for files matching a glob pattern.",
SearchInput.Schema,
async (input) => {
// input is typed as SearchInput
const files = await glob(input.pattern);
return { files: files.slice(0, input.limit ?? 10) };
}
)
5. Thought Streaming
Use .stream() instead of .run() to get real-time events:
const stream = agent
.think(Schema)
.text("Analyze this codebase")
.stream();
for await (const event of stream) {
switch (event.type) {
case "thought": // Agent's internal reasoning
process.stderr.write(event.text);
break;
case "message": // Agent's visible response
process.stdout.write(event.text);
break;
case "tool_start": // Tool invocation started
console.log(`Using tool: ${event.title}`);
break;
case "tool_done": // Tool completed
break;
case "plan": // Agent's execution plan
for (const entry of event.entries) {
console.log(`[${entry.status}] ${entry.content}`);
}
break;
}
}
const result = await stream.result; // Final typed result
The stream and result promise are independent â you can iterate events, await the result, or both.
6. Sessions (Multi-Turn)
For conversations where the agent needs to remember context across calls:
const session = await agent.createSession({ cwd: "/my/project" });
const analysis = await session
.think(AnalysisSchema)
.text("Analyze this codebase")
.run();
// Same session â agent remembers the analysis
const fixes = await session
.think(FixesSchema)
.text("Suggest fixes for the top issues")
.run();
session.close();
Each agent.think() creates an ephemeral session. Use agent.createSession() when you need multi-turn context.
7. Skills
Attach reusable instruction packages to a prompt:
Stored skill (from filesystem):
.skill("./skills/code-review")
Virtual skill (programmatic):
.skill({
name: "test-writer",
description: "Generates unit tests for TypeScript functions.",
body: `
# Test Writer
## Steps
1. Analyze function signatures
2. Generate test cases covering edge cases
3. Use the \`count-assertions\` tool to verify coverage
## Available Tools
### count-assertions
Count assertions in a test file.
Input: \`{ "path": "string" }\`
`,
tools: [{
name: "count-assertions",
description: "Count assertions in a test file",
handler: async ({ path }) => {
const content = await fs.readFile(path, "utf-8");
const matches = content.match(/expect\(/g) || [];
return { count: matches.length };
},
}],
})
8. schemaOf() Helper
For schemas without @JSONSchema (e.g., inline or dynamic schemas):
import { schemaOf } from "thinkwell";
const result = await agent
.think(schemaOf<{ answer: string }>({
type: "object",
properties: { answer: { type: "string" } },
required: ["answer"]
}))
.text("What is 2 + 2?")
.run();
CLI Usage
Running Scripts
# Run a script directly
thinkwell script.ts
# Or use a shebang
#!/usr/bin/env thinkwell
Scripts use standard npm imports (import { open } from "thinkwell").
IDE Support
Generate declaration files for TypeName.Schema autocomplete:
thinkwell types # One-time generation
thinkwell types --watch # Watch mode for development
Add *.thinkwell.d.ts to your tsconfig’s include array and .gitignore.
Alternatively, install the Thinkwell VSCode extension for automatic IDE support without generating files.
Type Checking
thinkwell check # Type-check the project (supports @JSONSchema)
Recommended Method Chain Order
const result = await agent
.think(OutputSchema) // 1. Schema (always first) â returns Plan
.cwd("/my/project") // 2. Configuration
.skill("./skills/code-review") // 3. Skills
.text("Analyze this code:") // 4. Prompt content
.code(sourceCode, "typescript")
.tool("helper", "...", handler) // 5. Tools
.run(); // 6. Execute (always last)
Common Patterns
Prompt-Only (No Tools)
const summary = await agent
.think(Summary.Schema)
.text("Summarize the following content:")
.quote(content)
.run();
Tool + Streaming
const stream = agent
.think(Greeting.Schema)
.text("Create a time-appropriate greeting")
.tool("current_time", "Get current time", async () => new Date())
.stream();
for await (const event of stream) {
if (event.type === 'message') process.stdout.write(event.text);
}
const greeting = await stream.result;
Custom Agent Command
const agent = await open({ cmd: 'my-custom-agent --acp' });
Environment Variable Override
# Override the agent for all scripts
THINKWELL_AGENT=gemini thinkwell script.ts
# Override with a custom command
THINKWELL_AGENT_CMD="my-agent --acp" thinkwell script.ts