@welluable/agn-cli
v0.0.10
Published
A yolo coding agent simple enough to fit in your head.
Maintainers
Readme
agn-cli
A small, inspectable coding agent for the terminal and for TypeScript/JavaScript programs.
agn runs a single prompt, calls an LLM provider, lets the model use the built-in tools, and exits. The implemented agent is intentionally simple: a message loop, an OpenAI provider, terminal rendering hooks, config loading, and tools for files and shell commands.
Yolo by design:
agncan read files, write files, patch files, and execute shell commands without confirmation prompts. Run it only in directories and environments where that is acceptable.
Implemented functionality
- Single-task CLI with
agn "<prompt>". - Interactive config setup with
agn init. - Model override flag with
--model <id>and trace file writing with--trace. - Per-run session IDs printed at the end of CLI runs and injected into the agent system prompt.
- Skill commands for listing discovered skills and creating project/global skills.
- Programmatic API exporting
Agent,OpenAIProvider, and shared TypeScript types. - Agent loop that sends messages to a provider, executes requested tool calls, appends tool results, and repeats until the provider returns no tool calls or the max iteration count is reached.
- Built-in tools:
read_file,write_file,patch,shell, andread_skill. - Skills system with internal skills, auto-discovery, and explicit loading from global (
~/.agn/skills/) and project-level (.agn/skills/) directories. - OpenAI chat completions provider with streaming response handling and function/tool-call support.
- Terminal renderer hooks that stream assistant text and print tool call/result summaries.
- Config resolution from environment variables and
~/.agn/config.yml.
Future plans
Planned features and areas of exploration include:
- Additional providers, starting with Anthropic support in the CLI/runtime path.
- Review mode for inspecting planned file writes, patches, and shell commands before applying them.
- Sandbox mode for executing tasks in an isolated disposable environment and reviewing a diff before applying changes.
- Pipe/stdin mode so prompts and input can be composed with standard Unix tools.
- Structured output support for JSON-schema-like results that scripts can parse and branch on reliably.
- Improved orchestration examples for CI, cron jobs, git hooks, migrations, and multi-step workflows.
Installation
npm install -g @welluable/agn-cliOr run without installing globally:
npx @welluable/agn-cli "list the files in this project"Requires Node.js 18 or newer.
Quick start
Configure provider settings:
agn initThen run a task:
agn "find all TODO comments and summarize them by file"Override the configured/default model for one run:
agn --model gpt-4.1-mini "read package.json and explain the scripts"List discovered skills or create a new project skill:
agn skills list
agn skill new my-skill --description "Knows how to do X"CLI usage
agn [--model <id>] [--trace] "<prompt>"
agn --version
agn -v
agn [--model <id>] [--trace] init
agn [--model <id>] [--trace] skills list
agn [--model <id>] [--trace] skill new <name> [--description "..."] [--global|--project]Examples:
agn "rename all .jpeg files in this folder to .jpg"
agn "run npm test and fix any failures"
agn "read package.json and explain the available scripts"
agn "curl https://example.com/health and tell me the status"The CLI streams assistant text to stdout. When tools run, it prints a compact tool block with the tool name, a short argument summary, and up to 20 lines of tool output. Each non-version CLI invocation generates an in-memory session ID and prints Session ID: <id> before exiting. Prompt runs also include the session ID in the agent system prompt. agn --version and agn -v print the package version and do not create a session ID. With --trace, prompt runs print the trace file path that corresponds to the current session ID under ~/.agn/traces/ and write a markdown trace file containing the run metadata block and JSON message/tool-call history.
agn skills list prints a box table with discovered internal, global, and project skills. agn skill new runs the built-in create-skill skill through the agent loop to create a new skill under .agn/skills/<name>/ by default, or under ~/.agn/skills/<name>/ with --global.
Exit codes:
| Exit code | Meaning |
| --- | --- |
| 0 | The agent finished with status done. |
| 1 | Missing prompt/config/API key, unsupported provider, provider error, max iterations reached, or another runtime error. |
Configuration
agn init writes a YAML config file to:
~/.agn/config.ymlExample:
provider: openai
model: gpt-4.1
api_key: sk-...Environment variables are also supported:
| Variable | Description |
| --- | --- |
| AGN_PROVIDER | Provider name. Currently only openai is implemented. |
| AGN_MODEL | Default model identifier. |
| AGN_API_KEY | API key passed to the provider. |
Resolution order implemented by the CLI:
| Setting | Resolution order |
| --- | --- |
| Provider | AGN_PROVIDER → ~/.agn/config.yml → openai |
| Model | --model → AGN_MODEL → ~/.agn/config.yml → gpt-4.1 |
| Trace mode | --trace flag only |
| API key | AGN_API_KEY → ~/.agn/config.yml |
If no API key is resolved, the CLI exits with an error.
agn init displays OpenAI and Anthropic choices and can write either provider name to the config file. The current run path only constructs an OpenAI provider; any other provider value exits with “Provider "..." is not implemented yet.”
Programmatic usage
import { Agent, OpenAIProvider } from '@welluable/agn-cli'
const agent = new Agent({
provider: new OpenAIProvider({
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-4.1-mini',
}),
hooks: {
onText: (delta) => process.stdout.write(delta),
onToolCall: (name, args) => console.log('\nTool:', name, args),
onToolResult: (name, result) => console.log('\nResult:', name, result),
},
})
const result = await agent.run('list all files in the current directory')
console.log(result.status)
console.log(result.iterations)agent.run() accepts a prompt and an optional max-iteration override:
const result = await agent.run('migrate the small utility files to TypeScript', {
maxIterations: 50,
})It returns:
interface RunResult {
content: string
iterations: number
status: 'done' | 'max_iterations' | 'error'
messages: Message[]
}Agent options
interface AgentOptions {
provider: Provider
hooks?: AgentHooks
skills?: string | string[]
}| Option | Description |
| --- | --- |
| provider | An LLM provider implementing the Provider interface. |
| hooks | Optional callbacks for observability (streaming, tool calls, iteration events). |
| skills | Optional skill name(s) to pre-load into the system prompt. When omitted, the agent auto-discovers all available skills and builds an index the LLM can load at runtime via read_skill. When provided, the loaded skills are wrapped in mandatory instructions telling the model to follow them and not call read_skill for them. |
Agent hooks
The Agent constructor accepts these optional hooks:
interface AgentHooks {
onToolCall?: (name: string, args: Record<string, unknown>) => void
onToolResult?: (name: string, result: string) => void
onText?: (delta: string) => void
onIterationStart?: (index: number) => void
onIterationEnd?: (index: number) => void
}OpenAIProvider options
new OpenAIProvider({
apiKey: 'sk-...',
model: 'gpt-4.1-mini',
baseUrl: 'https://example-compatible-endpoint/v1', // optional
})baseUrl is optional and is passed to the OpenAI SDK as baseURL.
How it works
The core loop is implemented in src/agent.ts:
- Build the system prompt: a base instruction, an optional session ID note when
global.sessionIdis set, and a skills section (either an auto-discovered index or explicitly loaded skill content). - Start a new message list with the system prompt and the user prompt.
- Call
provider.chat(messages, DEFAULT_TOOLS, { onText }). - Append the assistant response to the message history.
- If the assistant requested tool calls, execute those tool calls.
- Append each tool result as a
toolmessage. - Repeat until no tool calls are returned, an error occurs, or
maxIterationsis reached.
Tool calls returned in the same assistant response are executed concurrently with Promise.all.
The default maximum iteration count is 30. Each call to agent.run() starts a fresh message history; conversation history is returned in the result but is not carried into later runs automatically.
Built-in tools
The default tool definitions and handlers are implemented in src/tools.ts.
| Tool | Arguments | Implemented behavior |
| --- | --- | --- |
| read_file | { path } | Reads a UTF-8 file and returns its contents, or an error string. |
| write_file | { path, content } | Creates parent directories as needed, writes UTF-8 content, overwrites existing files, and returns a byte-count message or error string. |
| patch | { path, old_string, new_string } | Reads a UTF-8 file, replaces the first exact occurrence of old_string with new_string, writes the updated file, and returns a status/error string. |
| shell | { command } | Runs command with Node's child_process.exec and returns stdout plus stderr, or the error message if there is no output. |
| read_skill | { name } | Loads a skill by name from the skills directories and returns the full content including supporting markdown files. |
The toolset is fixed — you can't pass in additional tools. The 5 tools are general enough to handle any task. The way to extend the agent is skills, not more tools.
Skills
Skills are markdown files (SKILL.md) that provide domain-specific knowledge to the agent. They live in three locations:
- Internal: bundled skills copied to
dist/skills/, such ascreate-skill - Global:
~/.agn/skills/<skill-name>/SKILL.md— available in every project - Project:
.agn/skills/<skill-name>/SKILL.md— project-specific, overrides global and internal skills with the same directory name
Each skill directory contains a SKILL.md file with YAML frontmatter (name, description) and markdown content. Supporting .md files in the same directory, including nested markdown files, are bundled automatically when the skill is loaded.
How skills work
When agn runs, it scans internal, global, and project skill directories and builds an index of available skills. The index is injected into the system prompt so the LLM knows what skills exist. The LLM can then load any skill at runtime using the read_skill tool.
Skills are resolved by directory name or by the name field in the YAML frontmatter. Precedence by directory name is internal < global < project.
Creating a skill
Use the CLI to create a project skill:
agn skill new my-skill --description "Knows how to do the specific thing"Create a global skill instead:
agn skill new my-skill --description "Knows how to do the specific thing" --globalOr create the files manually:
~/.agn/skills/
my-skill/
SKILL.md
reference.md # optional supporting file---
name: my-skill
description: Knows how to do the specific thing
---
# My Skill
Instructions for the agent...Explicit skill loading
Pass skill names to the Agent constructor to pre-load them directly into the system prompt, bypassing auto-discovery. Explicitly loaded skills are mandatory instructions for that run; the system prompt tells the model to follow them and not call read_skill for them.
const agent = new Agent({
provider,
skills: 'my-skill', // single skill
})
const agent = new Agent({
provider,
skills: ['skill-a', 'skill-b'], // multiple skills
})See docs/Skills.md for the full skill file format, precedence rules, CLI commands, and environment overrides.
Provider interface
The shared provider interface is implemented in src/types.ts:
interface Provider {
chat(
messages: Message[],
tools: ToolDefinition[],
options?: { onText?: (delta: string) => void }
): Promise<ChatResponse>
}The included provider implementation is OpenAIProvider in src/providers/openai.ts. It sends chat completion requests with stream: true, maps internal tool definitions to OpenAI function tools, accumulates streamed text deltas, assembles streamed tool-call chunks, and returns { content, tool_calls }.
Repository layout
src/
agent.ts Agent loop, run result, hooks, skills integration
cli.ts Command-line entrypoint and argument parsing
config.ts Config file/env/default resolution
init.ts Interactive config writer
providers/openai.ts OpenAI provider implementation
renderer.ts Terminal rendering hooks
session.ts In-memory per-run session ID generation
skills.ts Skill discovery, loading, and index building
tools.ts Built-in tool definitions and handlers
types.ts Shared Provider/message/tool types
version.ts Auto-generated version constant
index.ts Package exports
docs/
Agent.md
Cli.md
Provider.md
Skills.md
examples/
openai.ts Programmatic OpenAI exampleLocal development
Clone the repository and install dependencies:
git clone https://github.com/welluable/agn-cli.git
cd agn-cli
npm installBuild TypeScript:
npm run buildRun tests:
npm testRun the CLI locally after building:
node dist/cli.js "list files in this repository"
node dist/cli.js skills listRun the OpenAI example:
OPENAI_API_KEY=sk-... npm run example -- examples/openai.tsSafety notes
agndoes not ask before modifying files or running commands.- Prefer using it in a clean git working tree so you can inspect or revert changes.
- Avoid running it with unnecessary credentials in the environment.
- Do not commit API keys or local
.envfiles. - The
shelltool uses your system shell throughchild_process.execand inherits the permissions and environment of the current process.
Implemented limitations
- The CLI can run only the OpenAI provider.
- The toolset is fixed at 5 built-in tools (
read_file,write_file,patch,shell,read_skill). Custom tools cannot be added; extend with skills instead. - There is no confirmation prompt before file writes, patches, or shell commands.
- There is no sandboxing around shell commands or filesystem access.
- The CLI accepts a prompt as command-line arguments; it does not read prompt text from stdin.
--tracewrites trace files only for prompt runs, not forinit,skills list, orskill new.- Each
agent.run()call starts a new conversation.
License
MIT © Welluable
