ainvoke provides a focused runner for invoking agent CLIs with a normalized JSON I/O contract.
go install github.com/metalagman/ainvoke/cmd/ainvoke@latestnpx @metalagman/ainvoke quickstart
# Or install globally
npm install -g @metalagman/ainvoke
ainvoke quickstartuvx ainvoke quickstart
# Or install
uv pip install ainvoke
ainvoke quickstartDownload pre-built binaries from GitHub Releases:
# Linux/macOS
curl -L https://github.com/metalagman/ainvoke/releases/latest/download/ainvoke-linux-amd64 -o ainvoke
chmod +x ainvoke
sudo mv ainvoke /usr/local/bin/
# Verify checksum (recommended)
curl -L https://github.com/metalagman/ainvoke/releases/latest/download/checksums.txt -o checksums.txt
sha256sum -c checksums.txtainvoke <command> [flags]
--input-schema(default{"type":"object","properties":{"input":{"type":"string"}},"required":["input"]})--output-schema(default{"type":"object","properties":{"output":{"type":"string"}},"required":["output"]})--input-schema-file--output-schema-file--prompt--input--extra-args--work-dir(must already exist; returns an error otherwise)--debug(forward agent stdout/stderr to stderr)
Displays a guide with common usage examples and instructions for all supported agents.
ainvoke quickstartPrints the build version, git commit, and build date embedded at build time.
ainvoke versionFlags:
- Common flags
--tty(defaulttrue)
ainvoke exec codex \
--input-schema='{"type":"object","properties":{"input":{"type":"string"}},"required":["input"]}' \
--output-schema='{"type":"object","properties":{"output":{"type":"string"}},"required":["output"]}' \
--prompt="input is a name, answer as Hello, <name>!" \
--input='{"input":"Bro"}' \
--extra-args="--model=gpt-5.1-codex-mini,--sandbox=workspace-write" \
--work-dir=.ainvoke exec codex \
--input-schema-file=./schemas/input.json \
--output-schema-file=./schemas/output.json \
--prompt="input is a name, answer as Hello, <name>!" \
--input='{"input":"Bro"}' \
--extra-args="--model=gpt-5.1-codex-mini,--sandbox=workspace-write" \
--work-dir=.Flags:
- Common flags
--model(optional)
Defaults:
- Runs in headless mode (no TTY).
ainvoke claude \
--model="claude-3-5-sonnet-latest" \
--prompt="Input is a name, answer as Salam, <name>!" \
--input='{"input":"Bro"}' \
--work-dir=.Flags:
- Common flags
--model(optional)
Defaults:
- Inserts
execsubcommand when missing. - Runs in headless mode (no TTY).
ainvoke codex \
--model="gpt-5.1-codex-mini" \
--prompt="Input is a name, answer as Salam, <name>!" \
--input='{"input":"Bro"}' \
--work-dir=.ainvoke codex \
--input-schema='{"type":"string"}' \
--output-schema='{"type":"string"}' \
--prompt="Input is a name, answer as Salam, <name>!" \
--input="Bro" \
--model="gpt-5.1-codex-mini" \
--work-dir=.Flags:
- Common flags
--model(optional)
Defaults:
- Adds
--output-format textunless provided. - Runs in headless mode (no TTY).
ainvoke gemini \
--model="gemini-3-flash-preview" \
--prompt="Input is a name, answer as Salam, <name>!" \
--input='{"input":"Bro"}' \
--work-dir=.Flags:
- Common flags
--model(optional)
Defaults:
- Inserts
runsubcommand when missing. - Runs in headless mode (no TTY).
ainvoke opencode \
--model="opencode/big-pickle" \
--prompt="Input is a name, answer as Salam, <name>!" \
--input='{"input":"Bro"}' \
--work-dir=.Notes:
- Use
--input-schema-fileor--output-schema-fileto load schemas from files. - On success, the CLI prints
output.jsonto stdout and preserves the agent exit code. --tty=falsedisables pseudo-terminal execution forexec.--debugforwards agent stdout/stderr to stderr for troubleshooting.
const inputSchema = `{"type":"object","properties":{"input":{"type":"string"}},"required":["input"]}`
const outputSchema = `{"type":"object","properties":{"output":{"type":"string"}},"required":["output"]}`const inputSchema = `{"type":"string"}`
const outputSchema = `{"type":"string"}`- Ensure the agent CLI specified by
cmdis installed and available onPATH. RunDirmust already exist; the runner does not create it and returns an error if it is missing.- The runner writes
input.jsonfromInvocation.Input(or expects it to already exist ifInputis nil). - The runner validates
input.jsonagainstInputSchemabefore running the agent. - The agent must write
output.jsoninRunDir; on success the runner validates it againstOutputSchema. SystemPromptis optional and should be used for extra instructions beyond the built-in schema and I/O requirements.WithStdoutandWithStderrare optional; omit them to disable streaming output (output bytes are still captured and returned).WithTTY(true)runs the agent inside a pseudo-terminal for CLIs that require one.
package main
import (
"context"
"log"
"os"
"github.com/metalagman/ainvoke"
)
func main() {
runDir := "./run"
_ = os.MkdirAll(runDir, 0o755)
const inputSchema = `{
"type":"object",
"properties":{
"input":{"type":"string"}
},
"required":["input"]
}`
const outputSchema = `{
"type":"object",
"properties":{
"output":{"type":"string"}
},
"required":["output"]
}`
inv := ainvoke.Invocation{
RunDir: runDir,
SystemPrompt: `Output "Hello, <input>!" in the output field.`,
Input: map[string]any{
"input": "Ada",
},
InputSchema: inputSchema,
OutputSchema: outputSchema,
}
cfg := ainvoke.AgentConfig{
Cmd: []string{"./my-agent-binary"},
}
runner, err := ainvoke.NewRunner(cfg)
if err != nil {
log.Fatal(err)
}
stdout, _ := os.Create("stdout.log")
stderr, _ := os.Create("stderr.log")
defer stdout.Close()
defer stderr.Close()
_, _, _, err = runner.Run(
context.Background(),
inv,
ainvoke.WithStdout(stdout),
ainvoke.WithStderr(stderr),
)
if err != nil {
log.Fatal(err)
}
}The ADK provides utilities for building agent integrations, including the ExecAgent for executing external commands.
The ExecAgent executes external commands and manages their input/output according to the ainvoke protocol.
The NewExecAgent constructor uses functional options with automatic validation:
import "github.com/metalagman/ainvoke/adk"
import "time"
// Minimal configuration
agent, err := adk.NewExecAgent(
"MyAgent", // name (required)
"Description of my agent", // description (required)
[]string{"my-agent-binary"}, // cmd (required)
)
// With functional options
agent, err := adk.NewExecAgent(
"MyAgent",
"Description of my agent",
[]string{"my-agent-binary"},
adk.WithExecAgentPrompt("Custom system prompt"),
adk.WithExecAgentUseTTY(true),
adk.WithExecAgentTimeout(30*time.Second),
adk.WithExecAgentRunDir("./work-dir"),
adk.WithExecAgentExtraArgs("--verbose", "--debug"),
adk.WithExecAgentInputSchema(`{"type":"string"}`),
adk.WithExecAgentOutputSchema(`{"type":"string"}`),
)WithExecAgentPrompt(string)- Set system promptWithExecAgentExtraArgs(...string)- Add command arguments (variadic)WithExecAgentUseTTY(bool)- Enable/disable pseudo-terminalWithExecAgentTimeout(time.Duration)- Set execution timeoutWithExecAgentInputSchema(string)- Override input JSON schemaWithExecAgentOutputSchema(string)- Override output JSON schemaWithExecAgentRunDir(string)- Set custom working directory
The following example shows how to create a standalone CLI agent using ExecAgent and the standard ADK launcher. This makes the agent fully compatible with ainvoke and other ADK-compliant tools.
package main
import (
"context"
"log"
"os"
"github.com/metalagman/ainvoke/adk"
"google.golang.org/adk/agent"
"google.golang.org/adk/cmd/launcher"
"google.golang.org/adk/cmd/launcher/full"
)
func main() {
// 1. Create the ExecAgent
// This wraps the codex CLI in exec mode.
myAgent, err := adk.NewExecAgent(
"CodexAssistant",
"A codex-backed assistant agent",
[]string{"codex", "exec", "--sandbox", "workspace-write"},
adk.WithExecAgentInputSchema(`{"type":"object","properties":{"input":{"type":"string"}},"required":["input"]}`),
adk.WithExecAgentOutputSchema(`{"type":"object","properties":{"output":{"type":"string"}},"required":["output"]}`),
)
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}
// 2. Configure the ADK launcher
config := &launcher.Config{
AgentLoader: agent.NewSingleLoader(myAgent),
}
// 3. Execute the launcher
// This provides a standard CLI interface (e.g., --input, --work-dir)
l := full.NewLauncher()
if err := l.Execute(context.Background(), config, os.Args[1:]); err != nil {
log.Fatalf("Run failed: %v", err)
}
}- Empty RunDir: Uses current working directory (
.) - Custom RunDir: Validates that the directory exists; returns an error otherwise
- Persistence: Files created in the run directory (like
input.jsonandoutput.json) are preserved after execution
The NewExecAgent constructor includes automatic validation:
- Required fields:
name,description,cmdmust be provided - Command array: Must not be empty
- All validations are performed at construction time with clear error messages
Refer to AGENTS.md for development guidelines.