@moikapy/lich 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -1
- package/README.md +22 -6
- package/dist/cli.d.ts +1 -2
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/{tui-V7ATLIKW.js → tui-K3EPRXTV.js} +5 -2
- package/dist/tui-K3EPRXTV.js.map +1 -0
- package/docs/.vitepress/config.mts +1 -0
- package/docs/architecture/extending.md +5 -2
- package/docs/architecture/overview.md +1 -1
- package/docs/architecture/plugins.md +1 -1
- package/docs/design/council/architecture-review-r2.md +36 -0
- package/docs/design/council/architecture-review-r3.md +69 -0
- package/docs/design/council/index.md +15 -0
- package/docs/design/council/security-review-r2.md +32 -0
- package/docs/design/council/security-review-r3.md +19 -0
- package/docs/design/council/simplicity-review-r2.md +35 -0
- package/docs/design/council/simplicity-review-r3.md +22 -0
- package/docs/design/self-improvement-loop.md +160 -162
- package/docs/getting-started.md +37 -15
- package/docs/index.md +8 -8
- package/docs/user-guide/cli.md +5 -5
- package/docs/user-guide/gateway.md +10 -10
- package/docs/user-guide/library.md +12 -8
- package/docs/user-guide/plugins.md +3 -3
- package/docs/user-guide/tui.md +3 -3
- package/package.json +4 -2
- package/dist/tui-V7ATLIKW.js.map +0 -1
package/docs/user-guide/cli.md
CHANGED
|
@@ -11,7 +11,7 @@ lich tui # interactive terminal UI (ink)
|
|
|
11
11
|
lich gateway <plat..> # messaging gateway (webhook|telegram|discord|twitch)
|
|
12
12
|
lich config # print a starter config template
|
|
13
13
|
lich --help # usage text
|
|
14
|
-
lich --version # print 0.
|
|
14
|
+
lich --version # print 0.3.0
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
- **One-shot** joins all positional words into a single task, runs the agent loop, prints the final answer to stdout, and exits. Progress (turn numbers, tool results) goes to stderr.
|
|
@@ -19,7 +19,7 @@ lich --version # print 0.2.0
|
|
|
19
19
|
- **TUI** launches the ink interface. See the [TUI guide](tui.md).
|
|
20
20
|
- **Gateway** runs platform adapters (defaults to `webhook` when no platform is given). See the [Gateway guide](gateway.md). Unknown platform names are skipped with a warning; if none remain, the CLI exits `1`.
|
|
21
21
|
|
|
22
|
-
`bun src/cli.ts`
|
|
22
|
+
The installed `lich` binary and `bun src/cli.ts` (from a repository clone) accept identical arguments.
|
|
23
23
|
|
|
24
24
|
## Flags
|
|
25
25
|
|
|
@@ -157,14 +157,14 @@ jq -r 'select(.kind=="message") | "\(.message.role): \(.message.content // "(too
|
|
|
157
157
|
Review a file with a scoped working directory:
|
|
158
158
|
|
|
159
159
|
```sh
|
|
160
|
-
|
|
160
|
+
lich --work-dir ./myproject --max-turns 15 \
|
|
161
161
|
"Review src/payments/retry.ts for correctness bugs. List each with a line number and a suggested fix."
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
Web research (search, then fetch the promising pages):
|
|
165
165
|
|
|
166
166
|
```sh
|
|
167
|
-
|
|
167
|
+
lich \
|
|
168
168
|
"Find the current LTS version of Node.js using web_search, fetch the release page with fetch_url, and summarize the support schedule."
|
|
169
169
|
```
|
|
170
170
|
|
|
@@ -177,6 +177,6 @@ Batch one-shots from a script, checking each exit code:
|
|
|
177
177
|
set -u
|
|
178
178
|
for task in "summarize README.md" "list the largest files with disk_usage" "grep for TODO comments"; do
|
|
179
179
|
echo "== $task"
|
|
180
|
-
LICH_PROVIDER_KIND=ollama LICH_MODEL=llama3.2
|
|
180
|
+
LICH_PROVIDER_KIND=ollama LICH_MODEL=llama3.2 lich --max-turns 10 "$task" || echo "FAILED ($?)"
|
|
181
181
|
done
|
|
182
182
|
```
|
|
@@ -19,10 +19,10 @@ flowchart LR
|
|
|
19
19
|
Platforms: `webhook` (HTTP server), `telegram` (long-poll), `discord` (gateway WebSocket), `twitch` (IRC over WebSocket). Pick any combination:
|
|
20
20
|
|
|
21
21
|
```sh
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
lich gateway webhook # http only
|
|
23
|
+
lich gateway webhook telegram # http + telegram polling
|
|
24
|
+
lich gateway telegram discord twitch # no webhook server
|
|
25
|
+
lich gateway # defaults to webhook
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
The gateway is silent after startup: Telegram/Discord/Twitch respond only in chats, channels, or servers the bot can see or has joined, and the webhook only serves HTTP. Telegram media messages arrive as the placeholder text `media not supported yet`; other non-text events are ignored. Telegram `/start` is answered like a plain "hello".
|
|
@@ -32,7 +32,7 @@ The gateway is silent after startup: Telegram/Discord/Twitch respond only in cha
|
|
|
32
32
|
Zero configuration — the server binds `0.0.0.0:$LICH_GATEWAY_PORT` (default 8089).
|
|
33
33
|
|
|
34
34
|
```sh
|
|
35
|
-
|
|
35
|
+
lich gateway webhook
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
```sh
|
|
@@ -47,7 +47,7 @@ curl -s http://localhost:8089/health
|
|
|
47
47
|
With token auth, every POST must carry the exact `x-lich-token` header; mismatched or missing tokens get `401 {"error":"unauthorized"}`:
|
|
48
48
|
|
|
49
49
|
```sh
|
|
50
|
-
LICH_GATEWAY_TOKEN=s3cret
|
|
50
|
+
LICH_GATEWAY_TOKEN=s3cret lich gateway webhook
|
|
51
51
|
curl -s -X POST http://localhost:8089/message \
|
|
52
52
|
-H "x-lich-token: s3cret" -H "content-type: application/json" -d '{"text": "hello"}'
|
|
53
53
|
```
|
|
@@ -61,7 +61,7 @@ Payload fields (all optional except `text`): `platform` (default `"webhook"`), `
|
|
|
61
61
|
|
|
62
62
|
```sh
|
|
63
63
|
export LICH_TELEGRAM_BOT_TOKEN=123456:ABC-your-token
|
|
64
|
-
|
|
64
|
+
lich gateway telegram
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
3. Open your bot in Telegram, send a message, get a reply. Media messages arrive as the text `media not supported yet`; the bot replies from there.
|
|
@@ -78,7 +78,7 @@ Telegram uses long polling (no public URL needed). Replies split at 4096 chars.
|
|
|
78
78
|
```sh
|
|
79
79
|
export LICH_DISCORD_BOT_TOKEN=your-bot-token
|
|
80
80
|
export LICH_DISCORD_BOT_ID=123456789012345678
|
|
81
|
-
|
|
81
|
+
lich gateway discord
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
5. Send the bot a message (DM or any channel it can read — every non-bot message gets a reply); each channel has its own conversation memory (keyed by `channel_id`). Replies split at 2000 chars. Bot-authored messages are ignored (no loops).
|
|
@@ -94,7 +94,7 @@ bun src.cli.ts gateway discord
|
|
|
94
94
|
export LICH_TWITCH_OAUTH_TOKEN=oauth:abc123...
|
|
95
95
|
export LICH_TWITCH_NICK=mylichbot
|
|
96
96
|
export LICH_TWITCH_CHANNELS=channelone,channeltwo
|
|
97
|
-
|
|
97
|
+
lich gateway twitch
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
3. The bot joins `#channelone` and `#channeltwo` and replies in chat (own messages are ignored). Replies split at 512 chars; IRC PING/PONG is answered automatically.
|
|
@@ -107,7 +107,7 @@ List the platforms in one command; all configured adapters start together and sh
|
|
|
107
107
|
|
|
108
108
|
```sh
|
|
109
109
|
LICH_TELEGRAM_BOT_TOKEN=... LICH_DISCORD_BOT_TOKEN=... \
|
|
110
|
-
|
|
110
|
+
lich gateway webhook telegram discord
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
Adapters whose credentials are missing start **idle** (a warning is logged, e.g. `gateway discord adapter idle: LICH_DISCORD_BOT_TOKEN not set`) and the rest keep running — so the same command works on machines with partial credentials. If no platform name is valid, the CLI exits `1` with `gateway needs at least one valid platform`.
|
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```sh
|
|
8
|
+
npm install @moikapy/lich
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
From source instead (`dist/` is not committed, so build first — see [development install](../getting-started.md#development-install-from-source)):
|
|
8
12
|
|
|
9
13
|
```sh
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
npm install
|
|
14
|
+
git clone https://github.com/Moikapy/lich.git && cd lich
|
|
15
|
+
bun install && bun run build
|
|
16
|
+
# then, from your project: npm install <path-to>/lich
|
|
13
17
|
```
|
|
14
18
|
|
|
15
19
|
The package ships ESM (`dist/index.js`, types at `dist/index.d.ts`, binary at `dist/cli.js`); `main`/`types`/`bin` are wired in `package.json`.
|
|
@@ -19,7 +23,7 @@ The package ships ESM (`dist/index.js`, types at `dist/index.d.ts`, binary at `d
|
|
|
19
23
|
`create_agent(raw_config)` validates the config (zod, defaults applied, frozen result) and returns an `Agent` with a `.run()` loop:
|
|
20
24
|
|
|
21
25
|
```ts
|
|
22
|
-
import { create_agent } from "lich";
|
|
26
|
+
import { create_agent } from "@moikapy/lich";
|
|
23
27
|
|
|
24
28
|
const agent = create_agent({
|
|
25
29
|
providers: [{ kind: "ollama", name: "local", model: "llama3.2:latest" }],
|
|
@@ -33,7 +37,7 @@ console.log(`tokens: ${result.usage_total.total_tokens}`);
|
|
|
33
37
|
The one-liner `run_agent(config, input)` is equivalent when you only need a single run:
|
|
34
38
|
|
|
35
39
|
```ts
|
|
36
|
-
import { run_agent } from "lich";
|
|
40
|
+
import { run_agent } from "@moikapy/lich";
|
|
37
41
|
|
|
38
42
|
const result = await run_agent(
|
|
39
43
|
{ providers: [{ kind: "ollama", name: "local", model: "llama3.2:latest" }] },
|
|
@@ -162,7 +166,7 @@ Provider failures throw `ProviderError`, an `Error` subclass with `kind`, `provi
|
|
|
162
166
|
| `unknown` | Non-provider errors (e.g. tool crashes surfaced as strings). | Treated as fatal for the provider. |
|
|
163
167
|
|
|
164
168
|
```ts
|
|
165
|
-
import { ProviderError } from "lich";
|
|
169
|
+
import { ProviderError } from "@moikapy/lich";
|
|
166
170
|
|
|
167
171
|
try {
|
|
168
172
|
await agent.run({ input: "hello" });
|
|
@@ -178,4 +182,4 @@ When every configured provider fails, the last `ProviderError` is thrown. Tool f
|
|
|
178
182
|
|
|
179
183
|
## Session access
|
|
180
184
|
|
|
181
|
-
Each `run()` appends a transcript line-by-line under `config.session_dir` (default `<work_dir>/.lich/sessions`); `result.session_path` gives the exact file. Records carry `{ts, kind: "message"|"meta", message?, meta?}`; read them with `jq` or the
|
|
185
|
+
Each `run()` appends a transcript line-by-line under `config.session_dir` (default `<work_dir>/.lich/sessions`); `result.session_path` gives the exact file. Records carry `{ts, kind: "message"|"meta", message?, meta?}`; read them with `jq` (or the `read_session_messages(path)` helper if you are working from a source checkout). Persistence is best-effort: a write failure logs a warning, returns `session_path: undefined`, and never fails the run.
|
|
@@ -14,7 +14,7 @@ Create the plugin module (defaults to `.lich/plugins/`, but any path works):
|
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
16
|
// .lich/plugins/my-plugin.ts
|
|
17
|
-
import type { Plugin } from "lich";
|
|
17
|
+
import type { Plugin } from "@moikapy/lich";
|
|
18
18
|
|
|
19
19
|
const my_plugin: Plugin = {
|
|
20
20
|
name: "my-plugin",
|
|
@@ -58,7 +58,7 @@ All hooks are awaited. Hook errors are logged as warnings and skipped — a brok
|
|
|
58
58
|
Plugin tools implement the same `Tool` interface as builtins: a `name`, a `description` the model reads, a JSON Schema `parameters` object, and an async `execute(args, context)` returning `{ok, output, error?}`:
|
|
59
59
|
|
|
60
60
|
```ts
|
|
61
|
-
import type { Plugin, Tool } from "lich";
|
|
61
|
+
import type { Plugin, Tool } from "@moikapy/lich";
|
|
62
62
|
|
|
63
63
|
const upper_tool: Tool = {
|
|
64
64
|
name: "upper_case",
|
|
@@ -112,7 +112,7 @@ Failures are contained at every layer:
|
|
|
112
112
|
|
|
113
113
|
## Runtime notes
|
|
114
114
|
|
|
115
|
-
- **Bun** runs TypeScript plugin files natively — `.ts` entries just work (`bun src/cli.ts ...`).
|
|
115
|
+
- **Bun** runs TypeScript plugin files natively — `.ts` entries just work (`bun src/cli.ts ...` from a clone).
|
|
116
116
|
- **Node** (the built `dist/cli.js`) uses the native ESM loader, which does not compile TS. For node deployments, compile your plugin or ship it as `.mjs`/plain JS and list that file in `plugins`.
|
|
117
117
|
|
|
118
118
|
## Security note
|
package/docs/user-guide/tui.md
CHANGED
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
## Launching
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
|
|
8
|
+
lich tui # or, from a clone: bun src/cli.ts tui
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
The TUI needs a TTY and a resolvable provider (same resolution as every mode). On startup it prints a dim header with the version and the first provider's model, e.g. `lich v0.
|
|
11
|
+
The TUI needs a TTY and a resolvable provider (same resolution as every mode). On startup it prints a dim header with the version and the first provider's model, e.g. `lich v0.3.0 — llama3.2 (ollama)`. Quit with `/exit`, `/quit`, `/q`, or Ctrl+C.
|
|
12
12
|
|
|
13
13
|
## Anatomy
|
|
14
14
|
|
|
15
15
|
```
|
|
16
|
-
lich v0.
|
|
16
|
+
lich v0.3.0 — llama3.2 (ollama) <- header: version, model, provider kind
|
|
17
17
|
you › list the files here <- your input, echoed into the transcript
|
|
18
18
|
⏺ list_dir({}) <- live tool-call row (name + args preview)
|
|
19
19
|
⏷ list_dir: ok (d src/ d test/ ...) <- result row (ok/error + output preview)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moikapy/lich",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Lich — a TypeScript AI agent harness (library + CLI) inspired by Hermes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"test": "node node_modules/vitest/vitest.mjs run",
|
|
34
34
|
"test:watch": "node node_modules/vitest/vitest.mjs",
|
|
35
35
|
"cli": "bun src/cli.ts",
|
|
36
|
+
"release": "bun scripts/release.ts",
|
|
37
|
+
"release:package": "bun scripts/release.ts",
|
|
36
38
|
"docs:dev": "vitepress dev docs",
|
|
37
39
|
"docs:build": "vitepress build docs",
|
|
38
40
|
"docs:preview": "vitepress preview docs"
|
|
@@ -51,4 +53,4 @@
|
|
|
51
53
|
"vitest": "^3.0.0",
|
|
52
54
|
"vitepress": "^1.6.4"
|
|
53
55
|
}
|
|
54
|
-
}
|
|
56
|
+
}
|
package/dist/tui-V7ATLIKW.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tui.tsx","../src/tui/app.tsx","../src/tui/state.ts","../src/tui/message_view.tsx","../src/tui/status_bar.tsx","../src/tui/command_bar.tsx"],"sourcesContent":["/**\n * TUI entry: builds the agent from the parsed config and renders the ink app\n * until exit. The CLI dynamic-imports this module for `lich tui`.\n */\nimport { render } from \"ink\";\nimport { create_agent } from \"./agent/agent.js\";\nimport type { AgentConfig } from \"./agent/config.js\";\nimport { TuiApp } from \"./tui/app.js\";\n\nexport async function run_tui(config: AgentConfig): Promise<number> {\n const agent = create_agent(config);\n const instance = render(<TuiApp agent={agent} />);\n await instance.waitUntilExit();\n return 0;\n}","/**\n * Root ink component for the lich TUI: wires agent events into the UI state\n * machine, drives agent.run with history continuity, and lays out header,\n * transcript, status bar, and the command input row.\n */\nimport { useCallback, useEffect, useRef, useState, type Dispatch, type SetStateAction } from \"react\";\nimport { Box, Text } from \"ink\";\nimport type { Agent, AgentRunResult } from \"../agent/agent.js\";\nimport type { AgentEvent } from \"../agent/events.js\";\nimport type { AgentConfig } from \"../agent/config.js\";\nimport type { Message } from \"../providers/types.js\";\nimport { readdir, stat } from \"node:fs/promises\";\nimport {\n apply_event,\n apply_run_result,\n compress_notice_block,\n error_notice_block,\n help_block,\n HISTORY_CAP,\n INITIAL_UI_STATE,\n model_label_block,\n parse_command,\n run_notice_blocks,\n session_list_block,\n tool_result_block,\n unknown_command_block,\n usage_notice_block,\n type HistoryBlock,\n type ParsedInput,\n type SessionEntryInfo,\n type UiState,\n} from \"./state.js\";\nimport { MessageView } from \"./message_view.js\";\nimport { StatusBar } from \"./status_bar.js\";\nimport { CommandBar } from \"./command_bar.js\";\n\nconst SESSION_LIST_CAP = 10;\n\ntype SlashInput = Extract<ParsedInput, { kind: \"slash\" }>;\ntype AddBlocks = (added: readonly HistoryBlock[]) => void;\ntype SetUiState = Dispatch<SetStateAction<UiState>>;\ntype SetBlocks = Dispatch<SetStateAction<readonly HistoryBlock[]>>;\n\n/** Map one agent event to optional transcript blocks (tool rows, notices). */\nfunction event_blocks(event: AgentEvent): readonly HistoryBlock[] {\n if (event.type === \"tool_call_end\") {\n return [tool_result_block(event.call, event.result.ok === true, event.result.output)];\n }\n if (event.type === \"compress_end\") {\n return [compress_notice_block(event.summary_chars)];\n }\n if (event.type === \"error\") {\n return [error_notice_block(event.error instanceof Error ? event.error.message : String(event.error))];\n }\n return [];\n}\n\n/** One agent turn: subscribe to events, run, unsubscribe in finally. */\nasync function run_agent_turn(\n agent: Agent,\n history: readonly Message[],\n input: string,\n on_event: (event: AgentEvent) => void,\n on_done: (result: AgentRunResult) => void,\n signal: AbortSignal,\n): Promise<void> {\n const stop_listening = agent.events.on(on_event);\n try {\n const result = await agent.run({ input, history, signal, label: \"tui\" });\n on_done(result);\n } finally {\n stop_listening();\n }\n}\n\n/** Async /sessions listing as a meta block (never throws). */\nasync function sessions_block(config: AgentConfig): Promise<HistoryBlock> {\n try {\n const dir_entries = await readdir(config.session_dir, { withFileTypes: true });\n const entries: SessionEntryInfo[] = [];\n for (const entry of dir_entries) {\n if (entry.isFile() === false || entry.name.endsWith(\".jsonl\") === false) {\n continue;\n }\n const info = await stat(`${config.session_dir}/${entry.name}`);\n entries.push({ name: entry.name, size_bytes: info.size, mtime_ms: info.mtimeMs });\n }\n return session_list_block(entries, SESSION_LIST_CAP);\n } catch {\n return { role: \"meta\", lines: [\"· no session files yet\"] };\n }\n}\n\nfunction run_error_text(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** Runs one message exchange; owns history continuity and abort wiring. */\nfunction use_agent_run(\n agent: Agent,\n add_blocks: AddBlocks,\n set_state: SetUiState,\n set_blocks: SetBlocks,\n): (text: string) => void {\n const history_ref = useRef<readonly Message[]>([]);\n const controller_ref = useRef<AbortController | undefined>(undefined);\n\n const finish_run = useCallback((result: AgentRunResult): void => {\n history_ref.current = result.messages;\n set_state((current) => apply_run_result(current, result));\n add_blocks(run_notice_blocks(result));\n }, [add_blocks, set_state]);\n\n const start_message_run = useCallback(\n (text: string): void => {\n add_blocks([{ role: \"user\", lines: [`you › ${text}`] }]);\n set_state((current) => ({ ...current, phase: \"thinking\", active_tool: undefined }));\n const controller = new AbortController();\n controller_ref.current = controller;\n const on_event = (event: AgentEvent): void => {\n set_state((current) => apply_event(current, event));\n set_blocks((current) => [...current, ...event_blocks(event)].slice(-HISTORY_CAP));\n };\n void run_agent_turn(agent, history_ref.current, text, on_event, finish_run, controller.signal)\n .catch((error: unknown) => {\n add_blocks([error_notice_block(run_error_text(error))]);\n set_state((current) => ({ ...current, phase: \"idle\" }));\n })\n .finally(() => {\n if (controller_ref.current === controller) {\n controller_ref.current = undefined;\n }\n });\n },\n [agent, add_blocks, finish_run, set_blocks, set_state],\n );\n\n useEffect(() => () => controller_ref.current?.abort(), []);\n\n return start_message_run;\n}\n\n/** Slash-command dispatch: pure client-side actions, never hits the agent. */\nfunction use_slash_commands(agent: Agent, add_blocks: AddBlocks, set_blocks: SetBlocks, total_tokens: number): (parsed: SlashInput) => void {\n const handle = useCallback(\n (parsed: SlashInput): void => {\n if (parsed.name === \"exit\" || parsed.name === \"quit\" || parsed.name === \"q\") {\n process.exit(0);\n return;\n }\n if (parsed.name === \"help\") {\n add_blocks([help_block()]);\n } else if (parsed.name === \"model\") {\n add_blocks([model_label_block(agent.config)]);\n } else if (parsed.name === \"usage\") {\n add_blocks([usage_notice_block(total_tokens)]);\n } else if (parsed.name === \"clear\") {\n set_blocks([]);\n } else if (parsed.name === \"sessions\") {\n void sessions_block(agent.config).then((block) => add_blocks([block]));\n } else {\n add_blocks([unknown_command_block(parsed.name)]);\n }\n },\n [agent, add_blocks, set_blocks, total_tokens],\n );\n return handle;\n}\n\ninterface TuiAppProps {\n readonly agent: Agent;\n}\n\nexport function TuiApp({ agent }: TuiAppProps): React.JSX.Element {\n const [blocks, set_blocks] = useState<readonly HistoryBlock[]>([]);\n const [state, set_state] = useState(INITIAL_UI_STATE);\n\n const add_blocks = useCallback<AddBlocks>((added: readonly HistoryBlock[]): void => {\n if (added.length === 0) {\n return;\n }\n set_blocks((current) => [...current, ...added].slice(-HISTORY_CAP));\n }, []);\n\n const start_message_run = use_agent_run(agent, add_blocks, set_state, set_blocks);\n const handle_slash = use_slash_commands(agent, add_blocks, set_blocks, state.usage.total_tokens);\n\n const submit = useCallback(\n (text: string): void => {\n const parsed = parse_command(text);\n if (parsed.kind === \"message\") {\n if (parsed.text.length > 0) {\n start_message_run(parsed.text);\n }\n return;\n }\n handle_slash(parsed);\n },\n [handle_slash, start_message_run],\n );\n\n const provider = agent.config.providers[0];\n return (\n <Box flexDirection=\"column\" minHeight={8}>\n <Text dimColor>{`lich v0.2.0 — ${provider?.model ?? \"unknown\"} (${provider?.kind ?? \"unknown\"})`}</Text>\n <MessageView blocks={blocks} state={state} />\n <StatusBar state={state} model={provider?.model ?? \"unknown\"} />\n <CommandBar busy={state.phase !== \"idle\"} on_submit={submit} />\n </Box>\n );\n}","/**\n * Pure state logic for the ink TUI: UI-state transitions from agent events,\n * slash-command parsing, transcript block mapping, and display formatters.\n * No ink/react imports here — this module is unit-tested without a TTY.\n */\nimport type { AgentEvent } from \"../agent/events.js\";\nimport type { AgentRunResult } from \"../agent/agent.js\";\nimport type { AssistantMessage, Message, ToolCall, Usage } from \"../providers/types.js\";\nimport { safe_json_parse, truncate_text } from \"../util/json.js\";\n\nexport type UiPhase = \"idle\" | \"thinking\" | \"tool\";\n\nexport interface UiState {\n readonly phase: UiPhase;\n readonly turns_used: number;\n readonly usage: Usage;\n readonly session_path: string | undefined;\n readonly compress_count: number;\n readonly budget_exhausted: boolean;\n readonly last_error: string | undefined;\n readonly active_tool: ToolCall | undefined;\n}\n\nexport const INITIAL_UI_STATE: UiState = {\n phase: \"idle\",\n turns_used: 0,\n usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n session_path: undefined,\n compress_count: 0,\n budget_exhausted: false,\n last_error: undefined,\n active_tool: undefined,\n};\n\nexport const HISTORY_CAP = 50;\nconst TOOL_ARGS_PREVIEW_CHARS = 80;\nconst TOOL_OUTPUT_PREVIEW_CHARS = 120;\nconst ERROR_PREVIEW_CHARS = 300;\n\nfunction error_text(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n return String(error);\n}\n\n/** Reducer over UiState; one pure mapping per AgentEvent variant. */\nexport function apply_event(state: UiState, event: AgentEvent): UiState {\n switch (event.type) {\n case \"llm_start\":\n return { ...state, phase: \"thinking\", active_tool: undefined };\n case \"llm_end\":\n return {\n ...state,\n usage: {\n prompt_tokens: state.usage.prompt_tokens + event.result.usage.prompt_tokens,\n completion_tokens: state.usage.completion_tokens + event.result.usage.completion_tokens,\n total_tokens: state.usage.total_tokens + event.result.usage.total_tokens,\n },\n };\n case \"tool_call_start\":\n return { ...state, phase: \"tool\", active_tool: event.call };\n case \"tool_call_end\":\n return {\n ...state,\n phase: \"thinking\",\n active_tool: undefined,\n last_error: event.result.ok === true ? state.last_error : (event.result.error ?? \"tool failed\"),\n };\n case \"turn_end\":\n return { ...state, turns_used: event.turn };\n case \"compress_start\":\n return { ...state, compress_count: state.compress_count + 1 };\n case \"budget_exhausted\":\n return { ...state, budget_exhausted: true };\n case \"error\":\n return { ...state, last_error: error_text(event.error) };\n default:\n return state;\n }\n}\n\n/** Fold an AgentRunResult back into UiState after the run promise resolves. */\nexport function apply_run_result(state: UiState, result: AgentRunResult): UiState {\n return {\n ...state,\n phase: \"idle\",\n turns_used: result.outcome.turns_used,\n session_path: result.session_path ?? state.session_path,\n last_error: result.outcome.stopped_reason === \"aborted\" ? \"run aborted\" : state.last_error,\n };\n}\n\nexport type ParsedInput = { kind: \"slash\"; name: string; args: string } | { kind: \"message\"; text: string };\n\n/** Split trimmed input into slash command vs plain message (empty input = message). */\nexport function parse_command(raw_input: string): ParsedInput {\n const text = raw_input.trim();\n if (text.startsWith(\"/\") === false) {\n return { kind: \"message\", text };\n }\n const body = text.slice(1);\n const space_index = body.indexOf(\" \");\n if (space_index === -1) {\n return { kind: \"slash\", name: body, args: \"\" };\n }\n return { kind: \"slash\", name: body.slice(0, space_index), args: body.slice(space_index + 1).trim() };\n}\n\n/** 1234567 -> \"1,234,567\" (US grouping, matching the status bar style). */\nexport function format_usage(total_tokens: number): string {\n return total_tokens.toLocaleString(\"en-US\");\n}\n\nexport type BlockRole = \"user\" | \"lich\" | \"tool\" | \"meta\" | \"error\";\n\nexport interface HistoryBlock {\n readonly role: BlockRole;\n readonly lines: readonly string[];\n}\n\nfunction assistant_tool_line(call: ToolCall): string {\n const args_json = JSON.stringify(call.args) ?? \"{}\";\n return ` \\u23bf ${truncate_text(args_json, TOOL_ARGS_PREVIEW_CHARS)}`;\n}\n\nfunction format_message_lines(message: Message): string[] {\n if (message.role === \"user\") {\n return [`you \\u203a ${message.content}`];\n }\n if (message.role === \"assistant\") {\n const lines = [`lich \\u203a ${message.content}`];\n for (const call of message.tool_calls ?? []) {\n lines.push(assistant_tool_line(call));\n }\n return lines;\n }\n if (message.role === \"tool\") {\n const flag = message.is_error === true ? \"error\" : \"ok\";\n return [` \\u23bf ${message.name}: ${flag} (${truncate_text(message.content, TOOL_OUTPUT_PREVIEW_CHARS)})`];\n }\n return [`\\u00b7 system: ${message.content}`];\n}\n\n/** Map one transcript Message to display lines with its role tag. */\nexport function format_message_block(message: Message): HistoryBlock {\n if (message.role === \"tool\") {\n const ok = message.is_error !== true;\n return { role: ok === true ? \"tool\" : \"error\", lines: format_message_lines(message) };\n }\n const roles: Record<Exclude<Message[\"role\"], \"tool\">, BlockRole> = {\n system: \"meta\",\n user: \"user\",\n assistant: \"lich\",\n };\n return { role: roles[message.role], lines: format_message_lines(message) };\n}\n\n/** Keep the newest `cap` non-system messages as renderable blocks. */\nexport function split_history_blocks(messages: readonly Message[], cap: number): HistoryBlock[] {\n const visible = messages.filter((message) => message.role !== \"system\");\n const start = Math.max(0, visible.length - cap);\n return visible.slice(start).map(format_message_block);\n}\n\nfunction assistant_result_block(message: AssistantMessage): HistoryBlock | undefined {\n if (message.content.length === 0) {\n return undefined;\n }\n return { role: \"lich\", lines: [`lich \\u203a ${message.content}`] };\n}\n\n/** Post-run meta blocks: compression notices, budget, errors, final answer. */\nexport function run_notice_blocks(result: AgentRunResult): HistoryBlock[] {\n const blocks: HistoryBlock[] = [];\n if (result.outcome.stopped_reason === \"budget\") {\n blocks.push({ role: \"error\", lines: [\"\\u00b7 budget exhausted (turn cap reached)\"] });\n }\n const final_block = result.outcome.final === undefined ? undefined : assistant_result_block(result.outcome.final);\n if (final_block !== undefined) {\n blocks.push(final_block);\n }\n return blocks;\n}\n\nfunction truncate_tool_preview(result_content: string, ok: boolean): string {\n return ` \\u23bf ${ok === true ? \"ok\" : \"error\"} (${truncate_text(result_content, TOOL_OUTPUT_PREVIEW_CHARS)})`;\n}\n\n/** One finalized live tool row; falls back to the transcript copy when absent. */\nexport function tool_result_block(call: ToolCall, ok: boolean, result_content: string): HistoryBlock {\n const args_json = JSON.stringify(call.args) ?? \"{}\";\n const lines = [\n `\\u23fa ${call.name}(${truncate_text(args_json, TOOL_ARGS_PREVIEW_CHARS)})`,\n truncate_tool_preview(result_content, ok),\n ];\n return { role: ok === true ? \"tool\" : \"error\", lines };\n}\n\nexport function parse_tool_message_content(content: string): { ok: boolean; output: string } {\n const parsed = safe_json_parse<{ ok?: unknown; output?: unknown }>(content);\n if (parsed !== undefined && typeof parsed.ok === \"boolean\" && typeof parsed.output === \"string\") {\n return { ok: parsed.ok, output: parsed.output };\n }\n return { ok: true, output: content };\n}\n\nexport function compress_notice_block(summary_chars: number): HistoryBlock {\n return { role: \"meta\", lines: [`\\u00b7 context compressed (summary ${summary_chars} chars)`] };\n}\n\nexport function error_notice_block(message: string): HistoryBlock {\n return { role: \"error\", lines: [`\\u00b7 error: ${truncate_text(message, ERROR_PREVIEW_CHARS)}`] };\n}\n\nexport function tool_args_preview(args: Record<string, unknown>): string {\n const args_json = JSON.stringify(args) ?? \"{}\";\n return truncate_text(args_json, TOOL_ARGS_PREVIEW_CHARS);\n}\n\nexport function help_block(): HistoryBlock {\n return { role: \"meta\", lines: [...HELP_LINES] };\n}\n\nexport function model_label_block(config: { providers: readonly { model: string; kind: string }[] }): HistoryBlock {\n const provider = config.providers[0];\n return {\n role: \"meta\",\n lines: [`\\u00b7 model: ${provider?.model ?? \"unknown\"} \\u00b7 provider: ${provider?.kind ?? \"unknown\"}`],\n };\n}\n\nexport function usage_notice_block(total_tokens: number): HistoryBlock {\n return { role: \"meta\", lines: [`\\u00b7 tokens used this session: ${format_usage(total_tokens)}`] };\n}\n\nexport function unknown_command_block(name: string): HistoryBlock {\n return { role: \"error\", lines: [`\\u00b7 unknown command: /${name} (try /help)`] };\n}\n\nexport interface SessionEntryInfo {\n readonly name: string;\n readonly size_bytes: number;\n readonly mtime_ms: number;\n}\n\n/** Newest-first session listing, capped at `cap` entries. */\nexport function session_list_block(entries: readonly SessionEntryInfo[], cap: number = 10): HistoryBlock {\n const sorted = [...entries].sort((a, b) => b.mtime_ms - a.mtime_ms).slice(0, cap);\n if (sorted.length === 0) {\n return { role: \"meta\", lines: [\"\\u00b7 no session files yet\"] };\n }\n const lines: string[] = [`\\u00b7 sessions (${sorted.length}):`];\n for (const entry of sorted) {\n lines.push(` ${entry.name} (${format_usage(entry.size_bytes)} bytes)`);\n }\n return { role: \"meta\", lines };\n}\n\nexport const SLASH_COMMAND_NAMES: readonly string[] = [\n \"exit\",\n \"quit\",\n \"q\",\n \"help\",\n \"model\",\n \"usage\",\n \"clear\",\n \"sessions\",\n];\n\nexport const HELP_LINES: readonly string[] = [\n \"commands: /help /model /usage /clear /sessions /exit (aliases: /quit /q)\",\n \"enter submits \\u00b7 backspace deletes \\u00b7 up/down recalls history \\u00b7 pasted newlines become spaces\",\n];","/**\n * Transcript rendering: maps HistoryBlock descriptors to ink elements with\n * role-based colors, and shows an animated braille spinner while thinking.\n * Blocks are pre-capped by the app, so a plain flex column is sufficient.\n */\nimport { useEffect, useState } from \"react\";\nimport { Box, Text } from \"ink\";\nimport { tool_args_preview, type HistoryBlock, type UiState } from \"./state.js\";\n\nconst SPINNER_FRAMES: readonly string[] = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\nconst SPINNER_INTERVAL_MS = 80;\n\n/** Braille spinner frames on an 80ms interval; clears on unmount. */\nfunction use_spinner(): string {\n const [frame, set_frame] = useState(SPINNER_FRAMES[0] ?? \"⠋\");\n useEffect(() => {\n const timer = setInterval(() => {\n const next = SPINNER_FRAMES[(SPINNER_FRAMES.indexOf(frame) + 1) % SPINNER_FRAMES.length];\n set_frame(next ?? \"⠋\");\n }, SPINNER_INTERVAL_MS);\n return () => {\n clearInterval(timer);\n };\n }, [frame]);\n return frame;\n}\n\nfunction ThinkingLine(): React.JSX.Element {\n const frame = use_spinner();\n return <Text dimColor>{`${frame} thinking…`}</Text>;\n}\n\nconst ROLE_COLORS: Record<HistoryBlock[\"role\"], string | undefined> = {\n user: \"white\",\n lich: \"green\",\n tool: \"cyan\",\n meta: undefined,\n error: \"red\",\n};\n\nfunction BlockLines({ block }: { block: HistoryBlock }): React.JSX.Element {\n const color = ROLE_COLORS[block.role];\n return (\n <>\n {block.lines.map((line, index) => (\n <Text key={index} color={color} dimColor={color === undefined}>{line}</Text>\n ))}\n </>\n );\n}\n\ninterface MessageViewProps {\n readonly blocks: readonly HistoryBlock[];\n readonly state: UiState;\n}\n\n/** Transcript column plus the live phase line (spinner / running tool row). */\nexport function MessageView({ blocks, state }: MessageViewProps): React.JSX.Element {\n return (\n <Box flexDirection=\"column\" flexGrow={1}>\n {blocks.map((block, index) => (\n <Box key={index} flexDirection=\"column\">\n <BlockLines block={block} />\n </Box>\n ))}\n {state.phase === \"thinking\" ? <ThinkingLine /> : null}\n {state.phase === \"tool\" && state.active_tool !== undefined ? (\n <Text color=\"cyan\">{`⏺ ${state.active_tool.name}(${tool_args_preview(state.active_tool.args)})`}</Text>\n ) : null}\n </Box>\n );\n}","/**\n * Bottom status line: model, turns, token totals, phase tag, compression\n * count, and the session path once the agent has persisted a transcript.\n */\nimport { Box, Text } from \"ink\";\nimport { format_usage, type UiState } from \"./state.js\";\n\nconst PHASE_LABELS: Record<UiState[\"phase\"], string> = {\n idle: \"idle\",\n thinking: \"thinking\",\n tool: \"tool\",\n};\n\ninterface StatusBarProps {\n readonly state: UiState;\n readonly model: string;\n}\n\nexport function StatusBar({ state, model }: StatusBarProps): React.JSX.Element {\n const phase = PHASE_LABELS[state.phase];\n return (\n <Box>\n <Text dimColor>\n {`model ${model} · turns ${state.turns_used} · tokens ${format_usage(state.usage.total_tokens)} · [${phase}]`}\n {state.compress_count > 0 ? ` · compressed ${state.compress_count}` : \"\"}\n {state.session_path !== undefined ? ` · ${state.session_path}` : \"\"}\n </Text>\n {state.budget_exhausted ? <Text color=\"red\"> · budget exhausted</Text> : null}\n </Box>\n );\n}","/**\n * Input row: printable characters accumulate in a buffer, Enter submits,\n * Backspace/Delete edits, Up/Down walk a 20-entry recall ring, and pasted\n * newlines collapse to spaces. Ctrl+C is left to ink's default handling.\n */\nimport { useState } from \"react\";\nimport { Box, Text, useInput } from \"ink\";\n\nconst INPUT_HISTORY_CAP = 20;\n\ninterface CommandBarProps {\n readonly busy: boolean;\n readonly on_submit: (text: string) => void;\n}\n\n/** Push onto a capped ring (newest first) without mutating the source. */\nfunction push_history(ring: readonly string[], entry: string): readonly string[] {\n return [entry, ...ring.filter((item) => item !== entry)].slice(0, INPUT_HISTORY_CAP);\n}\n\nexport function CommandBar({ busy, on_submit }: CommandBarProps): React.JSX.Element {\n const [buffer, set_buffer] = useState(\"\");\n const [recall_ring, set_recall_ring] = useState<readonly string[]>([]);\n const [recall_index, set_recall_index] = useState<number | undefined>(undefined);\n\n const submit_buffer = (): void => {\n const text = buffer.trim();\n set_buffer(\"\");\n set_recall_index(undefined);\n if (text.length > 0) {\n set_recall_ring((current) => push_history(current, text));\n on_submit(text);\n }\n };\n\n const walk_recall = (direction: 1 | -1): void => {\n if (recall_ring.length === 0) {\n return;\n }\n const current = recall_index ?? -direction;\n const next = Math.min(Math.max(current + direction, 0), recall_ring.length - 1);\n set_recall_index(next);\n set_buffer(recall_ring[next] ?? \"\");\n };\n\n useInput((input, key) => {\n if (key.return === true) {\n submit_buffer();\n return;\n }\n if (key.upArrow === true) {\n walk_recall(-1);\n return;\n }\n if (key.downArrow === true) {\n walk_recall(1);\n return;\n }\n if (key.backspace === true || key.delete === true) {\n set_buffer((current) => current.slice(0, -1));\n return;\n }\n if (key.ctrl === true || key.escape === true || key.tab === true || key.meta === true) {\n return;\n }\n if (input.length > 0) {\n set_buffer((current) => current + input.replaceAll(\"\\n\", \" \").replaceAll(\"\\r\", \" \"));\n }\n });\n\n return (\n <Box>\n <Text dimColor>{busy ? \" … \" : \"› \"}</Text>\n <Text>{buffer}</Text>\n <Text dimColor>▌</Text>\n </Box>\n );\n}"],"mappings":"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","names":["useEffect","useState","Box","Text","Box","Text","jsx","jsxs","Box","Text","useState","Box","Text","jsx","jsxs","jsx","jsxs","useEffect","useState","Box","Text","jsx"]}
|