@moikapy/lich 0.3.1 → 0.5.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 +42 -0
- package/README.md +12 -2
- package/dist/{chunk-P52U5M3L.js → chunk-CV2YH3FH.js} +449 -71
- package/dist/chunk-CV2YH3FH.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +590 -25
- package/dist/cli.js.map +1 -1
- package/dist/{gateway-CWPVIU3W.js → gateway-W6S43ETE.js} +27 -18
- package/dist/gateway-W6S43ETE.js.map +1 -0
- package/dist/index.d.ts +58 -11
- package/dist/index.js +1 -1
- package/dist/{tui-K3EPRXTV.js → tui-DT7XWDTX.js} +8 -5
- package/dist/tui-DT7XWDTX.js.map +1 -0
- package/docs/architecture/overview.md +8 -6
- package/docs/architecture/plugins.md +57 -4
- package/docs/architecture/tools.md +16 -4
- package/docs/getting-started.md +15 -4
- package/docs/index.md +4 -3
- package/docs/user-guide/cli.md +24 -3
- package/docs/user-guide/godot.md +160 -0
- package/docs/user-guide/library.md +4 -2
- package/docs/user-guide/plugins.md +79 -5
- package/docs/user-guide/tui.md +4 -3
- package/examples/game_bridge/README.md +68 -0
- package/examples/game_bridge/bridge_io.mjs +60 -0
- package/examples/game_bridge/bridge_paths.mjs +11 -0
- package/examples/game_bridge/dungeon_memory.mjs +56 -0
- package/examples/game_bridge/enemy_actions.mjs +44 -0
- package/examples/game_bridge/game_bridge.plugin.mjs +17 -0
- package/examples/game_bridge/meteor_veto.mjs +22 -0
- package/examples/game_bridge/schemas.mjs +48 -0
- package/examples/game_bridge/snapshot.mjs +19 -0
- package/examples/game_bridge/validate_order.mjs +44 -0
- package/package.json +2 -1
- package/dist/chunk-P52U5M3L.js.map +0 -1
- package/dist/gateway-CWPVIU3W.js.map +0 -1
- package/dist/tui-K3EPRXTV.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -3,19 +3,21 @@ import {
|
|
|
3
3
|
LICH_VERSION
|
|
4
4
|
} from "./chunk-ZVK3MUPC.js";
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
DEFAULT_GATEWAY_TOKEN_ENVS,
|
|
7
|
+
create_agent_with_plugins,
|
|
8
|
+
is_env_var_name,
|
|
7
9
|
parse_agent_config,
|
|
8
10
|
safe_json_parse
|
|
9
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-CV2YH3FH.js";
|
|
10
12
|
|
|
11
13
|
// src/cli.ts
|
|
12
14
|
import { readFileSync as readFileSync2 } from "fs";
|
|
13
15
|
import { createInterface } from "readline";
|
|
14
|
-
import
|
|
15
|
-
import { pathToFileURL } from "url";
|
|
16
|
+
import path4 from "path";
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
16
18
|
|
|
17
19
|
// src/cli_config.ts
|
|
18
|
-
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
19
21
|
import { homedir } from "os";
|
|
20
22
|
import path from "path";
|
|
21
23
|
var LICH_DIRNAME = ".lich";
|
|
@@ -34,12 +36,7 @@ function find_config_file(explicit) {
|
|
|
34
36
|
}
|
|
35
37
|
return explicit;
|
|
36
38
|
}
|
|
37
|
-
|
|
38
|
-
if (existsSync(candidate) === true) {
|
|
39
|
-
return candidate;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return void 0;
|
|
39
|
+
return existing_config_path(process.cwd());
|
|
43
40
|
}
|
|
44
41
|
function load_config(explicit) {
|
|
45
42
|
const found = find_config_file(explicit);
|
|
@@ -65,6 +62,461 @@ function config_template() {
|
|
|
65
62
|
}
|
|
66
63
|
return JSON.stringify({ providers: [provider], max_turns: 25 }, null, 2);
|
|
67
64
|
}
|
|
65
|
+
function ensure_lich_config_dir(work_dir) {
|
|
66
|
+
const dir = path.resolve(work_dir, LICH_DIRNAME);
|
|
67
|
+
mkdirSync(dir, { recursive: true });
|
|
68
|
+
return dir;
|
|
69
|
+
}
|
|
70
|
+
function project_config_path(work_dir) {
|
|
71
|
+
return path.resolve(work_dir, CONFIG_RELPATH);
|
|
72
|
+
}
|
|
73
|
+
function existing_config_path(work_dir) {
|
|
74
|
+
for (const candidate of config_search_paths(work_dir)) {
|
|
75
|
+
if (existsSync(candidate) === true) {
|
|
76
|
+
return candidate;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
function starter_config_object() {
|
|
82
|
+
const parsed = JSON.parse(config_template());
|
|
83
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
84
|
+
throw new Error("config template must be a JSON object");
|
|
85
|
+
}
|
|
86
|
+
return parsed;
|
|
87
|
+
}
|
|
88
|
+
function write_lich_config(work_dir, config) {
|
|
89
|
+
ensure_lich_config_dir(work_dir);
|
|
90
|
+
const file = project_config_path(work_dir);
|
|
91
|
+
if (existsSync(file) === true) {
|
|
92
|
+
return already_exists(file);
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
writeFileSync(file, `${JSON.stringify(config, null, 2)}
|
|
96
|
+
`, { encoding: "utf8", flag: "wx" });
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (is_eexist(error) === true) {
|
|
99
|
+
return already_exists(file);
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
return { path: file, written: true, message: `wrote ${file}` };
|
|
104
|
+
}
|
|
105
|
+
function already_exists(file) {
|
|
106
|
+
return {
|
|
107
|
+
path: file,
|
|
108
|
+
written: false,
|
|
109
|
+
message: `lich: .lich/config.json already exists at ${file}; not overwriting`
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function is_eexist(error) {
|
|
113
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/cli_update.ts
|
|
117
|
+
import { spawn } from "child_process";
|
|
118
|
+
import { existsSync as existsSync2 } from "fs";
|
|
119
|
+
import path2 from "path";
|
|
120
|
+
var PACKAGE_NAME = "@moikapy/lich";
|
|
121
|
+
var VIEW_ARGS = ["view", PACKAGE_NAME, "version"];
|
|
122
|
+
var INSTALL_ARGS = ["install", "-g", `${PACKAGE_NAME}@latest`];
|
|
123
|
+
var NPM_MISSING = "lich: npm is not on PATH. Install Node.js, or run `npm install -g @moikapy/lich@latest` once npm is available.\n";
|
|
124
|
+
var EXIT_FIRST_HINT = "Exit any running `lich tui` or `lich gateway` first \u2014 npm cannot replace the package while those processes are running.\n";
|
|
125
|
+
function parse_semver(version) {
|
|
126
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(version.trim());
|
|
127
|
+
if (match === null || match[1] === void 0 || match[2] === void 0 || match[3] === void 0) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return [Number(match[1]), Number(match[2]), Number(match[3]), match[4] ?? ""];
|
|
131
|
+
}
|
|
132
|
+
function version_relation(installed, registry) {
|
|
133
|
+
const left = parse_semver(installed);
|
|
134
|
+
const right = parse_semver(registry);
|
|
135
|
+
if (left === null || right === null) {
|
|
136
|
+
return "invalid";
|
|
137
|
+
}
|
|
138
|
+
for (let index = 0; index < 3; index += 1) {
|
|
139
|
+
const installed_part = left[index];
|
|
140
|
+
const registry_part = right[index];
|
|
141
|
+
if (typeof installed_part !== "number" || typeof registry_part !== "number") {
|
|
142
|
+
return "invalid";
|
|
143
|
+
}
|
|
144
|
+
if (installed_part < registry_part) {
|
|
145
|
+
return "update";
|
|
146
|
+
}
|
|
147
|
+
if (installed_part > registry_part) {
|
|
148
|
+
return "current";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (left[3] !== "" && right[3] === "") {
|
|
152
|
+
return "update";
|
|
153
|
+
}
|
|
154
|
+
return "current";
|
|
155
|
+
}
|
|
156
|
+
function detect_install_kind(module_path, env, has_git) {
|
|
157
|
+
if (env.npm_command === "exec" || module_path.includes(`${path2.sep}_npx${path2.sep}`)) {
|
|
158
|
+
return "npx";
|
|
159
|
+
}
|
|
160
|
+
let dir = path2.dirname(path2.resolve(module_path));
|
|
161
|
+
const root = path2.parse(dir).root;
|
|
162
|
+
while (dir !== root) {
|
|
163
|
+
if (path2.basename(dir) === "node_modules") {
|
|
164
|
+
return "npm";
|
|
165
|
+
}
|
|
166
|
+
if (has_git(dir) === true) {
|
|
167
|
+
return "git";
|
|
168
|
+
}
|
|
169
|
+
const parent = path2.dirname(dir);
|
|
170
|
+
if (parent === dir) {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
dir = parent;
|
|
174
|
+
}
|
|
175
|
+
return "npm";
|
|
176
|
+
}
|
|
177
|
+
function blocked_install_message(kind) {
|
|
178
|
+
if (kind === "git") {
|
|
179
|
+
return "lich: this copy is a git clone, not an npm install. Update with `git pull` in the repository.\n";
|
|
180
|
+
}
|
|
181
|
+
if (kind === "npx") {
|
|
182
|
+
return "lich: npx runs a temporary copy and cannot persist an update. Install with `npm install -g @moikapy/lich`.\n";
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
function registry_version(viewed, request) {
|
|
187
|
+
if (viewed.error_code === "ENOENT") {
|
|
188
|
+
request.write_stderr(NPM_MISSING);
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
if (viewed.exit_code !== 0) {
|
|
192
|
+
const detail = viewed.stderr.trim() || viewed.stdout.trim() || `exit ${viewed.exit_code}`;
|
|
193
|
+
request.write_stderr(`lich: could not read the registry version of ${PACKAGE_NAME}: ${detail}
|
|
194
|
+
`);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const registry = viewed.stdout.trim();
|
|
198
|
+
if (parse_semver(registry) === null) {
|
|
199
|
+
request.write_stderr(`lich: could not read the registry version of ${PACKAGE_NAME}: unexpected version "${registry}"
|
|
200
|
+
`);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return registry;
|
|
204
|
+
}
|
|
205
|
+
function is_permission_failure(installed) {
|
|
206
|
+
if (installed.error_code === "EACCES" || installed.error_code === "EPERM") {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
const text = `${installed.stderr}
|
|
210
|
+
${installed.stdout}`.toLowerCase();
|
|
211
|
+
return text.includes("eacces") || text.includes("eperm") || text.includes("permission denied");
|
|
212
|
+
}
|
|
213
|
+
function report_install(request, installed) {
|
|
214
|
+
if (installed.exit_code === 0) {
|
|
215
|
+
request.write_stdout(`updated ${PACKAGE_NAME}. Run \`lich --version\` in a new shell to confirm.
|
|
216
|
+
`);
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
if (installed.error_code === "ENOENT") {
|
|
220
|
+
request.write_stderr(NPM_MISSING);
|
|
221
|
+
return 1;
|
|
222
|
+
}
|
|
223
|
+
if (is_permission_failure(installed) === true) {
|
|
224
|
+
request.write_stderr("lich: npm install failed (permission denied). Use a writable npm prefix, or run `npm install -g @moikapy/lich@latest` yourself.\n");
|
|
225
|
+
return 1;
|
|
226
|
+
}
|
|
227
|
+
const detail = installed.stderr.trim() || `exit ${installed.exit_code}`;
|
|
228
|
+
request.write_stderr(`lich: npm install failed: ${detail}
|
|
229
|
+
`);
|
|
230
|
+
return 1;
|
|
231
|
+
}
|
|
232
|
+
async function install_if_newer(request, registry) {
|
|
233
|
+
const relation = version_relation(request.installed_version, registry);
|
|
234
|
+
if (relation === "invalid") {
|
|
235
|
+
request.write_stderr(`lich: could not compare versions (installed ${request.installed_version}, registry ${registry})
|
|
236
|
+
`);
|
|
237
|
+
return 1;
|
|
238
|
+
}
|
|
239
|
+
if (relation !== "update") {
|
|
240
|
+
request.write_stdout(`lich ${request.installed_version} is up to date
|
|
241
|
+
`);
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
request.write_stdout(`lich ${request.installed_version} -> ${registry}
|
|
245
|
+
`);
|
|
246
|
+
request.write_stdout(EXIT_FIRST_HINT);
|
|
247
|
+
const installed = await request.run_command("npm", INSTALL_ARGS);
|
|
248
|
+
return report_install(request, installed);
|
|
249
|
+
}
|
|
250
|
+
async function run_lich_update(request) {
|
|
251
|
+
const blocked = blocked_install_message(request.install_kind);
|
|
252
|
+
if (blocked !== null) {
|
|
253
|
+
request.write_stderr(blocked);
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
const viewed = await request.run_command("npm", VIEW_ARGS);
|
|
257
|
+
const registry = registry_version(viewed, request);
|
|
258
|
+
if (registry === null) {
|
|
259
|
+
return 1;
|
|
260
|
+
}
|
|
261
|
+
return install_if_newer(request, registry);
|
|
262
|
+
}
|
|
263
|
+
function default_command_runner(command, args) {
|
|
264
|
+
return new Promise((resolve) => {
|
|
265
|
+
let stdout = "";
|
|
266
|
+
let stderr = "";
|
|
267
|
+
let settled = false;
|
|
268
|
+
const finish = (result) => {
|
|
269
|
+
if (settled === true) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
settled = true;
|
|
273
|
+
resolve(result);
|
|
274
|
+
};
|
|
275
|
+
const forward = args[0] === "install";
|
|
276
|
+
const child = spawn(command, [...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
277
|
+
child.stdout?.on("data", (chunk) => {
|
|
278
|
+
const text = chunk.toString();
|
|
279
|
+
stdout += text;
|
|
280
|
+
if (forward === true) {
|
|
281
|
+
process.stdout.write(text);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
child.stderr?.on("data", (chunk) => {
|
|
285
|
+
const text = chunk.toString();
|
|
286
|
+
stderr += text;
|
|
287
|
+
if (forward === true) {
|
|
288
|
+
process.stderr.write(text);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
child.on("error", (error) => {
|
|
292
|
+
finish({ exit_code: 127, stdout, stderr: error.message, error_code: error.code });
|
|
293
|
+
});
|
|
294
|
+
child.on("close", (code) => {
|
|
295
|
+
finish({ exit_code: code ?? 1, stdout, stderr });
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
async function run_update(module_path) {
|
|
300
|
+
return run_lich_update({
|
|
301
|
+
installed_version: LICH_VERSION,
|
|
302
|
+
install_kind: detect_install_kind(module_path, process.env, (dir) => existsSync2(path2.join(dir, ".git"))),
|
|
303
|
+
run_command: default_command_runner,
|
|
304
|
+
write_stdout: (text) => {
|
|
305
|
+
process.stdout.write(text);
|
|
306
|
+
},
|
|
307
|
+
write_stderr: (text) => {
|
|
308
|
+
process.stderr.write(text);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/setup_wizard.ts
|
|
314
|
+
import { readdirSync } from "fs";
|
|
315
|
+
import path3 from "path";
|
|
316
|
+
var PLATFORMS = ["webhook", "telegram", "discord", "twitch"];
|
|
317
|
+
var PLUGIN_EXT = /\.(mjs|js|ts|mts|cts|jsx|tsx)$/;
|
|
318
|
+
var PROVIDER_DEFAULTS = {
|
|
319
|
+
ollama: { model: "llama3.2", base_url: "http://localhost:11434" },
|
|
320
|
+
openai_compat: { model: "gpt-4.1-mini", base_url: "https://api.openai.com/v1", api_key_env: "OPENAI_API_KEY" },
|
|
321
|
+
anthropic: { model: "claude-sonnet-4", base_url: "https://api.anthropic.com", api_key_env: "ANTHROPIC_API_KEY" }
|
|
322
|
+
};
|
|
323
|
+
function discover_plugin_entries(work_dir) {
|
|
324
|
+
const dir = path3.resolve(work_dir, ".lich", "plugins");
|
|
325
|
+
let names;
|
|
326
|
+
try {
|
|
327
|
+
names = readdirSync(dir);
|
|
328
|
+
} catch {
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
const entries = [];
|
|
332
|
+
for (const name of names) {
|
|
333
|
+
if (PLUGIN_EXT.test(name) === true) {
|
|
334
|
+
entries.push(path3.posix.join(".lich/plugins", name));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return entries;
|
|
338
|
+
}
|
|
339
|
+
function build_setup_config(answers) {
|
|
340
|
+
const provider = {
|
|
341
|
+
kind: answers.provider_kind,
|
|
342
|
+
name: "main",
|
|
343
|
+
model: answers.model,
|
|
344
|
+
base_url: answers.base_url
|
|
345
|
+
};
|
|
346
|
+
if (answers.api_key_env !== void 0 && answers.api_key_env.length > 0) {
|
|
347
|
+
provider["api_key_env"] = answers.api_key_env;
|
|
348
|
+
}
|
|
349
|
+
const config = {
|
|
350
|
+
agent_name: answers.agent_name,
|
|
351
|
+
providers: [provider],
|
|
352
|
+
max_turns: 25,
|
|
353
|
+
plugins: answers.plugins
|
|
354
|
+
};
|
|
355
|
+
if (answers.platforms.length > 0) {
|
|
356
|
+
config["gateway"] = { platforms: answers.platforms, token_envs: answers.token_envs };
|
|
357
|
+
}
|
|
358
|
+
return config;
|
|
359
|
+
}
|
|
360
|
+
function blank_as_default(raw, fallback) {
|
|
361
|
+
const trimmed = raw.trim();
|
|
362
|
+
return trimmed.length === 0 ? fallback : trimmed;
|
|
363
|
+
}
|
|
364
|
+
function parse_kind(raw) {
|
|
365
|
+
const trimmed = raw.trim();
|
|
366
|
+
const kind = trimmed.length === 0 ? "ollama" : trimmed;
|
|
367
|
+
if (kind === "ollama" || kind === "openai_compat" || kind === "anthropic") {
|
|
368
|
+
return kind;
|
|
369
|
+
}
|
|
370
|
+
return void 0;
|
|
371
|
+
}
|
|
372
|
+
function parse_platforms(raw) {
|
|
373
|
+
const seen = /* @__PURE__ */ new Set();
|
|
374
|
+
const platforms = [];
|
|
375
|
+
for (const part of raw.split(",")) {
|
|
376
|
+
const name = part.trim().toLowerCase();
|
|
377
|
+
if (PLATFORMS.includes(name) === false || seen.has(name) === true) {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
seen.add(name);
|
|
381
|
+
platforms.push(name);
|
|
382
|
+
}
|
|
383
|
+
return platforms;
|
|
384
|
+
}
|
|
385
|
+
function ask_line(rl, prompt) {
|
|
386
|
+
return new Promise((resolve) => {
|
|
387
|
+
let settled = false;
|
|
388
|
+
const finish = (value) => {
|
|
389
|
+
if (settled === true) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
settled = true;
|
|
393
|
+
rl.removeListener("SIGINT", on_sigint);
|
|
394
|
+
rl.removeListener("close", on_close);
|
|
395
|
+
resolve(value);
|
|
396
|
+
};
|
|
397
|
+
const on_sigint = () => finish(void 0);
|
|
398
|
+
const on_close = () => finish(void 0);
|
|
399
|
+
rl.once("SIGINT", on_sigint);
|
|
400
|
+
rl.once("close", on_close);
|
|
401
|
+
rl.question(prompt, (answer) => finish(answer));
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
async function ask_name(ask) {
|
|
405
|
+
const raw = await ask("Agent name [lich]: ");
|
|
406
|
+
if (raw === void 0) {
|
|
407
|
+
return void 0;
|
|
408
|
+
}
|
|
409
|
+
return blank_as_default(raw, "lich");
|
|
410
|
+
}
|
|
411
|
+
async function ask_provider(ask, model_hint2) {
|
|
412
|
+
const kind_raw = await ask("Provider kind (ollama|openai_compat|anthropic) [ollama]: ");
|
|
413
|
+
if (kind_raw === void 0) {
|
|
414
|
+
return void 0;
|
|
415
|
+
}
|
|
416
|
+
let kind = parse_kind(kind_raw);
|
|
417
|
+
if (kind === void 0) {
|
|
418
|
+
const retry = await ask("Unknown kind. Provider kind (ollama|openai_compat|anthropic) [ollama]: ");
|
|
419
|
+
if (retry === void 0) {
|
|
420
|
+
return void 0;
|
|
421
|
+
}
|
|
422
|
+
kind = parse_kind(retry) ?? "ollama";
|
|
423
|
+
}
|
|
424
|
+
const defaults = PROVIDER_DEFAULTS[kind];
|
|
425
|
+
const model_default = model_hint2 !== void 0 && model_hint2.length > 0 ? model_hint2 : defaults.model;
|
|
426
|
+
const model_raw = await ask(`Model [${model_default}]: `);
|
|
427
|
+
if (model_raw === void 0) {
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
const url_raw = await ask(`Base URL [${defaults.base_url}]: `);
|
|
431
|
+
if (url_raw === void 0) {
|
|
432
|
+
return void 0;
|
|
433
|
+
}
|
|
434
|
+
const key_default = defaults.api_key_env ?? "";
|
|
435
|
+
const key_prompt = key_default.length === 0 ? "API key env var name (empty to skip): " : `API key env var name [${key_default}]: `;
|
|
436
|
+
const key_raw = await ask(key_prompt);
|
|
437
|
+
if (key_raw === void 0) {
|
|
438
|
+
return void 0;
|
|
439
|
+
}
|
|
440
|
+
const api_key_env = blank_as_default(key_raw, key_default);
|
|
441
|
+
return {
|
|
442
|
+
provider_kind: kind,
|
|
443
|
+
model: blank_as_default(model_raw, model_default),
|
|
444
|
+
base_url: blank_as_default(url_raw, defaults.base_url),
|
|
445
|
+
api_key_env: api_key_env.length === 0 ? void 0 : api_key_env
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function token_env_name(raw, fallback) {
|
|
449
|
+
const name = blank_as_default(raw, fallback);
|
|
450
|
+
return is_env_var_name(name) === true ? name : void 0;
|
|
451
|
+
}
|
|
452
|
+
async function ask_token_env(ask, platform, fallback) {
|
|
453
|
+
const first = await ask(`${platform} token env var [${fallback}] (name only, not the secret): `);
|
|
454
|
+
if (first === void 0) {
|
|
455
|
+
return void 0;
|
|
456
|
+
}
|
|
457
|
+
const named = token_env_name(first, fallback);
|
|
458
|
+
if (named !== void 0) {
|
|
459
|
+
return named;
|
|
460
|
+
}
|
|
461
|
+
const retry = await ask(`Env var name must match [A-Za-z_][A-Za-z0-9_]* [${fallback}]: `);
|
|
462
|
+
if (retry === void 0) {
|
|
463
|
+
return void 0;
|
|
464
|
+
}
|
|
465
|
+
return token_env_name(retry, fallback);
|
|
466
|
+
}
|
|
467
|
+
async function ask_gateway(ask) {
|
|
468
|
+
const raw = await ask("Gateway platforms (webhook,telegram,discord,twitch; empty to skip): ");
|
|
469
|
+
if (raw === void 0) {
|
|
470
|
+
return void 0;
|
|
471
|
+
}
|
|
472
|
+
const platforms = parse_platforms(raw);
|
|
473
|
+
const token_envs = {};
|
|
474
|
+
for (const platform of platforms) {
|
|
475
|
+
const name = await ask_token_env(ask, platform, DEFAULT_GATEWAY_TOKEN_ENVS[platform] ?? "");
|
|
476
|
+
if (name === void 0) {
|
|
477
|
+
return void 0;
|
|
478
|
+
}
|
|
479
|
+
token_envs[platform] = name;
|
|
480
|
+
}
|
|
481
|
+
return { platforms, token_envs };
|
|
482
|
+
}
|
|
483
|
+
async function ask_plugins(work_dir, ask) {
|
|
484
|
+
const found = discover_plugin_entries(work_dir);
|
|
485
|
+
if (found.length === 0) {
|
|
486
|
+
return [];
|
|
487
|
+
}
|
|
488
|
+
const enabled = [];
|
|
489
|
+
for (const entry of found) {
|
|
490
|
+
const answer = await ask(`Enable plugin ${entry}? [y/N]: `);
|
|
491
|
+
if (answer === void 0) {
|
|
492
|
+
return void 0;
|
|
493
|
+
}
|
|
494
|
+
const yes = answer.trim().toLowerCase();
|
|
495
|
+
if (yes === "y" || yes === "yes") {
|
|
496
|
+
enabled.push(entry);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return enabled;
|
|
500
|
+
}
|
|
501
|
+
async function collect_setup_answers(work_dir, ask, model_hint2) {
|
|
502
|
+
const agent_name = await ask_name(ask);
|
|
503
|
+
if (agent_name === void 0) {
|
|
504
|
+
return void 0;
|
|
505
|
+
}
|
|
506
|
+
const provider = await ask_provider(ask, model_hint2);
|
|
507
|
+
if (provider === void 0) {
|
|
508
|
+
return void 0;
|
|
509
|
+
}
|
|
510
|
+
const gateway = await ask_gateway(ask);
|
|
511
|
+
if (gateway === void 0) {
|
|
512
|
+
return void 0;
|
|
513
|
+
}
|
|
514
|
+
const plugins = await ask_plugins(work_dir, ask);
|
|
515
|
+
if (plugins === void 0) {
|
|
516
|
+
return void 0;
|
|
517
|
+
}
|
|
518
|
+
return { agent_name, ...provider, platforms: gateway.platforms, token_envs: gateway.token_envs, plugins };
|
|
519
|
+
}
|
|
68
520
|
|
|
69
521
|
// src/cli.ts
|
|
70
522
|
var FLAG_KEYS = {
|
|
@@ -94,11 +546,14 @@ function usage_text() {
|
|
|
94
546
|
"lich \u2014 a TypeScript AI agent harness",
|
|
95
547
|
"",
|
|
96
548
|
"Usage:",
|
|
549
|
+
" lich open the TUI (first run: setup wizard, then TUI)",
|
|
550
|
+
" lich init write .lich/config.json without the wizard (flags apply; never overwrites)",
|
|
97
551
|
' lich "one shot task" run a single task and print the reply',
|
|
98
552
|
" lich chat interactive chat (commands: /exit, /quit)",
|
|
99
553
|
" lich tui interactive terminal UI (ink)",
|
|
100
554
|
" lich gateway <plat..> messaging gateway (webhook|telegram|discord|twitch)",
|
|
101
555
|
" lich config print a starter config template (save as .lich/config.json)",
|
|
556
|
+
" lich update install a newer @moikapy/lich from npm, if one exists",
|
|
102
557
|
" lich --help show this help",
|
|
103
558
|
" lich --version print version",
|
|
104
559
|
"",
|
|
@@ -239,8 +694,15 @@ function apply_overrides(config, overrides) {
|
|
|
239
694
|
}
|
|
240
695
|
apply_provider_override(config, overrides);
|
|
241
696
|
}
|
|
697
|
+
function load_discovered_config(work_dir) {
|
|
698
|
+
const found = existing_config_path(work_dir);
|
|
699
|
+
if (found === void 0) {
|
|
700
|
+
return void 0;
|
|
701
|
+
}
|
|
702
|
+
return load_config(found);
|
|
703
|
+
}
|
|
242
704
|
function build_config(options) {
|
|
243
|
-
const base = options.config_path === void 0 ?
|
|
705
|
+
const base = options.config_path === void 0 ? load_discovered_config(work_dir_of(options)) ?? { providers: [env_provider(options.overrides)] } : load_config_file(options.config_path);
|
|
244
706
|
apply_overrides(base, options.overrides);
|
|
245
707
|
const providers = base["providers"];
|
|
246
708
|
if (Array.isArray(providers) === false || providers.length === 0) {
|
|
@@ -274,7 +736,7 @@ function attach_progress(emitter) {
|
|
|
274
736
|
});
|
|
275
737
|
}
|
|
276
738
|
async function run_one_shot(config, input) {
|
|
277
|
-
const agent =
|
|
739
|
+
const agent = await create_agent_with_plugins(config);
|
|
278
740
|
const stop_progress = attach_progress(agent.events);
|
|
279
741
|
let result;
|
|
280
742
|
try {
|
|
@@ -316,7 +778,7 @@ async function run_chat_turn(agent, input) {
|
|
|
316
778
|
stop_progress();
|
|
317
779
|
}
|
|
318
780
|
}
|
|
319
|
-
function
|
|
781
|
+
function ask_line2(rl) {
|
|
320
782
|
return new Promise((resolve) => {
|
|
321
783
|
const on_close = () => resolve("");
|
|
322
784
|
rl.question("> ", (answer) => {
|
|
@@ -327,11 +789,11 @@ function ask_line(rl) {
|
|
|
327
789
|
});
|
|
328
790
|
}
|
|
329
791
|
async function run_chat(config) {
|
|
330
|
-
const agent =
|
|
792
|
+
const agent = await create_agent_with_plugins(config);
|
|
331
793
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
332
794
|
try {
|
|
333
795
|
for (; ; ) {
|
|
334
|
-
const line = await
|
|
796
|
+
const line = await ask_line2(rl);
|
|
335
797
|
if (line.trim() === "") {
|
|
336
798
|
return 0;
|
|
337
799
|
}
|
|
@@ -345,22 +807,113 @@ async function run_chat(config) {
|
|
|
345
807
|
rl.close();
|
|
346
808
|
}
|
|
347
809
|
}
|
|
810
|
+
function model_hint(options) {
|
|
811
|
+
const model = options.overrides["provider_model"] ?? process.env.LICH_MODEL;
|
|
812
|
+
if (model === void 0 || model.length === 0) {
|
|
813
|
+
return void 0;
|
|
814
|
+
}
|
|
815
|
+
return model;
|
|
816
|
+
}
|
|
817
|
+
function skip_setup_message(existing) {
|
|
818
|
+
if (existing.endsWith(`${path4.sep}.lich${path4.sep}config.json`) === true) {
|
|
819
|
+
return `lich: .lich/config.json already exists at ${existing}; skipping setup`;
|
|
820
|
+
}
|
|
821
|
+
return `lich: config already exists at ${existing}; skipping setup`;
|
|
822
|
+
}
|
|
823
|
+
function work_dir_of(options) {
|
|
824
|
+
return options.overrides["work_dir"] ?? process.cwd();
|
|
825
|
+
}
|
|
826
|
+
async function offer_wizard(work_dir, hint) {
|
|
827
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
828
|
+
try {
|
|
829
|
+
const answers = await collect_setup_answers(work_dir, (prompt) => ask_line(rl, prompt), hint);
|
|
830
|
+
if (answers === void 0) {
|
|
831
|
+
return "cancelled";
|
|
832
|
+
}
|
|
833
|
+
const result = write_lich_config(work_dir, build_setup_config(answers));
|
|
834
|
+
process.stdout.write(`${result.message}
|
|
835
|
+
`);
|
|
836
|
+
return result.path;
|
|
837
|
+
} finally {
|
|
838
|
+
rl.close();
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
async function maybe_first_run(options, work_dir) {
|
|
842
|
+
if (options.config_path !== void 0) {
|
|
843
|
+
return void 0;
|
|
844
|
+
}
|
|
845
|
+
const existing = existing_config_path(work_dir);
|
|
846
|
+
if (existing !== void 0) {
|
|
847
|
+
process.stdout.write(`${skip_setup_message(existing)}
|
|
848
|
+
`);
|
|
849
|
+
options.config_path = existing;
|
|
850
|
+
return void 0;
|
|
851
|
+
}
|
|
852
|
+
const wrote = await offer_wizard(work_dir, model_hint(options));
|
|
853
|
+
if (wrote === "cancelled") {
|
|
854
|
+
process.stderr.write("lich: setup cancelled; nothing written\n");
|
|
855
|
+
return 1;
|
|
856
|
+
}
|
|
857
|
+
options.config_path = wrote;
|
|
858
|
+
return void 0;
|
|
859
|
+
}
|
|
860
|
+
async function run_bare(options) {
|
|
861
|
+
if (process.stdin.isTTY !== true) {
|
|
862
|
+
process.stderr.write("lich: stdin is not a TTY; skipping setup wizard. Run `lich init` to write .lich/config.json, or `lich --help` for usage.\n");
|
|
863
|
+
return 1;
|
|
864
|
+
}
|
|
865
|
+
const work_dir = work_dir_of(options);
|
|
866
|
+
const stopped = await maybe_first_run(options, work_dir);
|
|
867
|
+
if (stopped !== void 0) {
|
|
868
|
+
return stopped;
|
|
869
|
+
}
|
|
870
|
+
return run_tui_entry(build_config_for(options, "tui"));
|
|
871
|
+
}
|
|
872
|
+
function model_still_placeholder(config) {
|
|
873
|
+
const providers = config["providers"];
|
|
874
|
+
const first = Array.isArray(providers) === true ? providers[0] : void 0;
|
|
875
|
+
if (typeof first !== "object" || first === null) {
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
return first["model"] === "<model-name>";
|
|
879
|
+
}
|
|
880
|
+
function run_init(options) {
|
|
881
|
+
if (options.positionals.length > 1) {
|
|
882
|
+
throw new Error("init takes no extra arguments");
|
|
883
|
+
}
|
|
884
|
+
const config = starter_config_object();
|
|
885
|
+
apply_overrides(config, options.overrides);
|
|
886
|
+
const result = write_lich_config(work_dir_of(options), config);
|
|
887
|
+
process.stdout.write(`${result.message}
|
|
888
|
+
`);
|
|
889
|
+
if (result.written === true && model_still_placeholder(config) === true) {
|
|
890
|
+
process.stdout.write("next: edit the model in .lich/config.json if needed, then run `lich`\n");
|
|
891
|
+
}
|
|
892
|
+
return 0;
|
|
893
|
+
}
|
|
348
894
|
async function run_tui_entry(config) {
|
|
349
|
-
const { run_tui } = await import("./tui-
|
|
895
|
+
const { run_tui } = await import("./tui-DT7XWDTX.js");
|
|
350
896
|
return run_tui(config);
|
|
351
897
|
}
|
|
898
|
+
function gateway_platforms(config, cli_platforms) {
|
|
899
|
+
if (cli_platforms.length > 0) {
|
|
900
|
+
return cli_platforms;
|
|
901
|
+
}
|
|
902
|
+
const configured = config.gateway?.platforms ?? [];
|
|
903
|
+
return configured.length === 0 ? ["webhook"] : configured;
|
|
904
|
+
}
|
|
352
905
|
async function run_gateway_entry(config, platforms) {
|
|
353
|
-
const { run_gateway } = await import("./gateway-
|
|
354
|
-
return run_gateway(config,
|
|
906
|
+
const { run_gateway } = await import("./gateway-W6S43ETE.js");
|
|
907
|
+
return run_gateway(config, gateway_platforms(config, platforms));
|
|
355
908
|
}
|
|
356
909
|
function is_main_module() {
|
|
357
910
|
const entry = process.argv[1];
|
|
358
911
|
if (entry === void 0) {
|
|
359
912
|
return false;
|
|
360
913
|
}
|
|
361
|
-
return import.meta.url === pathToFileURL(
|
|
914
|
+
return import.meta.url === pathToFileURL(path4.resolve(entry)).href;
|
|
362
915
|
}
|
|
363
|
-
async function
|
|
916
|
+
async function run_cli(argv) {
|
|
364
917
|
const options = parse_args(argv);
|
|
365
918
|
if (options.help === true) {
|
|
366
919
|
process.stdout.write(`${usage_text()}
|
|
@@ -374,15 +927,22 @@ async function main(argv) {
|
|
|
374
927
|
}
|
|
375
928
|
const [first] = options.positionals;
|
|
376
929
|
if (first === void 0) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
930
|
+
return run_bare(options);
|
|
931
|
+
}
|
|
932
|
+
if (first === "init") {
|
|
933
|
+
return run_init(options);
|
|
380
934
|
}
|
|
381
935
|
if (first === "config") {
|
|
382
936
|
process.stdout.write(`${config_template()}
|
|
383
937
|
`);
|
|
384
938
|
return 0;
|
|
385
939
|
}
|
|
940
|
+
if (first === "update") {
|
|
941
|
+
if (options.positionals.length > 1) {
|
|
942
|
+
throw new Error("update takes no arguments");
|
|
943
|
+
}
|
|
944
|
+
return run_update(fileURLToPath(import.meta.url));
|
|
945
|
+
}
|
|
386
946
|
if (first === "chat") {
|
|
387
947
|
if (options.positionals.length > 1) {
|
|
388
948
|
throw new Error("chat mode takes no task argument");
|
|
@@ -398,7 +958,7 @@ async function main(argv) {
|
|
|
398
958
|
return run_one_shot(build_config_for(options, "one-shot"), options.positionals.join(" "));
|
|
399
959
|
}
|
|
400
960
|
if (is_main_module() === true) {
|
|
401
|
-
|
|
961
|
+
run_cli(process.argv.slice(2)).then((code) => {
|
|
402
962
|
process.exitCode = code;
|
|
403
963
|
}).catch((error) => {
|
|
404
964
|
process.stderr.write(`lich: ${error_message(error)}
|
|
@@ -407,4 +967,9 @@ if (is_main_module() === true) {
|
|
|
407
967
|
process.exitCode = 1;
|
|
408
968
|
});
|
|
409
969
|
}
|
|
970
|
+
export {
|
|
971
|
+
run_chat,
|
|
972
|
+
run_cli,
|
|
973
|
+
run_one_shot
|
|
974
|
+
};
|
|
410
975
|
//# sourceMappingURL=cli.js.map
|