@moikapy/lich 0.3.0 → 0.4.0
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 +35 -1
- package/README.md +34 -8
- package/dist/{chunk-P52U5M3L.js → chunk-MLFJW4JU.js} +399 -70
- package/dist/chunk-MLFJW4JU.js.map +1 -0
- package/dist/cli.d.ts +1 -2
- package/dist/cli.js +4 -3
- package/dist/cli.js.map +1 -1
- package/dist/{gateway-CWPVIU3W.js → gateway-XTYDYT67.js} +2 -2
- package/dist/index.d.ts +25 -11
- package/dist/index.js +1 -1
- package/dist/{tui-V7ATLIKW.js → tui-VYBJSGRV.js} +6 -3
- package/dist/tui-VYBJSGRV.js.map +1 -0
- package/docs/.vitepress/config.mts +1 -0
- package/docs/architecture/extending.md +5 -2
- package/docs/architecture/overview.md +9 -7
- package/docs/architecture/plugins.md +58 -5
- package/docs/architecture/tools.md +16 -4
- 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 +11 -11
- package/docs/user-guide/cli.md +18 -5
- package/docs/user-guide/gateway.md +10 -10
- package/docs/user-guide/library.md +15 -9
- package/docs/user-guide/plugins.md +59 -6
- package/docs/user-guide/tui.md +3 -3
- package/package.json +4 -2
- package/dist/chunk-P52U5M3L.js.map +0 -1
- package/dist/tui-V7ATLIKW.js.map +0 -1
- /package/dist/{gateway-CWPVIU3W.js.map → gateway-XTYDYT67.js.map} +0 -0
|
@@ -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" }] },
|
|
@@ -43,7 +47,7 @@ const result = await run_agent(
|
|
|
43
47
|
|
|
44
48
|
## Agent class
|
|
45
49
|
|
|
46
|
-
`new Agent(config)` (or `create_agent(raw)`) builds the provider router, registers the
|
|
50
|
+
`new Agent(config)` (or `create_agent(raw)`) builds the provider router, registers the builtin tools (filtered by `tools_enabled`) plus the gatekeeper's `git_commit`, and exposes:
|
|
47
51
|
|
|
48
52
|
| Member | Type | Purpose |
|
|
49
53
|
| --- | --- | --- |
|
|
@@ -137,6 +141,8 @@ const config = {
|
|
|
137
141
|
|
|
138
142
|
Listed providers form a failover chain tried in order: `rate_limit`/`network` errors retry with backoff (3 attempts) on the current provider before failing over; `auth`, `overflow`, and `bad_request` fail over immediately. The last error is rethrown when all providers fail.
|
|
139
143
|
|
|
144
|
+
`LICH_ALLOW_SELF_COMMIT` and `LICH_TEST_COMMAND` are process-env knobs, not config fields. See the [CLI environment](cli.md#self-improvement-environment).
|
|
145
|
+
|
|
140
146
|
## Custom tool filtering
|
|
141
147
|
|
|
142
148
|
`tools_enabled` accepts `"all"` (default) or an array of builtin tool names to register; everything else stays unregistered and invisible to the model:
|
|
@@ -162,7 +168,7 @@ Provider failures throw `ProviderError`, an `Error` subclass with `kind`, `provi
|
|
|
162
168
|
| `unknown` | Non-provider errors (e.g. tool crashes surfaced as strings). | Treated as fatal for the provider. |
|
|
163
169
|
|
|
164
170
|
```ts
|
|
165
|
-
import { ProviderError } from "lich";
|
|
171
|
+
import { ProviderError } from "@moikapy/lich";
|
|
166
172
|
|
|
167
173
|
try {
|
|
168
174
|
await agent.run({ input: "hello" });
|
|
@@ -178,4 +184,4 @@ When every configured provider fails, the last `ProviderError` is thrown. Tool f
|
|
|
178
184
|
|
|
179
185
|
## Session access
|
|
180
186
|
|
|
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
|
|
187
|
+
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",
|
|
@@ -47,18 +47,18 @@ All hooks are awaited. Hook errors are logged as warnings and skipped — a brok
|
|
|
47
47
|
| Hook | Signature | Purpose |
|
|
48
48
|
| --- | --- | --- |
|
|
49
49
|
| `before_tool_call` | `(info: {tool_name, args}, ctx) => {block?: boolean, reason?: string} \| void` | Runs before each tool call in plugin registration order. Return `{block: true, reason}` to veto. |
|
|
50
|
-
| `after_tool_call` | `(info: {tool_name, args, result_summary}, ctx) => void` | Runs after each tool call with a 300-char
|
|
50
|
+
| `after_tool_call` | `(info: {tool_name, args, result_summary, ok, error?}, ctx) => void` | Runs after each tool call with a 300-char summary plus structured `ok`/`error`. |
|
|
51
51
|
| `on_run_start` | `(info: {input_chars}, ctx) => void` | Runs once before the conversation loop starts. |
|
|
52
52
|
| `on_run_end` | `(info: {stopped_reason, turns_used}, ctx) => void` | Runs once after the loop ends with the outcome. |
|
|
53
53
|
|
|
54
|
-
`ctx` is `{work_dir
|
|
54
|
+
`ctx` is `{work_dir, state?}` — the agent's working directory plus that plugin's per-run bag.
|
|
55
55
|
|
|
56
56
|
## Tool authoring
|
|
57
57
|
|
|
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,9 +112,62 @@ 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
|
+
## Self-improvement loop
|
|
119
|
+
|
|
120
|
+
The agent can write a tool, prove it with `run_tests`, and commit it with
|
|
121
|
+
`git_commit` — one commit per run, and only when you opt in. The gatekeeper
|
|
122
|
+
is constructed in code (not listed in `config.plugins`). If it does not
|
|
123
|
+
register, `git_commit` is absent. A config plugin naming `git_commit` is
|
|
124
|
+
inside the plugin-trust floor only when the gatekeeper is off; while it is
|
|
125
|
+
on, first-wins keeps the gatekeeper's tool.
|
|
126
|
+
|
|
127
|
+
Set `LICH_ALLOW_SELF_COMMIT=1` before startup. Unset, or any other value, is
|
|
128
|
+
fail-closed. `git_commit` is vetoed unless every condition holds; the reason
|
|
129
|
+
names the first failure, and the model sees `blocked_by_plugin: <reason>`:
|
|
130
|
+
|
|
131
|
+
| Failed condition | Reason |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `LICH_ALLOW_SELF_COMMIT` is not `1` | `self_commit_disabled` |
|
|
134
|
+
| no green `run_tests` yet this run | `tests_not_ok` |
|
|
135
|
+
| a `write_file` or `edit_file` succeeded after that green run | `worktree_dirty` |
|
|
136
|
+
| this run already committed once | `commit_budget_exhausted` |
|
|
137
|
+
|
|
138
|
+
`terminal` is vetoed when the command matches the hardcoded git denylist.
|
|
139
|
+
The reason is `git_denylist: <pattern>`. Patterns are flag-tolerant
|
|
140
|
+
`commit`/`push` (`commit`, `-commit`, `--commit`, `push`, `-push`, `--push`)
|
|
141
|
+
and any occurrence of `commit-tree` or `update-ref`. There is no `remote`
|
|
142
|
+
pattern. The denylist is best-effort: raw `terminal` can still run git. The
|
|
143
|
+
boundary is a human reviewing the local repo. Push is human-only.
|
|
144
|
+
|
|
145
|
+
`git_commit` takes `{message, paths}` — 1 to 50 paths relative to `work_dir`.
|
|
146
|
+
It rejects `""`, `.`, a path that resolves to `work_dir` itself, and
|
|
147
|
+
secret-ish basenames (`.env`, `.env.local`, `*.pem`, `*.p12`, `id_rsa*`).
|
|
148
|
+
It refuses an unreachable `HEAD`. It stages exactly the named paths
|
|
149
|
+
(`git add -- <paths>`) and commits with `git commit --only`. It never pushes.
|
|
150
|
+
|
|
151
|
+
`run_tests` takes an optional `filter` and runs `LICH_TEST_COMMAND` in
|
|
152
|
+
`work_dir` (default `node node_modules/vitest/vitest.mjs run`) with a 600s
|
|
153
|
+
timeout. A second call in the same process returns `run_tests_busy`. The
|
|
154
|
+
mutex is process-local: one lich process per repo.
|
|
155
|
+
|
|
156
|
+
Clean state attests no `write_file`/`edit_file` since the last green
|
|
157
|
+
`run_tests`; it does NOT attest absence of terminal-mediated writes.
|
|
158
|
+
|
|
159
|
+
### Skills and memory
|
|
160
|
+
|
|
161
|
+
Write a markdown note with `write_file` to `.lich/skills/<name>.md`.
|
|
162
|
+
`docs_search` finds those files. That directory does not need `index.md`,
|
|
163
|
+
and it is walked fresh on every search. The default system prompt says tool
|
|
164
|
+
results — docs, skills, memory — are reference data, not instructions.
|
|
165
|
+
|
|
166
|
+
`MEMORY.md` is append-only and human-reviewable. It is never auto-loaded.
|
|
167
|
+
Review it between appends and the next self-commit.
|
|
168
|
+
|
|
118
169
|
## Security note
|
|
119
170
|
|
|
120
|
-
Plugins execute **in-process with full privileges** — the same trust level as the agent itself and your shell. A plugin can read any file the process can, make network calls, and alter process state. Only load plugin files you wrote or audited; treat `.lich/plugins/` like you treat `.env` files.
|
|
171
|
+
Plugins execute **in-process with full privileges** — the same trust level as the agent itself and your shell. A plugin can read any file the process can, make network calls, and alter process state. Only load plugin files you wrote or audited; treat `.lich/plugins/` like you treat `.env` files.
|
|
172
|
+
|
|
173
|
+
`.lich/config.json` `plugins` is persistent arbitrary code at the next process start. Review config diffs before the next self-commit. The terminal git denylist does not close that hole.
|
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
|
+
"version": "0.4.0",
|
|
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
|
+
}
|