@moikapy/lich 0.5.1 → 0.7.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +84 -4
  3. package/dist/chunk-JYURFAGB.js +92 -0
  4. package/dist/chunk-JYURFAGB.js.map +1 -0
  5. package/dist/{chunk-CV2YH3FH.js → chunk-QVJCIZIF.js} +748 -13
  6. package/dist/chunk-QVJCIZIF.js.map +1 -0
  7. package/dist/chunk-SAEB3QL3.js +58 -0
  8. package/dist/chunk-SAEB3QL3.js.map +1 -0
  9. package/dist/cli.js +263 -22
  10. package/dist/cli.js.map +1 -1
  11. package/dist/{gateway-W6S43ETE.js → gateway-5BG3YCZF.js} +2 -2
  12. package/dist/index.d.ts +229 -118
  13. package/dist/index.js +5 -3
  14. package/dist/{tui-2VO6LAFM.js → tui-L6RABP2J.js} +43 -41
  15. package/dist/tui-L6RABP2J.js.map +1 -0
  16. package/docs/.vitepress/config.mts +6 -1
  17. package/docs/architecture/agent-loop.md +5 -3
  18. package/docs/architecture/overview.md +36 -22
  19. package/docs/architecture/plugins.md +1 -1
  20. package/docs/architecture/tools.md +7 -2
  21. package/docs/getting-started.md +9 -7
  22. package/docs/index.md +6 -4
  23. package/docs/user-guide/cli.md +24 -9
  24. package/docs/user-guide/games.md +93 -0
  25. package/docs/user-guide/godot.md +3 -1
  26. package/docs/user-guide/library.md +8 -2
  27. package/docs/user-guide/plugins.md +2 -2
  28. package/docs/user-guide/redot.md +93 -0
  29. package/docs/user-guide/tui.md +11 -11
  30. package/examples/game_bridge/README.md +2 -0
  31. package/optional-mcps/godot/manifest.json +6 -0
  32. package/optional-mcps/redot/manifest.json +18 -0
  33. package/package.json +2 -1
  34. package/dist/chunk-6M6OAQGN.js +0 -17
  35. package/dist/chunk-6M6OAQGN.js.map +0 -1
  36. package/dist/chunk-CV2YH3FH.js.map +0 -1
  37. package/dist/tui-2VO6LAFM.js.map +0 -1
  38. /package/dist/{gateway-W6S43ETE.js.map → gateway-5BG3YCZF.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0 (unreleased)
4
+
5
+ - add a general MCP client. `mcp_servers` is a closed record of named
6
+ stdio or loopback-http entries, default off. tool names are
7
+ `mcp_<server>_<tool>`. `tools_enabled: []` drops them. the commander
8
+ persona does not copy `mcp_servers`. redot is a catalog entry (local
9
+ `redot` binary, `--headless --mcp-server --path <project>`, five
10
+ upstream controllers, no execute). godot has no official server; lich
11
+ does not download a community addon. play stays the webhook and
12
+ game_bridge, the opposite direction. see docs/user-guide/redot.md.
13
+ - add `lich mcp` to list, add, enable, disable, and remove servers.
14
+ add uses the catalog or `--command`/`--url`, stays disabled, and
15
+ updates `.lich/config.json` through the existing writer without
16
+ dropping other keys.
17
+ - sync user docs with this tree. the published package is 0.6.0
18
+ (`lich --version` reads package.json). MCP commands are not in that
19
+ package. CLI, config, plugin load, and sidebar links match the code.
20
+ 0.7.0 is not released.
21
+
22
+ ## 0.6.0
23
+
24
+ - document a per-persona orchestrator example: one agent per NPC, the
25
+ webhook `POST /message` shape, and history serialization the game repo
26
+ copies. lich does not ship the service. see
27
+ examples/persona_orchestrator.
28
+ - document session JSONL as a combat log, with jq recipes for rationale,
29
+ ability use, vetoes, rejects, and `run_end` token totals. see
30
+ docs/user-guide/games.md.
31
+ - append a `run_end` meta record (`stopped_reason`, `usage`) on every
32
+ completed run so token spend is in the transcript. `budget_exhausted`
33
+ is still written when the stop reason is budget.
34
+ - add a display-only undead theme: frozen `lich` strings, `theme` / `--theme`, and `~/.lich/themes/<name>.json` that falls back to the built-in theme when missing or invalid.
35
+
3
36
  ## 0.5.1
4
37
 
5
38
  - fix the CLI so bun's global bin enters main (symlink argv no longer skips the entry).
package/README.md CHANGED
@@ -1,20 +1,44 @@
1
- # lich
1
+ # lich
2
2
 
3
3
  Lich is a TypeScript AI agent harness (library + CLI) that runs a
4
4
  Think-Act-Observe loop: an LLM plans, calls tools, observes results, and
5
5
  repeats until it produces a final answer. It ships with provider failover,
6
6
  tool guardrails, context compression, and JSONL session persistence.
7
7
 
8
+ ## Lore glossary
9
+
10
+ Lore names are prose only. Identifiers, config keys, event types, and tool
11
+ names do not change. The default system prompt is a myth-free behavior spec;
12
+ mythology lives in display strings only.
13
+
14
+ | Lore term | Actual concept | Where it appears |
15
+ | --- | --- | --- |
16
+ | **phylacteries** | JSONL session files in `.lich/sessions/` — conversations survive process death | This glossary; TUI `/sessions` listing label |
17
+ | **vessel-hopping** | Provider failover: 429/5xx retried with backoff, then the next provider takes over | This glossary |
18
+ | **the lair / wards** | `work_dir` confinement + `path_escape` guardrails | This glossary |
19
+ | **lair actions** | Plugin hooks that observe or veto tool calls | This glossary |
20
+ | **familiars** | Gateway adapters (webhook/telegram/discord/twitch) routing into one shared agent | This glossary |
21
+ | **spells** | Builtin tools in the registry | This glossary |
22
+ | **distillation** | Context compression: old turns summarized to fit the token budget | TUI compress notice |
23
+ | **the ritual is spent** | Turn-budget exhaustion | TUI + CLI budget notices |
24
+ | **dormant / deliberating / casting** | idle / thinking / tool phases | TUI status bar phase labels |
25
+ | **mortal** | The human user | TUI user transcript label |
26
+
8
27
  ## Documentation
9
28
 
10
29
  | Page | Contents |
11
30
  | --- | --- |
12
31
  | [Docs home](docs/index.md) | Overview, feature map, and a 60-second quickstart. |
13
32
  | [Getting started](docs/getting-started.md) | Zero-to-first-reply: install, config paths, one-shot, TUI, gateway. |
14
- | [CLI reference](docs/user-guide/cli.md) | All four modes, flags, provider resolution, config schema, recipes. |
33
+ | [CLI reference](docs/user-guide/cli.md) | Modes, flags, provider resolution, config schema, `lich mcp`, recipes. |
15
34
  | [TUI guide](docs/user-guide/tui.md) | Launch, slash commands, status bar, memory semantics. |
16
35
  | [Gateway guide](docs/user-guide/gateway.md) | Webhook/Telegram/Discord/Twitch setup and the webhook API. |
17
36
  | [Library guide](docs/user-guide/library.md) | Embedding: `create_agent`, events, multi-turn history, errors. |
37
+ | [Plugins guide](docs/user-guide/plugins.md) | User tools and hooks, and the self-improvement loop. |
38
+ | [Godot guide](docs/user-guide/godot.md) | The game connects to lich (webhook + `game_bridge`). |
39
+ | [Redot guide](docs/user-guide/redot.md) | lich connects to editor MCP servers. Redot is a catalog entry. |
40
+ | [Games guide](docs/user-guide/games.md) | Session JSONL as a combat log, and jq recipes over it. |
41
+ | [Persona example](examples/persona_orchestrator/README.md) | Per-NPC agents the game repo copies. Not a second core. |
18
42
 
19
43
  ## Quick start (CLI)
20
44
 
@@ -42,7 +66,8 @@ lich gateway webhook
42
66
 
43
67
  The CLI has four modes: **one-shot** (`lich "task"`), **chat**
44
68
  (`lich chat`), **tui** (`lich tui`), and **gateway**
45
- (`lich gateway <platform...>`).
69
+ (`lich gateway <platform...>`). Other commands do not start an agent:
70
+ `lich init`, `lich config`, `lich update`, and `lich mcp`.
46
71
 
47
72
  Or use a JSON config file: `lich --config lich.json "task"` (see
48
73
  `AgentConfig` in `src/agent/config.ts` for the schema).
@@ -92,7 +117,9 @@ snake_case args and are registered under the `builtin` toolset.
92
117
  Customize lich with your own tools and lifecycle hooks: keep a `Plugin`
93
118
  object (`{name, tools?, hooks?}`) in your repo, list its file path in the
94
119
  `plugins` config array, and the agent merges your tools and lets your hooks
95
- observe or veto tool calls. See
120
+ observe or veto tool calls. Bare `lich`, one-shot, chat, tui, and gateway
121
+ all load `config.plugins` (`create_agent_with_plugins`). `create_agent` does
122
+ not. See
96
123
  [docs/user-guide/plugins.md](docs/user-guide/plugins.md).
97
124
 
98
125
  Skills are markdown files you write to `.lich/skills/` with `write_file`;
@@ -184,6 +211,59 @@ lich tui
184
211
  Slash commands: `/help`, `/model`, `/usage`, `/clear`, `/sessions`,
185
212
  `/exit` (also `/quit`, `/q`). The transcript shows the newest 50 blocks.
186
213
 
214
+ ## Themes
215
+
216
+ Display strings come from one active theme per process. Set `"theme": "lich"`
217
+ in `.lich/config.json`, or pass `--theme <name>`. The built-in `lich` theme
218
+ is frozen data: `~/.lich/themes/lich.json` is ignored. Any other name is read
219
+ from `~/.lich/themes/<name>.json`. A missing file, invalid JSON, or a file
220
+ that fails the theme schema logs one warning and falls back to `lich`. Themes
221
+ do not change the system prompt, tool descriptions, event types, or slash
222
+ command names.
223
+
224
+ The built-in tagline is `the agent that will not stay dead`, and it appears once, in the TUI banner (`welcome`). Greppable keywords
225
+ stay in place: budget notices still start with `budget exhausted`, and
226
+ compression notices still start with `context compressed`.
227
+
228
+ ```json
229
+ {
230
+ "name": "vampire",
231
+ "agent_name": "vampire",
232
+ "glyph": "🦇",
233
+ "tagline": "night's clerk, unpaid",
234
+ "welcome": "🦇 vampire v{version} — night's clerk, unpaid · {model} ({kind})",
235
+ "goodbye": "dawn approaches",
236
+ "response_label": "vampire",
237
+ "user_label": "mortal",
238
+ "phase_labels": { "idle": "sleeping", "thinking": "scheming", "tool": "feeding" },
239
+ "notices": {
240
+ "budget_exhausted": "budget exhausted — the blood bank is dry (turn cap reached)",
241
+ "compressed": "context compressed — memories enthralled (summary {chars} chars)",
242
+ "sessions": "coffins ({count}):"
243
+ }
244
+ }
245
+ ```
246
+
247
+ `welcome` substitutes `{version}`, `{model}`, and `{kind}`. `notices.compressed`
248
+ substitutes `{chars}`; `notices.sessions` substitutes `{count}`.
249
+
250
+ ## Editor MCP
251
+
252
+ Two directions. lich connects **to** an editor MCP server (stdio local
253
+ command, or loopback HTTP). The game connects **to** lich through the webhook
254
+ and [`examples/game_bridge`](examples/game_bridge/README.md). Default is off.
255
+ `mcp_servers` is a closed record. Names are `mcp_<server>_<tool>`. `npx`,
256
+ `npm`, `bunx`, `uvx`, `curl`, `wget`, remote URLs, and shell metacharacters
257
+ are refused.
258
+
259
+ This source has `lich mcp list|add|enable|disable|remove` (changelog 0.7.0,
260
+ unreleased). The published npm package is 0.6.0 and does not include those
261
+ commands; `lich --version` still prints `0.6.0` because it reads
262
+ `package.json`. From a clone: `bun src/cli.ts mcp list`. Redot is a catalog
263
+ entry (`redot --headless --mcp-server`), not a fork inside lich. Godot has
264
+ no official MCP server; lich does not download a community addon. See
265
+ [docs/user-guide/redot.md](docs/user-guide/redot.md).
266
+
187
267
  ## Development
188
268
 
189
269
  ```sh
@@ -0,0 +1,92 @@
1
+ import {
2
+ logger
3
+ } from "./chunk-QVJCIZIF.js";
4
+
5
+ // src/util/theme.ts
6
+ import { readFileSync } from "fs";
7
+ import { homedir } from "os";
8
+ import path from "path";
9
+ import { z } from "zod";
10
+
11
+ // src/util/lore.ts
12
+ var phase_labels = Object.freeze({
13
+ idle: "dormant",
14
+ thinking: "deliberating",
15
+ tool: "casting"
16
+ });
17
+ var notices = Object.freeze({
18
+ budget_exhausted: "budget exhausted \u2014 the ritual is spent (turn cap reached)",
19
+ compressed: "context compressed \u2014 memories distilled (summary {chars} chars)",
20
+ sessions: "phylacteries ({count}):"
21
+ });
22
+ var LICH_THEME = Object.freeze({
23
+ name: "lich",
24
+ agent_name: "lich",
25
+ glyph: "\u26B1",
26
+ tagline: "the agent that will not stay dead",
27
+ welcome: "\u26B1 lich v{version} \u2014 the agent that will not stay dead \xB7 {model} ({kind})",
28
+ goodbye: "the lich endures",
29
+ response_label: "lich",
30
+ user_label: "mortal",
31
+ phase_labels,
32
+ notices
33
+ });
34
+
35
+ // src/util/theme.ts
36
+ var text = z.string().min(1);
37
+ var theme_schema = z.object({
38
+ name: text,
39
+ agent_name: text,
40
+ glyph: text,
41
+ tagline: text,
42
+ welcome: text,
43
+ goodbye: text,
44
+ response_label: text,
45
+ user_label: text,
46
+ phase_labels: z.object({ idle: text, thinking: text, tool: text }),
47
+ notices: z.object({ budget_exhausted: text, compressed: text, sessions: text })
48
+ });
49
+ function fill_template(template, vars) {
50
+ let filled = template;
51
+ for (const [key, value] of Object.entries(vars)) {
52
+ filled = filled.replaceAll(`{${key}}`, String(value));
53
+ }
54
+ return filled;
55
+ }
56
+ function notice_flavor(notice) {
57
+ const start = notice.indexOf(" \u2014 ");
58
+ if (start === -1) {
59
+ return "";
60
+ }
61
+ const rest = notice.slice(start + 3);
62
+ const end = rest.indexOf(" (");
63
+ return end === -1 ? rest : rest.slice(0, end);
64
+ }
65
+ function load_theme(name, themes_dir) {
66
+ if (name === "lich" || name.length === 0) {
67
+ return LICH_THEME;
68
+ }
69
+ try {
70
+ return read_theme_file(name, themes_dir);
71
+ } catch (error) {
72
+ logger.warn(`theme '${name}' unavailable; using default lich theme`, error);
73
+ return LICH_THEME;
74
+ }
75
+ }
76
+ function read_theme_file(name, themes_dir) {
77
+ if (path.basename(name) !== name) {
78
+ throw new Error("invalid theme name");
79
+ }
80
+ const root = themes_dir ?? path.join(homedir(), ".lich", "themes");
81
+ const theme = theme_schema.parse(JSON.parse(readFileSync(path.join(root, `${name}.json`), "utf8")));
82
+ Object.freeze(theme.phase_labels);
83
+ Object.freeze(theme.notices);
84
+ return Object.freeze(theme);
85
+ }
86
+
87
+ export {
88
+ fill_template,
89
+ notice_flavor,
90
+ load_theme
91
+ };
92
+ //# sourceMappingURL=chunk-JYURFAGB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/util/theme.ts","../src/util/lore.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\nimport { logger } from \"./log.js\";\nimport { LICH_THEME, type ThemeSpec } from \"./lore.js\";\n\nconst text = z.string().min(1);\n\nexport const theme_schema = z.object({\n name: text, agent_name: text, glyph: text, tagline: text,\n welcome: text, goodbye: text, response_label: text, user_label: text,\n phase_labels: z.object({ idle: text, thinking: text, tool: text }),\n notices: z.object({ budget_exhausted: text, compressed: text, sessions: text }),\n});\n\nexport function fill_template(template: string, vars: Record<string, string | number>): string {\n let filled = template;\n for (const [key, value] of Object.entries(vars)) {\n filled = filled.replaceAll(`{${key}}`, String(value));\n }\n return filled;\n}\n\nexport function notice_flavor(notice: string): string {\n const start = notice.indexOf(\" — \");\n if (start === -1) {\n return \"\";\n }\n const rest = notice.slice(start + 3);\n const end = rest.indexOf(\" (\");\n return end === -1 ? rest : rest.slice(0, end);\n}\n\nexport function load_theme(name: string, themes_dir?: string): ThemeSpec {\n if (name === \"lich\" || name.length === 0) {\n return LICH_THEME;\n }\n try {\n return read_theme_file(name, themes_dir);\n } catch (error) {\n logger.warn(`theme '${name}' unavailable; using default lich theme`, error);\n return LICH_THEME;\n }\n}\n\nfunction read_theme_file(name: string, themes_dir?: string): ThemeSpec {\n if (path.basename(name) !== name) {\n throw new Error(\"invalid theme name\");\n }\n const root = themes_dir ?? path.join(homedir(), \".lich\", \"themes\");\n const theme: ThemeSpec = theme_schema.parse(JSON.parse(readFileSync(path.join(root, `${name}.json`), \"utf8\")));\n Object.freeze(theme.phase_labels);\n Object.freeze(theme.notices);\n return Object.freeze(theme);\n}\n","/**\n * Default lich theme as frozen display data. No loader logic here.\n * Mythology stays in these strings; the system prompt stays myth-free.\n */\nexport interface ThemeSpec {\n readonly name: string;\n readonly agent_name: string;\n readonly glyph: string;\n readonly tagline: string;\n readonly welcome: string;\n readonly goodbye: string;\n readonly response_label: string;\n readonly user_label: string;\n readonly phase_labels: {\n readonly idle: string;\n readonly thinking: string;\n readonly tool: string;\n };\n readonly notices: {\n readonly budget_exhausted: string;\n readonly compressed: string;\n readonly sessions: string;\n };\n}\n\nconst phase_labels = Object.freeze({\n idle: \"dormant\",\n thinking: \"deliberating\",\n tool: \"casting\",\n});\n\nconst notices = Object.freeze({\n budget_exhausted: \"budget exhausted — the ritual is spent (turn cap reached)\",\n compressed: \"context compressed — memories distilled (summary {chars} chars)\",\n sessions: \"phylacteries ({count}):\",\n});\n\nexport const LICH_THEME: ThemeSpec = Object.freeze({\n name: \"lich\",\n agent_name: \"lich\",\n glyph: \"⚱\",\n tagline: \"the agent that will not stay dead\",\n welcome: \"⚱ lich v{version} — the agent that will not stay dead · {model} ({kind})\",\n goodbye: \"the lich endures\",\n response_label: \"lich\",\n user_label: \"mortal\",\n phase_labels,\n notices,\n});\n"],"mappings":";;;;;AAAA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,OAAO,UAAU;AACjB,SAAS,SAAS;;;ACsBlB,IAAM,eAAe,OAAO,OAAO;AAAA,EACjC,MAAM;AAAA,EACN,UAAU;AAAA,EACV,MAAM;AACR,CAAC;AAED,IAAM,UAAU,OAAO,OAAO;AAAA,EAC5B,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,UAAU;AACZ,CAAC;AAEM,IAAM,aAAwB,OAAO,OAAO;AAAA,EACjD,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ;AAAA,EACA;AACF,CAAC;;;ADzCD,IAAM,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAEtB,IAAM,eAAe,EAAE,OAAO;AAAA,EACnC,MAAM;AAAA,EAAM,YAAY;AAAA,EAAM,OAAO;AAAA,EAAM,SAAS;AAAA,EACpD,SAAS;AAAA,EAAM,SAAS;AAAA,EAAM,gBAAgB;AAAA,EAAM,YAAY;AAAA,EAChE,cAAc,EAAE,OAAO,EAAE,MAAM,MAAM,UAAU,MAAM,MAAM,KAAK,CAAC;AAAA,EACjE,SAAS,EAAE,OAAO,EAAE,kBAAkB,MAAM,YAAY,MAAM,UAAU,KAAK,CAAC;AAChF,CAAC;AAEM,SAAS,cAAc,UAAkB,MAA+C;AAC7F,MAAI,SAAS;AACb,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,aAAS,OAAO,WAAW,IAAI,GAAG,KAAK,OAAO,KAAK,CAAC;AAAA,EACtD;AACA,SAAO;AACT;AAEO,SAAS,cAAc,QAAwB;AACpD,QAAM,QAAQ,OAAO,QAAQ,UAAK;AAClC,MAAI,UAAU,IAAI;AAChB,WAAO;AAAA,EACT;AACA,QAAM,OAAO,OAAO,MAAM,QAAQ,CAAC;AACnC,QAAM,MAAM,KAAK,QAAQ,IAAI;AAC7B,SAAO,QAAQ,KAAK,OAAO,KAAK,MAAM,GAAG,GAAG;AAC9C;AAEO,SAAS,WAAW,MAAc,YAAgC;AACvE,MAAI,SAAS,UAAU,KAAK,WAAW,GAAG;AACxC,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,gBAAgB,MAAM,UAAU;AAAA,EACzC,SAAS,OAAO;AACd,WAAO,KAAK,UAAU,IAAI,2CAA2C,KAAK;AAC1E,WAAO;AAAA,EACT;AACF;AAEA,SAAS,gBAAgB,MAAc,YAAgC;AACrE,MAAI,KAAK,SAAS,IAAI,MAAM,MAAM;AAChC,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AACA,QAAM,OAAO,cAAc,KAAK,KAAK,QAAQ,GAAG,SAAS,QAAQ;AACjE,QAAM,QAAmB,aAAa,MAAM,KAAK,MAAM,aAAa,KAAK,KAAK,MAAM,GAAG,IAAI,OAAO,GAAG,MAAM,CAAC,CAAC;AAC7G,SAAO,OAAO,MAAM,YAAY;AAChC,SAAO,OAAO,MAAM,OAAO;AAC3B,SAAO,OAAO,OAAO,KAAK;AAC5B;","names":[]}