@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.
- package/CHANGELOG.md +33 -0
- package/README.md +84 -4
- package/dist/chunk-JYURFAGB.js +92 -0
- package/dist/chunk-JYURFAGB.js.map +1 -0
- package/dist/{chunk-CV2YH3FH.js → chunk-QVJCIZIF.js} +748 -13
- package/dist/chunk-QVJCIZIF.js.map +1 -0
- package/dist/chunk-SAEB3QL3.js +58 -0
- package/dist/chunk-SAEB3QL3.js.map +1 -0
- package/dist/cli.js +263 -22
- package/dist/cli.js.map +1 -1
- package/dist/{gateway-W6S43ETE.js → gateway-5BG3YCZF.js} +2 -2
- package/dist/index.d.ts +229 -118
- package/dist/index.js +5 -3
- package/dist/{tui-2VO6LAFM.js → tui-L6RABP2J.js} +43 -41
- package/dist/tui-L6RABP2J.js.map +1 -0
- package/docs/.vitepress/config.mts +6 -1
- package/docs/architecture/agent-loop.md +5 -3
- package/docs/architecture/overview.md +36 -22
- package/docs/architecture/plugins.md +1 -1
- package/docs/architecture/tools.md +7 -2
- package/docs/getting-started.md +9 -7
- package/docs/index.md +6 -4
- package/docs/user-guide/cli.md +24 -9
- package/docs/user-guide/games.md +93 -0
- package/docs/user-guide/godot.md +3 -1
- package/docs/user-guide/library.md +8 -2
- package/docs/user-guide/plugins.md +2 -2
- package/docs/user-guide/redot.md +93 -0
- package/docs/user-guide/tui.md +11 -11
- package/examples/game_bridge/README.md +2 -0
- package/optional-mcps/godot/manifest.json +6 -0
- package/optional-mcps/redot/manifest.json +18 -0
- package/package.json +2 -1
- package/dist/chunk-6M6OAQGN.js +0 -17
- package/dist/chunk-6M6OAQGN.js.map +0 -1
- package/dist/chunk-CV2YH3FH.js.map +0 -1
- package/dist/tui-2VO6LAFM.js.map +0 -1
- /package/dist/{gateway-W6S43ETE.js.map → gateway-5BG3YCZF.js.map} +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
catalog_by_name
|
|
3
|
+
} from "./chunk-QVJCIZIF.js";
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
import { readFileSync } from "fs";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
|
|
9
|
+
// src/mcp/mcp_catalog_entry.ts
|
|
10
|
+
var PLACEHOLDER = /\$\{([a-z_]+)\}/g;
|
|
11
|
+
function expand(value, substitutes) {
|
|
12
|
+
const tokens = value.match(PLACEHOLDER) ?? [];
|
|
13
|
+
let next = value;
|
|
14
|
+
for (const token of tokens) {
|
|
15
|
+
const key = token.slice(2, -1);
|
|
16
|
+
const found = substitutes[key];
|
|
17
|
+
if (found === void 0 || found.length === 0) {
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
next = next.replace(token, found);
|
|
21
|
+
}
|
|
22
|
+
return next;
|
|
23
|
+
}
|
|
24
|
+
function catalog_client_entry(name, substitutes = {}) {
|
|
25
|
+
const manifest = catalog_by_name(name);
|
|
26
|
+
if (manifest === void 0) {
|
|
27
|
+
return `unknown mcp catalog entry '${name}'`;
|
|
28
|
+
}
|
|
29
|
+
if (manifest.transport !== "stdio" || manifest.command === void 0) {
|
|
30
|
+
return manifest.note ?? `${name} has no official mcp server to enable`;
|
|
31
|
+
}
|
|
32
|
+
const args = [];
|
|
33
|
+
for (const arg of manifest.args ?? []) {
|
|
34
|
+
const expanded = expand(arg, substitutes);
|
|
35
|
+
if (expanded === void 0) {
|
|
36
|
+
return `mcp catalog '${name}' needs a substitute for ${arg}`;
|
|
37
|
+
}
|
|
38
|
+
args.push(expanded);
|
|
39
|
+
}
|
|
40
|
+
return { enabled: false, command: manifest.command, args };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/index.ts
|
|
44
|
+
function read_package_version() {
|
|
45
|
+
const pkg_path = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
46
|
+
const pkg = JSON.parse(readFileSync(pkg_path, "utf8"));
|
|
47
|
+
if (typeof pkg.version !== "string" || pkg.version.length === 0) {
|
|
48
|
+
throw new Error("package.json is missing version");
|
|
49
|
+
}
|
|
50
|
+
return pkg.version;
|
|
51
|
+
}
|
|
52
|
+
var LICH_VERSION = read_package_version();
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
catalog_client_entry,
|
|
56
|
+
LICH_VERSION
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=chunk-SAEB3QL3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/mcp/mcp_catalog_entry.ts"],"sourcesContent":["/**\n * Public surface of the lich agent harness. Pure re-exports, plus the package version.\n */\nimport { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nfunction read_package_version(): string {\n const pkg_path = fileURLToPath(new URL(\"../package.json\", import.meta.url));\n const pkg = JSON.parse(readFileSync(pkg_path, \"utf8\")) as { version?: unknown };\n if (typeof pkg.version !== \"string\" || pkg.version.length === 0) {\n throw new Error(\"package.json is missing version\");\n }\n return pkg.version;\n}\n\nexport const LICH_VERSION = read_package_version();\n\nexport { Agent, create_agent, run_agent, create_agent_with_plugins } from \"./agent/agent.js\";\nexport type { AgentRunOptions, AgentRunResult } from \"./agent/agent.js\";\nexport type { AgentConfig } from \"./agent/config.js\";\nexport { parse_agent_config } from \"./agent/config.js\";\nexport { catalog_client_entry } from \"./mcp/mcp_catalog_entry.js\";\nexport { AgentEmitter } from \"./agent/events.js\";\nexport type { AgentEvent, AgentEventHandler, AgentEvents } from \"./agent/events.js\";\nexport type { LoopOutcome, LoopParams } from \"./agent/loop.js\";\nexport type {\n ChatOptions,\n ChatResult,\n Message,\n ProviderConfig,\n ToolCall,\n ToolDefinition,\n Usage,\n} from \"./providers/types.js\";\nexport { ProviderError } from \"./providers/types.js\";\nexport { ToolExecutor } from \"./tools/executor.js\";\nexport { ToolRegistry } from \"./tools/registry.js\";\nexport type { Tool, ToolContext, ToolResult } from \"./tools/types.js\";\nexport { register_builtin_tools } from \"./tools/builtin/index.js\";\nexport { HookedToolRunner } from \"./plugins/hooks.js\";\nexport { load_plugins, plugin_errors_summary } from \"./plugins/loader.js\";\nexport type { LoadedPlugin, PluginLoadError } from \"./plugins/loader.js\";\nexport type {\n AfterToolCallInfo,\n BeforeToolCallInfo,\n BeforeToolCallResult,\n HookContext,\n Plugin,\n PluginHooks,\n RunEndInfo,\n} from \"./plugins/types.js\";","/**\n * Install shape: turn a catalog manifest into a disabled mcp_servers entry.\n * Does not spawn, download, or copy secrets.\n */\nimport { catalog_by_name } from \"./mcp_catalog.js\";\n\nconst PLACEHOLDER = /\\$\\{([a-z_]+)\\}/g;\n\nfunction expand(value: string, substitutes: Record<string, string>): string | undefined {\n const tokens = value.match(PLACEHOLDER) ?? [];\n let next = value;\n for (const token of tokens) {\n const key = token.slice(2, -1);\n const found = substitutes[key];\n if (found === undefined || found.length === 0) {\n return undefined;\n }\n next = next.replace(token, found);\n }\n return next;\n}\n\n/** Write the client entry. The user sets enabled. Godot has nothing to write. */\nexport function catalog_client_entry(\n name: string,\n substitutes: Record<string, string> = {},\n): Record<string, unknown> | string {\n const manifest = catalog_by_name(name);\n if (manifest === undefined) {\n return `unknown mcp catalog entry '${name}'`;\n }\n if (manifest.transport !== \"stdio\" || manifest.command === undefined) {\n return manifest.note ?? `${name} has no official mcp server to enable`;\n }\n const args: string[] = [];\n for (const arg of manifest.args ?? []) {\n const expanded = expand(arg, substitutes);\n if (expanded === undefined) {\n return `mcp catalog '${name}' needs a substitute for ${arg}`;\n }\n args.push(expanded);\n }\n return { enabled: false, command: manifest.command, args };\n}\n"],"mappings":";;;;;AAGA,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;;;ACE9B,IAAM,cAAc;AAEpB,SAAS,OAAO,OAAe,aAAyD;AACtF,QAAM,SAAS,MAAM,MAAM,WAAW,KAAK,CAAC;AAC5C,MAAI,OAAO;AACX,aAAW,SAAS,QAAQ;AAC1B,UAAM,MAAM,MAAM,MAAM,GAAG,EAAE;AAC7B,UAAM,QAAQ,YAAY,GAAG;AAC7B,QAAI,UAAU,UAAa,MAAM,WAAW,GAAG;AAC7C,aAAO;AAAA,IACT;AACA,WAAO,KAAK,QAAQ,OAAO,KAAK;AAAA,EAClC;AACA,SAAO;AACT;AAGO,SAAS,qBACd,MACA,cAAsC,CAAC,GACL;AAClC,QAAM,WAAW,gBAAgB,IAAI;AACrC,MAAI,aAAa,QAAW;AAC1B,WAAO,8BAA8B,IAAI;AAAA,EAC3C;AACA,MAAI,SAAS,cAAc,WAAW,SAAS,YAAY,QAAW;AACpE,WAAO,SAAS,QAAQ,GAAG,IAAI;AAAA,EACjC;AACA,QAAM,OAAiB,CAAC;AACxB,aAAW,OAAO,SAAS,QAAQ,CAAC,GAAG;AACrC,UAAM,WAAW,OAAO,KAAK,WAAW;AACxC,QAAI,aAAa,QAAW;AAC1B,aAAO,gBAAgB,IAAI,4BAA4B,GAAG;AAAA,IAC5D;AACA,SAAK,KAAK,QAAQ;AAAA,EACpB;AACA,SAAO,EAAE,SAAS,OAAO,SAAS,SAAS,SAAS,KAAK;AAC3D;;;ADrCA,SAAS,uBAA+B;AACtC,QAAM,WAAW,cAAc,IAAI,IAAI,mBAAmB,YAAY,GAAG,CAAC;AAC1E,QAAM,MAAM,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AACrD,MAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,WAAW,GAAG;AAC/D,UAAM,IAAI,MAAM,iCAAiC;AAAA,EACnD;AACA,SAAO,IAAI;AACb;AAEO,IAAM,eAAe,qBAAqB;","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
load_theme,
|
|
4
|
+
notice_flavor
|
|
5
|
+
} from "./chunk-JYURFAGB.js";
|
|
6
|
+
import {
|
|
7
|
+
LICH_VERSION,
|
|
8
|
+
catalog_client_entry
|
|
9
|
+
} from "./chunk-SAEB3QL3.js";
|
|
5
10
|
import {
|
|
6
11
|
DEFAULT_GATEWAY_TOKEN_ENVS,
|
|
7
12
|
create_agent_with_plugins,
|
|
8
13
|
is_env_var_name,
|
|
9
14
|
parse_agent_config,
|
|
15
|
+
refuse_mcp_entry,
|
|
10
16
|
safe_json_parse
|
|
11
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-QVJCIZIF.js";
|
|
12
18
|
|
|
13
19
|
// src/cli.ts
|
|
14
|
-
import { existsSync as
|
|
20
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, realpathSync } from "fs";
|
|
15
21
|
import { createInterface } from "readline";
|
|
16
22
|
import path4 from "path";
|
|
17
23
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
@@ -60,7 +66,7 @@ function config_template() {
|
|
|
60
66
|
if (kind === "ollama") {
|
|
61
67
|
provider["base_url"] = "http://localhost:11434";
|
|
62
68
|
}
|
|
63
|
-
return JSON.stringify({ providers: [provider], max_turns: 25 }, null, 2);
|
|
69
|
+
return JSON.stringify({ providers: [provider], max_turns: 25, theme: "lich" }, null, 2);
|
|
64
70
|
}
|
|
65
71
|
function ensure_lich_config_dir(work_dir) {
|
|
66
72
|
const dir = path.resolve(work_dir, LICH_DIRNAME);
|
|
@@ -85,22 +91,26 @@ function starter_config_object() {
|
|
|
85
91
|
}
|
|
86
92
|
return parsed;
|
|
87
93
|
}
|
|
88
|
-
function write_lich_config(work_dir, config) {
|
|
94
|
+
function write_lich_config(work_dir, config, update = false) {
|
|
89
95
|
ensure_lich_config_dir(work_dir);
|
|
90
96
|
const file = project_config_path(work_dir);
|
|
91
|
-
if (existsSync(file) === true) {
|
|
97
|
+
if (existsSync(file) === true && update !== true) {
|
|
92
98
|
return already_exists(file);
|
|
93
99
|
}
|
|
94
100
|
try {
|
|
95
101
|
writeFileSync(file, `${JSON.stringify(config, null, 2)}
|
|
96
|
-
`, {
|
|
102
|
+
`, {
|
|
103
|
+
encoding: "utf8",
|
|
104
|
+
flag: update === true ? "w" : "wx"
|
|
105
|
+
});
|
|
97
106
|
} catch (error) {
|
|
98
|
-
if (is_eexist(error) === true) {
|
|
107
|
+
if (update !== true && is_eexist(error) === true) {
|
|
99
108
|
return already_exists(file);
|
|
100
109
|
}
|
|
101
110
|
throw error;
|
|
102
111
|
}
|
|
103
|
-
|
|
112
|
+
const verb = update === true ? "updated" : "wrote";
|
|
113
|
+
return { path: file, written: true, message: `${verb} ${file}` };
|
|
104
114
|
}
|
|
105
115
|
function already_exists(file) {
|
|
106
116
|
return {
|
|
@@ -113,9 +123,211 @@ function is_eexist(error) {
|
|
|
113
123
|
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
114
124
|
}
|
|
115
125
|
|
|
126
|
+
// src/cli_mcp_add.ts
|
|
127
|
+
var SERVER_NAME = /^[a-z][a-z0-9_]*$/;
|
|
128
|
+
function require_server_name(name, action) {
|
|
129
|
+
if (name === void 0 || SERVER_NAME.test(name) === false) {
|
|
130
|
+
throw new Error(`mcp ${action} requires a snake_case name`);
|
|
131
|
+
}
|
|
132
|
+
return name;
|
|
133
|
+
}
|
|
134
|
+
function entry_from_flags(name, flags) {
|
|
135
|
+
if (flags.command !== void 0 && flags.url !== void 0) {
|
|
136
|
+
throw new Error("mcp add accepts --command or --url, not both");
|
|
137
|
+
}
|
|
138
|
+
if (flags.url !== void 0) {
|
|
139
|
+
if (flags.args.length > 0) {
|
|
140
|
+
throw new Error("mcp add --url does not take --arg");
|
|
141
|
+
}
|
|
142
|
+
return { enabled: false, url: flags.url };
|
|
143
|
+
}
|
|
144
|
+
if (flags.command === void 0) {
|
|
145
|
+
throw new Error("mcp add needs a catalog entry, --command, or --url");
|
|
146
|
+
}
|
|
147
|
+
return { enabled: false, command: flags.command, args: [...flags.args] };
|
|
148
|
+
}
|
|
149
|
+
function entry_from_catalog(name, flags) {
|
|
150
|
+
const substitutes = {};
|
|
151
|
+
if (flags.project_path !== void 0) {
|
|
152
|
+
substitutes["project_path"] = flags.project_path;
|
|
153
|
+
}
|
|
154
|
+
const built = catalog_client_entry(name, substitutes);
|
|
155
|
+
if (typeof built === "string") {
|
|
156
|
+
throw new Error(built);
|
|
157
|
+
}
|
|
158
|
+
return built;
|
|
159
|
+
}
|
|
160
|
+
function build_mcp_entry(name, flags) {
|
|
161
|
+
const entry = flags.command !== void 0 || flags.url !== void 0 ? entry_from_flags(name, flags) : entry_from_catalog(name, flags);
|
|
162
|
+
const refused = refuse_mcp_entry(name, {
|
|
163
|
+
command: typeof entry["command"] === "string" ? entry["command"] : void 0,
|
|
164
|
+
args: Array.isArray(entry["args"]) ? entry["args"].filter((arg) => typeof arg === "string") : [],
|
|
165
|
+
url: typeof entry["url"] === "string" ? entry["url"] : void 0
|
|
166
|
+
});
|
|
167
|
+
if (refused !== void 0) {
|
|
168
|
+
throw new Error(refused);
|
|
169
|
+
}
|
|
170
|
+
return entry;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/cli_mcp_store.ts
|
|
174
|
+
import { existsSync as existsSync2 } from "fs";
|
|
175
|
+
function read_work_config(work_dir) {
|
|
176
|
+
const file = project_config_path(work_dir);
|
|
177
|
+
if (existsSync2(file) === false) {
|
|
178
|
+
return {};
|
|
179
|
+
}
|
|
180
|
+
return load_config(file) ?? {};
|
|
181
|
+
}
|
|
182
|
+
function mcp_servers_of(config) {
|
|
183
|
+
const raw = config["mcp_servers"];
|
|
184
|
+
if (raw === void 0) {
|
|
185
|
+
return {};
|
|
186
|
+
}
|
|
187
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw) === true) {
|
|
188
|
+
throw new Error("mcp_servers must be an object");
|
|
189
|
+
}
|
|
190
|
+
const servers = {};
|
|
191
|
+
for (const [name, entry] of Object.entries(raw)) {
|
|
192
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry) === true) {
|
|
193
|
+
throw new Error(`mcp server '${name}' is not an object`);
|
|
194
|
+
}
|
|
195
|
+
servers[name] = { ...entry };
|
|
196
|
+
}
|
|
197
|
+
return servers;
|
|
198
|
+
}
|
|
199
|
+
function save_mcp_servers(work_dir, current, servers) {
|
|
200
|
+
const next = { ...current };
|
|
201
|
+
if (Object.keys(servers).length === 0) {
|
|
202
|
+
delete next["mcp_servers"];
|
|
203
|
+
} else {
|
|
204
|
+
next["mcp_servers"] = servers;
|
|
205
|
+
}
|
|
206
|
+
const result = write_lich_config(work_dir, next, true);
|
|
207
|
+
if (result.written !== true) {
|
|
208
|
+
throw new Error("mcp config was not written");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function list_mcp_lines(servers) {
|
|
212
|
+
const lines = [];
|
|
213
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
214
|
+
const transport = typeof entry["url"] === "string" ? "http" : "stdio";
|
|
215
|
+
const enabled = entry["enabled"] === true ? "true" : "false";
|
|
216
|
+
lines.push(`${name} ${transport} ${enabled}`);
|
|
217
|
+
}
|
|
218
|
+
return lines;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/cli_mcp_edit.ts
|
|
222
|
+
function require_entry(servers, name) {
|
|
223
|
+
const entry = servers[name];
|
|
224
|
+
if (entry === void 0) {
|
|
225
|
+
throw new Error(`mcp server '${name}' not found`);
|
|
226
|
+
}
|
|
227
|
+
return entry;
|
|
228
|
+
}
|
|
229
|
+
function set_mcp_enabled(work_dir, name, enabled) {
|
|
230
|
+
const server = require_server_name(name, enabled === true ? "enable" : "disable");
|
|
231
|
+
const current = read_work_config(work_dir);
|
|
232
|
+
const servers = mcp_servers_of(current);
|
|
233
|
+
const entry = require_entry(servers, server);
|
|
234
|
+
servers[server] = { ...entry, enabled };
|
|
235
|
+
save_mcp_servers(work_dir, current, servers);
|
|
236
|
+
return enabled === true ? `enabled ${server}` : `disabled ${server}`;
|
|
237
|
+
}
|
|
238
|
+
function remove_mcp_server(work_dir, name) {
|
|
239
|
+
const server = require_server_name(name, "remove");
|
|
240
|
+
const current = read_work_config(work_dir);
|
|
241
|
+
const servers = mcp_servers_of(current);
|
|
242
|
+
require_entry(servers, server);
|
|
243
|
+
delete servers[server];
|
|
244
|
+
save_mcp_servers(work_dir, current, servers);
|
|
245
|
+
return `removed ${server}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/cli_mcp.ts
|
|
249
|
+
function write_line(line) {
|
|
250
|
+
process.stdout.write(`${line}
|
|
251
|
+
`);
|
|
252
|
+
}
|
|
253
|
+
function add_mcp_server(work_dir, name, flags) {
|
|
254
|
+
const server = require_server_name(name, "add");
|
|
255
|
+
const current = read_work_config(work_dir);
|
|
256
|
+
const servers = mcp_servers_of(current);
|
|
257
|
+
if (servers[server] !== void 0) {
|
|
258
|
+
throw new Error(`mcp server '${server}' already exists`);
|
|
259
|
+
}
|
|
260
|
+
servers[server] = build_mcp_entry(server, flags);
|
|
261
|
+
save_mcp_servers(work_dir, current, servers);
|
|
262
|
+
return `added ${server} disabled`;
|
|
263
|
+
}
|
|
264
|
+
function run_mcp(work_dir, positionals, flags) {
|
|
265
|
+
const action = positionals[1];
|
|
266
|
+
if (positionals.length > 3) {
|
|
267
|
+
throw new Error("mcp takes one name");
|
|
268
|
+
}
|
|
269
|
+
if (action === "list") {
|
|
270
|
+
if (positionals[2] !== void 0) {
|
|
271
|
+
throw new Error("mcp list takes no name");
|
|
272
|
+
}
|
|
273
|
+
const lines = list_mcp_lines(mcp_servers_of(read_work_config(work_dir)));
|
|
274
|
+
if (lines.length > 0) {
|
|
275
|
+
process.stdout.write(`${lines.join("\n")}
|
|
276
|
+
`);
|
|
277
|
+
}
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
if (action === "add") {
|
|
281
|
+
write_line(add_mcp_server(work_dir, positionals[2], flags));
|
|
282
|
+
return 0;
|
|
283
|
+
}
|
|
284
|
+
if (action === "enable" || action === "disable") {
|
|
285
|
+
write_line(set_mcp_enabled(work_dir, positionals[2], action === "enable"));
|
|
286
|
+
return 0;
|
|
287
|
+
}
|
|
288
|
+
if (action === "remove") {
|
|
289
|
+
write_line(remove_mcp_server(work_dir, positionals[2]));
|
|
290
|
+
return 0;
|
|
291
|
+
}
|
|
292
|
+
throw new Error("mcp requires list, add, enable, disable, or remove");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/cli_mcp_flags.ts
|
|
296
|
+
function empty_mcp_flags() {
|
|
297
|
+
return { args: [] };
|
|
298
|
+
}
|
|
299
|
+
function take_mcp_flag(argv, index, flags) {
|
|
300
|
+
const arg = argv[index];
|
|
301
|
+
if (arg === "--arg") {
|
|
302
|
+
const value2 = argv[index + 1];
|
|
303
|
+
if (value2 === void 0) {
|
|
304
|
+
throw new Error("--arg requires a value");
|
|
305
|
+
}
|
|
306
|
+
flags.args.push(value2);
|
|
307
|
+
return index + 1;
|
|
308
|
+
}
|
|
309
|
+
if (arg !== "--command" && arg !== "--url" && arg !== "--project-path") {
|
|
310
|
+
return void 0;
|
|
311
|
+
}
|
|
312
|
+
const value = argv[index + 1];
|
|
313
|
+
if (value === void 0 || value.startsWith("--") === true) {
|
|
314
|
+
throw new Error(`${arg} requires a value`);
|
|
315
|
+
}
|
|
316
|
+
if (arg === "--command") {
|
|
317
|
+
flags.command = value;
|
|
318
|
+
}
|
|
319
|
+
if (arg === "--url") {
|
|
320
|
+
flags.url = value;
|
|
321
|
+
}
|
|
322
|
+
if (arg === "--project-path") {
|
|
323
|
+
flags.project_path = value;
|
|
324
|
+
}
|
|
325
|
+
return index + 1;
|
|
326
|
+
}
|
|
327
|
+
|
|
116
328
|
// src/cli_update.ts
|
|
117
329
|
import { spawn } from "child_process";
|
|
118
|
-
import { existsSync as
|
|
330
|
+
import { existsSync as existsSync3 } from "fs";
|
|
119
331
|
import path2 from "path";
|
|
120
332
|
var PACKAGE_NAME = "@moikapy/lich";
|
|
121
333
|
var VIEW_ARGS = ["view", PACKAGE_NAME, "version"];
|
|
@@ -347,7 +559,7 @@ function bun_command_runner(command, args) {
|
|
|
347
559
|
async function run_update(module_path) {
|
|
348
560
|
return run_lich_update({
|
|
349
561
|
installed_version: LICH_VERSION,
|
|
350
|
-
install_kind: detect_install_kind(module_path, process.env, (dir) =>
|
|
562
|
+
install_kind: detect_install_kind(module_path, process.env, (dir) => existsSync3(path2.join(dir, ".git"))),
|
|
351
563
|
run_command: default_command_runner,
|
|
352
564
|
write_stdout: (text) => {
|
|
353
565
|
process.stdout.write(text);
|
|
@@ -576,7 +788,8 @@ var FLAG_KEYS = {
|
|
|
576
788
|
"--api-key-env": "provider_api_key_env",
|
|
577
789
|
"--system-prompt": "system_prompt",
|
|
578
790
|
"--session-dir": "session_dir",
|
|
579
|
-
"--log-level": "log_level"
|
|
791
|
+
"--log-level": "log_level",
|
|
792
|
+
"--theme": "theme"
|
|
580
793
|
};
|
|
581
794
|
var DEFAULT_BASE_URLS = {
|
|
582
795
|
openai_compat: "https://api.openai.com/v1",
|
|
@@ -591,7 +804,7 @@ var DEFAULT_ENV_API_KEYS = {
|
|
|
591
804
|
};
|
|
592
805
|
function usage_text() {
|
|
593
806
|
return [
|
|
594
|
-
"lich \u2014
|
|
807
|
+
"lich \u2014 the undead agent harness",
|
|
595
808
|
"",
|
|
596
809
|
"Usage:",
|
|
597
810
|
" lich open the TUI (first run: setup wizard, then TUI)",
|
|
@@ -602,6 +815,9 @@ function usage_text() {
|
|
|
602
815
|
" lich gateway <plat..> messaging gateway (webhook|telegram|discord|twitch)",
|
|
603
816
|
" lich config print a starter config template (save as .lich/config.json)",
|
|
604
817
|
" lich update install a newer @moikapy/lich from npm, if one exists",
|
|
818
|
+
" lich mcp list list mcp servers in .lich/config.json",
|
|
819
|
+
" lich mcp add <name> add a catalog or --command/--url server (disabled)",
|
|
820
|
+
" lich mcp enable <name> / disable <name> / remove <name>",
|
|
605
821
|
" lich --help show this help",
|
|
606
822
|
" lich --version print version",
|
|
607
823
|
"",
|
|
@@ -615,7 +831,12 @@ function usage_text() {
|
|
|
615
831
|
" --api-key-env <NAME> env var holding the api key (default LICH_API_KEY_ENV; unused by ollama)",
|
|
616
832
|
" --system-prompt <s> system prompt override",
|
|
617
833
|
" --session-dir <path> session transcript directory",
|
|
618
|
-
" --log-level <level> debug | info | warn | error"
|
|
834
|
+
" --log-level <level> debug | info | warn | error",
|
|
835
|
+
" --theme <name> display theme (default lich; files in ~/.lich/themes)",
|
|
836
|
+
" --command <bin> mcp add: local stdio binary",
|
|
837
|
+
" --arg <value> mcp add: repeatable stdio arg (may start with --)",
|
|
838
|
+
" --url <url> mcp add: loopback http url",
|
|
839
|
+
" --project-path <path> mcp add: catalog ${project_path} substitute"
|
|
619
840
|
].join("\n");
|
|
620
841
|
}
|
|
621
842
|
function error_message(error) {
|
|
@@ -634,7 +855,8 @@ function error_for_mode(mode, base_message) {
|
|
|
634
855
|
return base_message;
|
|
635
856
|
}
|
|
636
857
|
function parse_args(argv) {
|
|
637
|
-
const options = { overrides: {}, positionals: [] };
|
|
858
|
+
const options = { overrides: {}, positionals: [], mcp_flags: empty_mcp_flags() };
|
|
859
|
+
const mcp = argv.includes("mcp");
|
|
638
860
|
for (let index = 0; index < argv.length; index += 1) {
|
|
639
861
|
const arg = argv[index];
|
|
640
862
|
if (arg === void 0) {
|
|
@@ -657,6 +879,13 @@ function parse_args(argv) {
|
|
|
657
879
|
index += 1;
|
|
658
880
|
continue;
|
|
659
881
|
}
|
|
882
|
+
if (mcp === true) {
|
|
883
|
+
const consumed = take_mcp_flag(argv, index, options.mcp_flags);
|
|
884
|
+
if (consumed !== void 0) {
|
|
885
|
+
index = consumed;
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
660
889
|
const override_key = FLAG_KEYS[arg];
|
|
661
890
|
if (override_key === void 0) {
|
|
662
891
|
if (arg.startsWith("--") === true) {
|
|
@@ -728,7 +957,7 @@ function apply_provider_override(config, overrides) {
|
|
|
728
957
|
}
|
|
729
958
|
}
|
|
730
959
|
function apply_overrides(config, overrides) {
|
|
731
|
-
for (const key of ["work_dir", "system_prompt", "session_dir", "log_level"]) {
|
|
960
|
+
for (const key of ["work_dir", "system_prompt", "session_dir", "log_level", "theme"]) {
|
|
732
961
|
if (overrides[key] !== void 0) {
|
|
733
962
|
config[key] = overrides[key];
|
|
734
963
|
}
|
|
@@ -785,6 +1014,7 @@ function attach_progress(emitter) {
|
|
|
785
1014
|
}
|
|
786
1015
|
async function run_one_shot(config, input) {
|
|
787
1016
|
const agent = await create_agent_with_plugins(config);
|
|
1017
|
+
const theme = load_theme(agent.config.theme);
|
|
788
1018
|
const stop_progress = attach_progress(agent.events);
|
|
789
1019
|
let result;
|
|
790
1020
|
try {
|
|
@@ -798,8 +1028,7 @@ async function run_one_shot(config, input) {
|
|
|
798
1028
|
`);
|
|
799
1029
|
}
|
|
800
1030
|
if (result.outcome.stopped_reason === "budget") {
|
|
801
|
-
process.stderr.write(
|
|
802
|
-
`);
|
|
1031
|
+
process.stderr.write(budget_stderr_line(result.outcome.turns_used, theme.notices.budget_exhausted));
|
|
803
1032
|
return 1;
|
|
804
1033
|
}
|
|
805
1034
|
if (result.outcome.stopped_reason === "aborted") {
|
|
@@ -836,8 +1065,15 @@ function ask_line2(rl) {
|
|
|
836
1065
|
rl.once("close", on_close);
|
|
837
1066
|
});
|
|
838
1067
|
}
|
|
1068
|
+
function budget_stderr_line(turns, notice) {
|
|
1069
|
+
const flavor = notice_flavor(notice);
|
|
1070
|
+
const suffix = flavor.length === 0 ? "" : ` \u2014 ${flavor}`;
|
|
1071
|
+
return `[lich] budget exhausted after ${turns} turns${suffix}
|
|
1072
|
+
`;
|
|
1073
|
+
}
|
|
839
1074
|
async function run_chat(config) {
|
|
840
1075
|
const agent = await create_agent_with_plugins(config);
|
|
1076
|
+
const theme = load_theme(agent.config.theme);
|
|
841
1077
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
842
1078
|
try {
|
|
843
1079
|
for (; ; ) {
|
|
@@ -847,6 +1083,8 @@ async function run_chat(config) {
|
|
|
847
1083
|
}
|
|
848
1084
|
const command = line.trim();
|
|
849
1085
|
if (command === "/exit" || command === "/quit") {
|
|
1086
|
+
process.stdout.write(`${theme.glyph} ${theme.goodbye}
|
|
1087
|
+
`);
|
|
850
1088
|
return 0;
|
|
851
1089
|
}
|
|
852
1090
|
await run_chat_turn(agent, command);
|
|
@@ -940,7 +1178,7 @@ function run_init(options) {
|
|
|
940
1178
|
return 0;
|
|
941
1179
|
}
|
|
942
1180
|
async function run_tui_entry(config) {
|
|
943
|
-
const { run_tui } = await import("./tui-
|
|
1181
|
+
const { run_tui } = await import("./tui-L6RABP2J.js");
|
|
944
1182
|
return run_tui(config);
|
|
945
1183
|
}
|
|
946
1184
|
function gateway_platforms(config, cli_platforms) {
|
|
@@ -951,7 +1189,7 @@ function gateway_platforms(config, cli_platforms) {
|
|
|
951
1189
|
return configured.length === 0 ? ["webhook"] : configured;
|
|
952
1190
|
}
|
|
953
1191
|
async function run_gateway_entry(config, platforms) {
|
|
954
|
-
const { run_gateway } = await import("./gateway-
|
|
1192
|
+
const { run_gateway } = await import("./gateway-5BG3YCZF.js");
|
|
955
1193
|
return run_gateway(config, gateway_platforms(config, platforms));
|
|
956
1194
|
}
|
|
957
1195
|
function is_cli_entry(input) {
|
|
@@ -979,7 +1217,7 @@ function node_entry_matches(module_url, entry) {
|
|
|
979
1217
|
return same_real_file(fileURLToPath(module_url), resolved);
|
|
980
1218
|
}
|
|
981
1219
|
function same_real_file(left, right) {
|
|
982
|
-
if (
|
|
1220
|
+
if (existsSync4(left) === false || existsSync4(right) === false) {
|
|
983
1221
|
return false;
|
|
984
1222
|
}
|
|
985
1223
|
return realpathSync(left) === realpathSync(right);
|
|
@@ -1008,6 +1246,9 @@ async function run_cli(argv) {
|
|
|
1008
1246
|
`);
|
|
1009
1247
|
return 0;
|
|
1010
1248
|
}
|
|
1249
|
+
if (first === "mcp") {
|
|
1250
|
+
return run_mcp(work_dir_of(options), options.positionals, options.mcp_flags);
|
|
1251
|
+
}
|
|
1011
1252
|
if (first === "update") {
|
|
1012
1253
|
if (options.positionals.length > 1) {
|
|
1013
1254
|
throw new Error("update takes no arguments");
|