@moikapy/lich 0.4.0 → 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/dist/cli.js CHANGED
@@ -3,19 +3,21 @@ import {
3
3
  LICH_VERSION
4
4
  } from "./chunk-ZVK3MUPC.js";
5
5
  import {
6
- create_agent,
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-MLFJW4JU.js";
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 path2 from "path";
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
- for (const candidate of config_search_paths()) {
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 ? load_config() ?? { providers: [env_provider(options.overrides)] } : load_config_file(options.config_path);
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 = create_agent(config);
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 ask_line(rl) {
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 = create_agent(config);
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 ask_line(rl);
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-VYBJSGRV.js");
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-XTYDYT67.js");
354
- return run_gateway(config, platforms.length === 0 ? ["webhook"] : platforms);
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(path2.resolve(entry)).href;
914
+ return import.meta.url === pathToFileURL(path4.resolve(entry)).href;
362
915
  }
363
- async function main(argv) {
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
- process.stderr.write(`${usage_text()}
378
- `);
379
- return 1;
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
- main(process.argv.slice(2)).then((code) => {
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