agenttk
v0.1.4
Published
A TypeScript toolkit for building deterministic, agent-friendly CLIs.
Maintainers
Readme
AgentTK
AgentTK is a small TypeScript toolkit for building deterministic, agent-friendly CLIs. It is a framework for agent-authored CLIs, not a built-in CLI generator.
It gives you a clean core for tools that need:
- deterministic command dispatch and built-in help
- structured success and failure envelopes
- JSON-first output for agents
- concise human output for operators
- reusable auth, lookup, adapter, and config patterns
- dry-run semantics for mutation commands
- lightweight test helpers for CLI behavior
What it is
AgentTK is a narrow toolkit for building reliable CLIs with a stable machine-facing contract.
It is designed for a workflow like this:
- an agent reads a skill, API, or integration contract
- the agent writes the actual CLI code
- AgentTK supplies the reusable plumbing so the tool does not have to reinvent it
Use it when you want:
- predictable command dispatch with alias and help support
- machine-readable results without string parsing
- simple human output layered on top of the same result envelope
- reusable framework blocks for auth, lookup, config, and adapter contracts
- small test helpers instead of shell-heavy CLI tests
What it is not
AgentTK intentionally does not include:
- a built-in CLI generator
- plugin systems
- workflow engines
- auth doctor flows
- provenance helpers
- dynamic runtime loading
- provider SDK wrappers
- domain-specific adapters
Product boundary
AgentTK owns
- runtime and command structure
- help metadata and rendering
- result envelopes
- validation and corrective guidance
- dry-run semantics
- auth, lookup, adapter, and config primitives
- test helpers
Your agent owns
- reading the skill or API contract
- deciding the command surface
- writing the actual tool code
- composing the AgentTK primitives correctly
The downstream tool repo owns
- provider integrations
- business rules
- safety constraints
- domain quirks
- live operational behavior
Install
npm install agenttkIf you want schema validation:
npm install zodExample
import { createTool, defineCommand, ok } from 'agenttk'
const tool = createTool({
name: 'demo',
description: 'Example CLI built with AgentTK',
commands: [
defineCommand({
name: 'hello',
description: 'Say hello',
aliases: ['hi'],
usage: 'demo hello [name]',
examples: ['demo hello', 'demo hi Dushyant'],
handler: async ({ rawArgs }) =>
ok({
type: 'greeting',
record: { message: `hello, ${rawArgs[0] ?? 'world'}` }
})
})
]
})
await tool.run(process.argv.slice(2))Validation + dry-run
import { z } from 'zod'
import { asDryRun, createTool, defineCommand, isFailure, ok, validateInput } from 'agenttk'
const schema = z.object({
title: z.string().min(1, 'title is required'),
dryRun: z.boolean().default(false)
})
const tool = createTool({
name: 'tasks',
commands: [
defineCommand({
name: 'add',
handler: async () => {
const parsed = validateInput(schema, { title: '', dryRun: true }, {
nextStep: 'Run tasks add --title "Send estimate" [--dry-run]'
})
if (isFailure(parsed)) return parsed
const result = ok({
type: 'task',
id: 'task-123',
destination: 'demo_tasks',
record: { title: parsed.title }
})
return parsed.dryRun ? asDryRun(result) : result
}
})
]
})API
Core runtime
createTooldefineCommandokfailisFailurerenderResult
Validation and command behavior
validateInputvalidationErrorexpectedPayloadShapenextStepGuidanceasDryRun
Auth blocks
authRequiredauthInvalidaccountMismatchrequireAuth
Lookup resolution
notFoundambiguousMatchresolveByIdresolveByQueryresolveOne
Adapter contracts
defineAdaptersupportsCapabilityrequireCapabilityadapterFailureunsupportedCapability
Config conventions
defineProfileloadConfigvalidateConfigmissingConfigmalformedConfigselectProfile
Testing helpers
runToolexpectOkexpectFailureexpectDryRunexpectAuthFailureexpectLookupFailureexpectAdapterFailureexpectConfigFailureauthFailureFixturelookupCandidatesFixturefakeAdapter
Auth preflight
import { createTool, defineCommand, ok, requireAuth } from 'agenttk'
const tool = createTool({
name: 'calendar',
commands: [
defineCommand({
name: 'sync',
handler: async () => {
const auth = await requireAuth(async () => ({
ok: false,
code: 'AUTH_REQUIRED',
provider: 'google',
nextStep: 'Run calendar auth login'
}))
if (auth !== true) return auth
return ok({ type: 'sync', record: { status: 'started' } })
}
})
]
})Adapter contracts
import {
adapterFailure,
createTool,
defineAdapter,
defineCommand,
requireCapability
} from 'agenttk'
const adapter = defineAdapter({
provider: 'google',
capabilities: ['tasks.read']
})
const tool = createTool({
name: 'tasks',
commands: [
defineCommand({
name: 'add',
handler: async () => {
const capability = requireCapability(adapter, 'tasks.write', {
operation: 'createTask',
nextStep: 'Reconnect with write scopes'
})
if (capability !== true) return capability
return adapterFailure('Google API timed out', {
provider: 'google',
operation: 'createTask',
category: 'timeout',
retryable: true,
nextStep: 'Retry in a few seconds'
})
}
})
]
})Provider-specific SDK calls and object models should stay in the downstream repo. AgentTK only owns the contract boundary, capability checks, and normalized failure shapes.
Config conventions
import { createTool, defineCommand, isFailure, loadConfig, ok } from 'agenttk'
import { z } from 'zod'
const schema = z.object({
account: z.string().min(1),
apiBaseUrl: z.string().url()
})
const config = loadConfig(schema, {
env: { apiBaseUrl: process.env.API_BASE_URL },
profiles: {
work: { account: '[email protected]', apiBaseUrl: 'https://api.example.com' }
},
profile: 'work'
}, {
expected: '{"account":"[email protected]","apiBaseUrl":"https://api.example.com"}',
nextStep: 'Set API_BASE_URL or choose a valid profile'
})
if (isFailure(config)) return config
return ok({ type: 'config', record: config })Load order is predictable: base config, then selected profile, then env overrides. AgentTK stays out of secrets management and provider-specific config policy.
Lookup resolution
import { ambiguousMatch, createTool, defineCommand, notFound, ok, resolveOne } from 'agenttk'
const tool = createTool({
name: 'tasks',
commands: [
defineCommand({
name: 'pick',
handler: async () => {
const matches = [
{ id: 'task-1', label: 'Invoice follow-up', description: 'Daily Focus' },
{ id: 'task-2', label: 'Invoice draft', description: 'Backlog' }
]
const resolved = resolveOne({ query: 'invoice' }, matches, {
nextStep: 'Retry with an explicit id'
})
if (!resolved.ok) return resolved
return ok({ type: 'task', id: resolved.candidate.id, record: resolved.candidate })
}
})
]
})Testing helpers
Use the richer assertions when you want cheap confidence around framework primitives, not shell-heavy rechecks of every field by hand.
import test from 'node:test'
import { authFailureFixture, expectAuthFailure, requireAuth } from 'agenttk'
test('auth failure stays structured', async () => {
const auth = await requireAuth(authFailureFixture({
code: 'ACCOUNT_MISMATCH',
currentAccount: '[email protected]',
expectedAccount: '[email protected]'
}))
expectAuthFailure(auth, {
code: 'ACCOUNT_MISMATCH',
provider: 'google',
currentAccount: '[email protected]',
expectedAccount: '[email protected]'
})
})Keep using plain runTool(...) tests for command wiring and output mode checks. Use the richer helpers when you want fast, data-first checks around auth, lookup, dry-run, adapter, and config behavior.
Examples
examples/minimal-cli/- basic runtime, help, and JSON outputexamples/tasks-cli/- validation, dry-run, and command-style mutationsexamples/adapter-cli/- capability checks and normalized adapter failuresexamples/config-cli/- profile selection and config diagnosticsexamples/testing-fixtures/- richer framework-aware test assertions
node examples/minimal-cli/index.mjs hello --json
node examples/minimal-cli/index.mjs help
node examples/tasks-cli/index.mjs add --title "Send estimate" --dry-run --json
node examples/tasks-cli/index.mjs add
node examples/adapter-cli/index.mjs status --json
node examples/config-cli/index.mjs show --profile workDevelopment
npm run build
npm test
npm run examples:smoke
npm run verifyChangelog
See CHANGELOG.md.
Release discipline
For normal releases:
npm version patch
git push origin main --follow-tagsRules:
- release only from a clean
main - let CI verify before trusting the tag
- keep
package.json, npm, and GitHub releases aligned - document user-visible changes in
CHANGELOG.md
