Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
85d238a
fix: normalize object validation errors
0xpolarzero May 22, 2026
832671a
fix(cli): preserve stream terminal records
0xpolarzero May 26, 2026
ea85131
fix: add stream terminal changeset
0xpolarzero May 27, 2026
3d8b4ca
fix: reuse cli skill projection
0xpolarzero May 27, 2026
375c0eb
chore: add changeset for skill projection fix
0xpolarzero May 27, 2026
da28b04
feat: add typed client runtime foundation
0xpolarzero May 25, 2026
6a71ed4
fix: keep runtime foundation typegen output unchanged
0xpolarzero May 26, 2026
0e0fac3
test: harden streaming duration snapshots
0xpolarzero May 26, 2026
4972267
refactor: flatten client transport capabilities
0xpolarzero May 26, 2026
f528bdb
refactor: remove unused transport context
0xpolarzero May 26, 2026
aca3ab7
test: keep client runtime OpenAPI fixture scoped
0xpolarzero May 26, 2026
5f92ad8
refactor(client): namespace transport modules
0xpolarzero May 26, 2026
a548dc6
test(client): expand transport route coverage
0xpolarzero May 26, 2026
b51681e
refactor(client): split request discover local runtimes
0xpolarzero May 27, 2026
2499cfa
refactor(client): wrap local runtime capability
0xpolarzero May 27, 2026
e2e6687
refactor(client): inline runtime methods
0xpolarzero May 27, 2026
93af911
refactor(client): reuse cli command tree internals
0xpolarzero May 27, 2026
ba15725
refactor(client): rename runtime context module
0xpolarzero May 27, 2026
1afb8d5
refactor(client): move request status mapping
0xpolarzero May 27, 2026
d3bd9e3
fix(client): expose rpc output metadata
0xpolarzero May 27, 2026
c7dc375
fix(client): share rendered output default
0xpolarzero May 27, 2026
8d78ee4
fix(client): canonicalize runtime client contracts
0xpolarzero May 27, 2026
60a8029
fix(client): preserve HTTP transport error metadata
0xpolarzero May 27, 2026
dc1cd11
refactor(client): share structured command collection
0xpolarzero May 27, 2026
2f1d7f9
fix(typegen): emit exact optional property types
0xpolarzero May 27, 2026
4e8a8a8
fix(typegen): emit command output metadata
0xpolarzero May 27, 2026
4eede40
fix: reuse cli discovery projection
0xpolarzero May 27, 2026
0be074c
fix(client): preserve runtime cli metadata
0xpolarzero May 27, 2026
a1acb15
refactor(client): rename request and discover type namespaces
0xpolarzero May 27, 2026
f00ba52
refactor(client): align internal handler names
0xpolarzero May 27, 2026
1147a6d
test(client): cover internal handlers
0xpolarzero May 27, 2026
3c9e469
refactor(client): rename local methods type
0xpolarzero May 27, 2026
c5d2832
refactor(client): expose rpc error type
0xpolarzero May 27, 2026
20fc913
chore(client): add client path alias
0xpolarzero May 27, 2026
3085711
feat: add typed client public surface
0xpolarzero May 25, 2026
d61b412
fix: tighten typed client typegen surface
0xpolarzero May 26, 2026
83d307c
docs: remove generated command jsdoc claims
0xpolarzero May 26, 2026
9d17b44
fix: align client actions with flattened transports
0xpolarzero May 26, 2026
d5e5c32
refactor: remove unused client uid
0xpolarzero May 26, 2026
6e33ae4
refactor: keep public surface typegen scoped
0xpolarzero May 26, 2026
1292b72
docs: remove docs folder
0xpolarzero May 27, 2026
fcd5f69
fix: align typed client contracts
0xpolarzero May 27, 2026
e106c39
fix(client): consume rpc output metadata
0xpolarzero May 27, 2026
6e463a8
fix(client): consume canonical runtime contracts
0xpolarzero May 27, 2026
cf00c85
fix(typegen): keep public surface scoped
0xpolarzero May 27, 2026
1ca0972
refactor(client): namespace public client surface
0xpolarzero May 27, 2026
db86e38
refactor(client): compose action sets
0xpolarzero May 27, 2026
1a30a9d
refactor(client): organize public action types
0xpolarzero May 27, 2026
6ce7da3
test(client): remove api example type test
0xpolarzero May 27, 2026
a2cd491
test(client): update type assertions
0xpolarzero May 27, 2026
a556fee
test(client): expand client type coverage
0xpolarzero May 27, 2026
1fd5dfd
test: use real client paths in client tests
0xpolarzero May 27, 2026
513d3f5
refactor: remove useless AnyCli abstraction
0xpolarzero May 27, 2026
b441dc7
refactor: refine MemoryClient overloads
0xpolarzero May 27, 2026
2cc49f1
docs: add TypeScript client skill
0xpolarzero May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quiet-walls-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'incur': patch
---

Fixed HTTP and MCP command input validation to return standard validation field errors for object-shaped inputs.
7 changes: 7 additions & 0 deletions .changeset/sour-dingos-shine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'incur': patch
---

Fixed streaming command terminal records so HTTP NDJSON responses preserve returned `c.ok()` CTA metadata, represent returned or yielded `c.error()` values as terminal errors, include terminal duration metadata, and unwind generators on response cancellation.

Also preserves `IncurError.retryable` metadata in streaming machine-format errors.
7 changes: 7 additions & 0 deletions .changeset/tame-pillows-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'incur': patch
---

Fixed generated and synced skills to use the same command projection as CLI skill output.

`Skillgen` and `SyncSkills` now avoid generating duplicate skills for command aliases, preserve output schemas and examples consistently, and include the fetch gateway skill hint for fetch-based commands.
185 changes: 184 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
</p>

<p align="center">
<a href="#features">Features</a> · <a href="#quickprompt">Quickprompt</a> · <a href="#install">Install</a> · <a href="#usage">Usage</a> · <a href="#walkthrough">Walkthrough</a> · <a href="#license">License</a>
<a href="#features">Features</a> · <a href="#quickprompt">Quickprompt</a> · <a href="#install">Install</a> · <a href="#usage">Usage</a> · <a href="#typescript-client">TypeScript Client</a> · <a href="#walkthrough">Walkthrough</a> · <a href="#license">License</a>
</p>

## Features
Expand Down Expand Up @@ -411,6 +411,189 @@ POST /mcp { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "user

Non-`/mcp` paths continue routing to the command API as usual.

## TypeScript Client

Use the TypeScript client when another TypeScript program needs to call an incur CLI with typed commands, structured data, streaming, CTAs, and discovery resources. Use the CLI directly for shell workflows, Skills for agent discovery, and MCP when the caller is an MCP-capable agent.

### Generate Command Types

Export the CLI instance from your entrypoint:

```ts
import { Cli, z } from 'incur'

const cli = Cli.create('acme', {
description: 'Acme operations CLI',
}).command('project status', {
args: z.object({ projectId: z.string() }),
output: z.object({ status: z.enum(['ok', 'blocked']) }),
run(c) {
return { status: 'ok' as const }
},
})

cli.serve()

export default cli
```

Generate the command map:

```sh
npx incur gen --entry ./src/cli.ts --output ./src/incur.generated.ts
```

Import the generated type where you create clients:

```ts
import { HttpClient, MemoryClient } from 'incur/client'
import type { Commands } from './incur.generated.js'
```

`incur gen` also augments `incur` and `incur/client`, so clients can use registered command types without explicit generics after the generated file is included by TypeScript.

### HTTP Client

Serve the CLI with `cli.fetch`, then call it with `HttpClient.create<Commands>()`:

```ts
import { HttpClient } from 'incur/client'
import type { Commands } from './incur.generated.js'

const client = HttpClient.create<Commands>({
baseUrl: 'https://ops.acme.test',
headers: { authorization: `Bearer ${token}` },
outputFormat: 'toon',
})

const status = await client.run('project status', {
args: { projectId: 'proj_web_2026' },
})

status.data.status
// ^? 'ok' | 'blocked'
```

`HttpClient` talks to the served CLI's `/_incur/rpc` endpoint for command runs and `/_incur/*` resource endpoints for discovery. You normally should not call those lower-level endpoints directly.

### Memory Client

Use `MemoryClient.create(cli)` for in-process callers, tests, local automation, and tools that need local-only actions:

```ts
import { MemoryClient } from 'incur/client'
import cli from './cli.js'

const client = MemoryClient.create(cli, {
env: { ACME_TOKEN: 'dev_secret_123' },
})

const result = await client.run('project status', {
args: { projectId: 'proj_web_2026' },
})
```

Memory clients infer commands directly from a concrete CLI. They also expose filesystem actions that HTTP clients intentionally do not expose:

```ts
await client.skills.list()
await client.skills.add({ global: true })
await client.mcp.add({ agents: ['codex'] })
```

### Running Commands

`client.run(command, input)` mirrors CLI invocation:

```ts
const report = await client.run('project report', {
args: { projectId: 'proj_web_2026' },
options: { includeClosed: false },
selection: ['summary', 'items[0:3]', 'nextCursor'],
outputFormat: 'md',
outputTokenCount: true,
outputTokenLimit: 128,
})
```

The result contains typed structured data, optional rendered output text, and metadata:

```ts
report.ok
report.data
report.output?.text
report.output?.next
report.meta.cta
```

`selection` is equivalent to `--filter-output`. Because it changes the shape of `data`, selected results are typed as `unknown`. Pass `selection: undefined` on a call to clear a client-level default and recover the full output type.

### Streaming

Commands implemented with `async *run` return a stream wrapper:

```ts
const stream = await client.run('logs tail', {
args: { service: 'checkout-api' },
})

for await (const line of stream) {
console.log(line)
}

const final = await stream.final
```

Use `stream.records()` when you need raw chunk, done, and error records. A stream can be consumed once: either chunks, records, or final-only consumption.

### CTAs and Errors

CTAs returned by commands are runnable from the client:

```ts
const cta = report.meta.cta?.commands[0]
if (cta) {
console.log(cta.cliCommand)
const next = await cta.run({ outputFormat: 'toon' })
}
```

Failed command runs throw `Client.ClientError`:

```ts
import { Client } from 'incur/client'

try {
await client.run('project deploy', {
args: { projectId: 'proj_web_2026' },
options: { environment: 'production' },
})
} catch (error) {
if (error instanceof Client.ClientError) {
console.error(error.code, error.status, error.retryable)
console.error(error.meta?.cta)
}
}
```

### Discovery Resources

Clients can read the same discovery surfaces agents use:

```ts
await client.llms()
await client.llms({ command: 'project', format: 'md' })
await client.llmsFull()
await client.schema('project report')
await client.help('project report')
await client.openapi()
await client.skills.index()
await client.skills.get('deploy')
await client.mcp.tools()
```

Use these resource actions for documentation, SDK tooling, agent setup, tests, and UI generation. Use `client.run()` for actual command execution.

## Walkthrough

### Agent discovery
Expand Down
36 changes: 34 additions & 2 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -965,13 +965,45 @@ async *run({ ok }) {

## Type Generation

Generate type definitions for your CLI's command map to get typed CTAs:
Generate type definitions for your CLI's command map:

```sh
incur gen
```

This creates a `incur.generated.ts` file that registers your commands on the `Cli.Commands` type, enabling autocomplete on CTA command names, args, and options.
The CLI entrypoint must `export default cli` so `incur gen` can import it. The generated file exports `Commands` and augments both `incur` and `incur/client`, enabling typed CTAs while authoring a CLI and typed TypeScript clients when consuming one.

```ts
import { HttpClient } from 'incur/client'
import type { Commands } from './incur.generated.js'

const client = HttpClient.create<Commands>({ baseUrl: 'https://ops.acme.test' })
```

## TypeScript Client

Use `incur/client` when TypeScript code needs to consume an incur CLI programmatically. Prefer normal CLI commands for shell workflows, Skills for agent usage, and MCP for MCP-capable agents.

```ts
import { HttpClient, MemoryClient } from 'incur/client'
import cli from './cli.js'
import type { Commands } from './incur.generated.js'

const http = HttpClient.create<Commands>({
baseUrl: 'https://ops.acme.test',
outputFormat: 'toon',
})

const memory = MemoryClient.create(cli, {
env: { ACME_TOKEN: 'dev_secret_123' },
})

const result = await http.run('project status', {
args: { projectId: 'proj_web_2026' },
})
```

Use the dedicated `incur-typescript-client` skill for exhaustive client usage: `HttpClient`, `MemoryClient`, lower-level transports, `client.run`, streaming, CTAs, `ClientError`, discovery resources, and memory-only local actions.

## Full Example

Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"examples",
"dist",
"src",
"skills",
"SKILL.md"
],
"dependencies": {
Expand Down Expand Up @@ -70,6 +71,11 @@
"types": "./dist/index.d.ts",
"src": "./src/index.ts",
"default": "./dist/index.js"
},
"./client": {
"types": "./dist/client/index.d.ts",
"src": "./src/client/index.ts",
"default": "./dist/client/index.js"
}
}
}
Loading