Complete reference for all 10 JOC TypeScript tools
- Overview
- Tool Architecture
- Skill Management Tools
- Agent Context Tools
- Session Management Tools
- Workflow State Tools
- Tool Development Guide
- Best Practices
JOC includes TypeScript tools that provide programmatic access to core functionality. Each tool is a TypeScript file using the @opencode-ai/plugin SDK.
| Tool | Purpose |
|---|---|
| loadSkill | Load skill content, metadata, and scripts |
| runSkillScript | Execute skill-bundled shell scripts |
| agentContext | Manage project memory, notepad, and modes |
| listAgents | List and filter agents by capability |
| modeState | Start/stop/update/list execution modes |
| taskTodos | Create and manage task lists |
| artifacts | Save and load skill artifacts |
| getSessionID | Get current session ID |
| saveCommitMessage | Save commit message for later |
| getCommitMessage | Retrieve saved commit message |
┌─────────────────────────────────────────────────────────────────┐
│ OpenCode Session │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Tool Invocation │ │
│ │ (via agent/tool) │ │
│ └─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ TypeScript Tool │ │
│ │ (execute function) │ │
│ └─────────────────────┘ │
│ │ │
│ ┌────────────────┴────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │File Ops │ │State Ops │ │Context │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Return Result │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "Tool description",
args: {
argName: {
type: "string",
description: "Argument description"
}
},
async execute(args, context) {
// Implementation
return result
}
})Loads skill content, metadata, and bundled scripts.
Tool File: loadSkill.ts
Arguments:
| Argument | Type | Required | Description |
|---|---|---|---|
skillName |
string | Yes | Name of the skill to load |
loadContent |
boolean | No | Whether to load full content (default: true) |
loadScripts |
boolean | No | Whether to load bundled scripts (default: false) |
Returns:
{
name: string;
description: string;
level: number;
content: string; // If loadContent: true
scripts: Script[]; // If loadScripts: true
metadata: {
userInvocable: boolean;
argumentHint: string;
};
path: string;
}Example:
// Load skill with content
const skill = await loadSkill({
skillName: "autopilot",
loadContent: true,
loadScripts: false
})
console.log(skill.description) // "Full autonomous execution..."
console.log(skill.level) // 4
console.log(skill.content) // Full SKILL.md contentSearch Order:
- Project-level:
.opencode/skills/{skillName}/SKILL.md - User-wide:
~/.config/opencode/skills/{skillName}/SKILL.md
Executes skill-bundled shell scripts.
Tool File: runSkillScript.ts
Arguments:
| Argument | Type | Required | Description |
|---|---|---|---|
skillName |
string | Yes | Name of the skill |
scriptPath |
string | Yes | Path to script relative to skill directory |
args |
object | No | Arguments to pass to script |
env |
object | No | Environment variables |
Returns:
{
success: boolean;
stdout: string;
stderr: string;
exitCode: number;
duration: number;
}Example:
// Run setup script
const result = await runSkillScript({
skillName: "joc-setup",
scriptPath: "scripts/setup.sh",
args: { mode: "global" }
})
if (result.success) {
console.log("Setup complete")
} else {
console.error("Setup failed:", result.stderr)
}Script Security:
- Scripts run in non-interactive mode
- Environment variables are sanitized
- Scripts must be in skill's
scripts/directory - Exit codes are captured
Manages project memory, notepad, and active modes.
Tool File: agentContext.ts
Arguments:
| Argument | Type | Required | Description |
|---|---|---|---|
action |
string | Yes | Action to perform |
data |
object | No | Data for the action |
Actions:
| Action | Description |
|---|---|
getMemory |
Get project memory |
setMemory |
Set project memory |
updateMemory |
Update memory fields |
getNotepad |
Get notepad content |
setNotepad |
Set notepad content |
appendNotepad |
Append to notepad |
getActiveModes |
List active modes |
clearContext |
Clear all context |
Returns:
// getMemory
{
techStack: {
languages: [{ name: string, version: string }],
frameworks: [{ name: string, version: string }],
databases: [{ name: string, type: string }]
},
customNotes: [{ note: string, timestamp: string }],
createdAt: string,
updatedAt: string
}
// getNotepad
{
content: string,
updatedAt: string
}
// getActiveModes
{
modes: [{
name: string,
active: boolean,
startedAt: string
}]
}Example:
// Get project memory
const memory = await agentContext({ action: "getMemory" })
// Update memory
await agentContext({
action: "updateMemory",
data: {
techStack: {
languages: [{ name: "TypeScript", version: "5.7" }],
frameworks: [{ name: "React", version: "19.0" }]
}
}
})
// Append to notepad
await agentContext({
action: "appendNotepad",
data: { content: "\n- Task completed: auth" }
})
// List active modes
const modes = await agentContext({ action: "getActiveModes" })Storage Location:
- Memory:
.opencode/state/project-memory.json - Notepad:
.opencode/state/notepad.md
Lists and filters agents by capability.
Tool File: listAgents.ts
Arguments:
| Argument | Type | Required | Description |
|---|---|---|---|
filter |
object | No | Filter criteria |
includeDetails |
boolean | No | Include full details (default: false) |
Filter Options:
| Property | Type | Description |
|---|---|---|
model |
string | Filter by model (sonnet, opus, etc.) |
capability |
string | Filter by capability (implementation, review, etc.) |
name |
string | Filter by name (regex supported) |
Returns:
{
agents: [{
name: string;
description: string;
model: string;
mode: string;
capabilities?: string[];
permissions?: Record<string, string>;
}],
total: number,
filtered: number
}Example:
// List all agents
const allAgents = await listAgents({})
// Filter by model
const sonnetAgents = await listAgents({
filter: { model: "sonnet" }
})
// Filter by capability
const reviewAgents = await listAgents({
filter: { capability: "review" },
includeDetails: true
})
// Search by name
const matchingAgents = await listAgents({
filter: { name: "test" }
})Capability Categories:
| Category | Agents |
|---|---|
| implementation | executor, code-simplifier, refactoring, frontend-design |
| architecture | architect, planner, analyst |
| review | code-reviewer, security-reviewer, critic, verifier |
| testing | test-engineer, qa-tester |
| documentation | writer, document-specialist |
| research | scientist, explore |
| debugging | debugger, tracer |
Gets the current session identifier.
Tool File: getSessionID.ts
Arguments: None
Returns:
string // Session ID (e.g., "abc123def456")Example:
const sessionId = await getSessionID()
console.log(`Current session: ${sessionId}`)Usage:
- Used to track session-specific state
- Passed to other tools that need session context
- Logs and debugging
Saves a commit message for later retrieval.
Tool File: saveCommitMessage.ts
Arguments:
| Argument | Type | Required | Description |
|---|---|---|---|
commitMessage |
string | Yes | Commit message to save |
Returns:
{
success: boolean;
savedAt: string;
sessionId: string;
}Example:
await saveCommitMessage({
commitMessage: "feat(auth): add OAuth2 support"
})
// Later, in another part of workflow
const message = await getCommitMessage()Retrieves the saved commit message.
Tool File: getCommitMessage.ts
Arguments: None
Returns:
{
message: string | null;
savedAt: string | null;
sessionId: string;
}Example:
const { message } = await getCommitMessage()
if (message) {
console.log("Saved message:", message)
} else {
console.log("No message saved")
}Use Case:
Draft commit message early in workflow, use during actual commit:
// In planning phase
await saveCommitMessage({
commitMessage: "feat(auth): add OAuth2 support\n\n- Implement flow\n- Add refresh tokens"
})
// Later, during commit
const { message } = await getCommitMessage()
if (message) {
await git.commit(message)
}Manages execution mode state (ralph, autopilot, etc.).
Tool File: modeState.ts
Arguments:
| Argument | Type | Required | Description |
|---|---|---|---|
action |
string | Yes | Action to perform |
mode |
string | No | Mode name (for start/stop/update) |
data |
object | No | Additional data |
Actions:
| Action | Description |
|---|---|
start |
Start a mode |
stop |
Stop a mode |
update |
Update mode state |
list |
List all mode states |
get |
Get specific mode state |
Returns:
// start/stop/update
{
success: boolean;
mode: string;
state: {
active: boolean;
started_at?: string;
completed_at?: string;
iteration?: number;
max_iterations?: number;
current_phase?: string;
[key: string]: any;
}
}
// list
{
modes: [{
name: string;
active: boolean;
state: object;
}]
}
// get
{
mode: string;
state: object | null;
}Example:
// Start ralph mode
await modeState({
action: "start",
mode: "ralph",
data: {
prompt: "fix all TypeScript errors",
max_iterations: 100
}
})
// Update state (increment iteration)
await modeState({
action: "update",
mode: "ralph",
data: {
iteration: 5,
current_phase: "fixing errors"
}
})
// Check if mode is active
const { state } = await modeState({
action: "get",
mode: "ralph"
})
if (state?.active) {
console.log(`Ralph iteration: ${state.iteration}`)
}
// List all active modes
const { modes } = await modeState({ action: "list" })
// Stop mode
await modeState({
action: "stop",
mode: "ralph"
})State Persistence:
States are persisted to .opencode/state/{mode}-state.json.
Creates and manages task lists.
Tool File: taskTodos.ts
Arguments:
| Argument | Type | Required | Description |
|---|---|---|---|
action |
string | Yes | Action to perform |
todos |
array | No | Todo items (for create) |
id |
string | No | Todo ID (for update) |
status |
string | No | New status (for update) |
Actions:
| Action | Description |
|---|---|
create |
Create new todo list |
update |
Update todo status |
list |
List all todos |
get |
Get specific todo |
clear |
Clear all todos |
Todo Item Structure:
{
id: string; // Auto-generated
content: string; // Task description
status: "pending" | "in_progress" | "completed" | "cancelled";
priority: "high" | "medium" | "low";
created_at: string;
updated_at: string;
}Returns:
{
todos: Todo[];
total: number;
pending: number;
in_progress: number;
completed: number;
}Example:
// Create todo list
await taskTodos({
action: "create",
todos: [
{ content: "Implement auth", priority: "high" },
{ content: "Add tests", priority: "medium" },
{ content: "Update docs", priority: "low" }
]
})
// Update status to in_progress
await taskTodos({
action: "update",
id: "todo-1",
status: "in_progress"
})
// Mark as completed
await taskTodos({
action: "update",
id: "todo-1",
status: "completed"
})
// List all todos
const { todos } = await taskTodos({ action: "list" })
// Clear all
await taskTodos({ action: "clear" })Saves and loads skill artifacts.
Tool File: artifacts.ts
Arguments:
| Argument | Type | Required | Description |
|---|---|---|---|
action |
string | Yes | Action to perform |
skillName |
string | Yes | Skill name |
artifact |
object | No | Artifact data (for save) |
filename |
string | No | Specific file to load |
Actions:
| Action | Description |
|---|---|
save |
Save artifact |
load |
Load artifact |
list |
List artifacts for skill |
delete |
Delete artifact |
Returns:
// save
{
success: boolean;
path: string;
}
// load
{
data: any;
path: string;
createdAt: string;
}
// list
{
artifacts: [{
name: string;
path: string;
size: number;
createdAt: string;
}]
}Example:
// Save artifact
await artifacts({
action: "save",
skillName: "planning",
artifact: {
plan: "Implementation plan...",
tasks: ["task1", "task2"],
estimated_time: "2 hours"
}
})
// Load artifact
const { data } = await artifacts({
action: "load",
skillName: "planning",
filename: "plan.json"
})
// List artifacts
const { artifacts } = await artifacts({
action: "list",
skillName: "planning"
})Storage Location:
.opencode/state/artifacts/{skillName}/{sessionId}/{filename}
touch .opencode/tools/my-tool.tsimport { tool } from "@opencode-ai/plugin"
import { z } from "zod" // Optional: for validation
// Define arguments schema
const ArgsSchema = z.object({
arg1: z.string().describe("First argument"),
arg2: z.string().optional().default("default").describe("Second argument")
})
// Define return type
interface MyToolResult {
success: boolean
data?: any
error?: string
}
export default tool({
description: "My custom tool description",
args: {
arg1: {
type: "string",
description: "First argument"
},
arg2: {
type: "string",
description: "Second argument (default: 'default')",
default: "default"
}
},
async execute(args, context): Promise<MyToolResult> {
const { arg1, arg2 } = args
const { sessionID, projectRoot } = context
// Implementation
const result = await doSomething(arg1, arg2)
// Return result
return {
success: true,
data: result
}
}
})Add to opencode.jsonc:
The context object provides:
interface ToolContext {
sessionID: string; // Current session ID
projectRoot: string; // Project root directory
workingDirectory: string; // Current working directory
// ... additional context
}import { readFileSync, writeFileSync } from "fs"
import { join } from "path"
export default tool({
// ...
async execute(args, context) {
const filePath = join(context.projectRoot, ".opencode", "state", "my-file.json")
// Read
const data = JSON.parse(readFileSync(filePath, "utf-8"))
// Write
writeFileSync(filePath, JSON.stringify(data, null, 2))
return { success: true }
}
})export default tool({
// ...
async execute(args, context) {
try {
const result = await riskyOperation(args)
return { success: true, data: result }
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error"
}
}
}
})- Use camelCase:
loadSkill,agentContext - Be descriptive:
getSessionIDnotgsi - Match action:
getStatenotstate
- Keep minimal required arguments
- Provide sensible defaults
- Use consistent naming patterns
- Document all arguments
- Always return success/failure status
- Include relevant data
- Make errors actionable
- Keep consistent structure
- Cache expensive operations
- Use lazy loading
- Batch operations when possible
- Avoid redundant file reads
- Execution Modes - Modes that use these tools
- Skills - Skills that invoke tools
- Plugin System - How tools hook into the system
{ "tools": { "myTool": "./tools/my-tool.ts" } }