@moikapy/lich 0.7.1 → 0.8.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.
@@ -0,0 +1,75 @@
1
+ // src/session/resolve.ts
2
+ import { readdir, stat } from "fs/promises";
3
+ import path from "path";
4
+ var CANDIDATE_CAP = 5;
5
+ function is_enoent(error) {
6
+ return typeof error === "object" && error !== null && error.code === "ENOENT";
7
+ }
8
+ async function list_session_files(dir) {
9
+ let names;
10
+ try {
11
+ names = await readdir(dir);
12
+ } catch (error) {
13
+ if (is_enoent(error) === true) {
14
+ return [];
15
+ }
16
+ throw error;
17
+ }
18
+ const entries = [];
19
+ for (const name of names) {
20
+ if (name.endsWith(".jsonl") === false) {
21
+ continue;
22
+ }
23
+ try {
24
+ const info = await stat(path.join(dir, name));
25
+ if (info.isFile() === false) {
26
+ continue;
27
+ }
28
+ entries.push({ name, id: name.slice(0, -".jsonl".length), mtime_ms: info.mtimeMs });
29
+ } catch {
30
+ continue;
31
+ }
32
+ }
33
+ return entries.sort((a, b) => b.mtime_ms - a.mtime_ms);
34
+ }
35
+ function candidate_ids(entries) {
36
+ if (entries.length === 0) {
37
+ return "(none)";
38
+ }
39
+ return entries.slice(0, CANDIDATE_CAP).map((entry) => entry.id).join(", ");
40
+ }
41
+ function missing_error(dir, value, entries) {
42
+ return new Error(`session not found: "${value}" in ${dir} (candidates: ${candidate_ids(entries)})`);
43
+ }
44
+ function ambiguous_error(dir, value, matches) {
45
+ const sorted = [...matches].sort((a, b) => b.mtime_ms - a.mtime_ms);
46
+ return new Error(`ambiguous session prefix: "${value}" in ${dir} matches: ${candidate_ids(sorted)}`);
47
+ }
48
+ async function resolve_session_path(dir, value) {
49
+ const entries = await list_session_files(dir);
50
+ if (value === "latest") {
51
+ const newest = entries[0];
52
+ if (newest === void 0) {
53
+ throw missing_error(dir, value, entries);
54
+ }
55
+ return path.join(dir, newest.name);
56
+ }
57
+ const exact_name = `${value}.jsonl`;
58
+ const exact = entries.find((entry) => entry.name === exact_name);
59
+ if (exact !== void 0) {
60
+ return path.join(dir, exact.name);
61
+ }
62
+ const prefix_matches = entries.filter((entry) => entry.id.startsWith(value));
63
+ if (prefix_matches.length === 1) {
64
+ return path.join(dir, prefix_matches[0].name);
65
+ }
66
+ if (prefix_matches.length > 1) {
67
+ throw ambiguous_error(dir, value, prefix_matches);
68
+ }
69
+ throw missing_error(dir, value, entries);
70
+ }
71
+
72
+ export {
73
+ resolve_session_path
74
+ };
75
+ //# sourceMappingURL=chunk-PL6MKRKE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/session/resolve.ts"],"sourcesContent":["/**\n * Resolve a `--resume` value to an existing session transcript path.\n * Pure filesystem lookup: latest by mtime, exact id, or unique prefix.\n */\nimport { readdir, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport interface SessionFileInfo {\n readonly name: string;\n readonly id: string;\n readonly mtime_ms: number;\n}\n\nconst CANDIDATE_CAP = 5;\n\nfunction is_enoent(error: unknown): boolean {\n return typeof error === \"object\" && error !== null && (error as { code?: unknown }).code === \"ENOENT\";\n}\n\nasync function list_session_files(dir: string): Promise<SessionFileInfo[]> {\n let names: string[];\n try {\n names = await readdir(dir);\n } catch (error) {\n if (is_enoent(error) === true) {\n return [];\n }\n throw error;\n }\n const entries: SessionFileInfo[] = [];\n for (const name of names) {\n if (name.endsWith(\".jsonl\") === false) {\n continue;\n }\n try {\n const info = await stat(path.join(dir, name));\n if (info.isFile() === false) {\n continue;\n }\n entries.push({ name, id: name.slice(0, -\".jsonl\".length), mtime_ms: info.mtimeMs });\n } catch {\n continue;\n }\n }\n return entries.sort((a, b) => b.mtime_ms - a.mtime_ms);\n}\n\nfunction candidate_ids(entries: readonly SessionFileInfo[]): string {\n if (entries.length === 0) {\n return \"(none)\";\n }\n return entries\n .slice(0, CANDIDATE_CAP)\n .map((entry) => entry.id)\n .join(\", \");\n}\n\nfunction missing_error(dir: string, value: string, entries: readonly SessionFileInfo[]): Error {\n return new Error(`session not found: \"${value}\" in ${dir} (candidates: ${candidate_ids(entries)})`);\n}\n\nfunction ambiguous_error(dir: string, value: string, matches: readonly SessionFileInfo[]): Error {\n const sorted = [...matches].sort((a, b) => b.mtime_ms - a.mtime_ms);\n return new Error(`ambiguous session prefix: \"${value}\" in ${dir} matches: ${candidate_ids(sorted)}`);\n}\n\n/** Map `(dir, value)` to an absolute transcript path, or throw with candidates. */\nexport async function resolve_session_path(dir: string, value: string): Promise<string> {\n const entries = await list_session_files(dir);\n if (value === \"latest\") {\n const newest = entries[0];\n if (newest === undefined) {\n throw missing_error(dir, value, entries);\n }\n return path.join(dir, newest.name);\n }\n const exact_name = `${value}.jsonl`;\n const exact = entries.find((entry) => entry.name === exact_name);\n if (exact !== undefined) {\n return path.join(dir, exact.name);\n }\n const prefix_matches = entries.filter((entry) => entry.id.startsWith(value));\n if (prefix_matches.length === 1) {\n return path.join(dir, prefix_matches[0]!.name);\n }\n if (prefix_matches.length > 1) {\n throw ambiguous_error(dir, value, prefix_matches);\n }\n throw missing_error(dir, value, entries);\n}\n"],"mappings":";AAIA,SAAS,SAAS,YAAY;AAC9B,OAAO,UAAU;AAQjB,IAAM,gBAAgB;AAEtB,SAAS,UAAU,OAAyB;AAC1C,SAAO,OAAO,UAAU,YAAY,UAAU,QAAS,MAA6B,SAAS;AAC/F;AAEA,eAAe,mBAAmB,KAAyC;AACzE,MAAI;AACJ,MAAI;AACF,YAAQ,MAAM,QAAQ,GAAG;AAAA,EAC3B,SAAS,OAAO;AACd,QAAI,UAAU,KAAK,MAAM,MAAM;AAC7B,aAAO,CAAC;AAAA,IACV;AACA,UAAM;AAAA,EACR;AACA,QAAM,UAA6B,CAAC;AACpC,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,SAAS,QAAQ,MAAM,OAAO;AACrC;AAAA,IACF;AACA,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,KAAK,KAAK,KAAK,IAAI,CAAC;AAC5C,UAAI,KAAK,OAAO,MAAM,OAAO;AAC3B;AAAA,MACF;AACA,cAAQ,KAAK,EAAE,MAAM,IAAI,KAAK,MAAM,GAAG,CAAC,SAAS,MAAM,GAAG,UAAU,KAAK,QAAQ,CAAC;AAAA,IACpF,QAAQ;AACN;AAAA,IACF;AAAA,EACF;AACA,SAAO,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AACvD;AAEA,SAAS,cAAc,SAA6C;AAClE,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,EACT;AACA,SAAO,QACJ,MAAM,GAAG,aAAa,EACtB,IAAI,CAAC,UAAU,MAAM,EAAE,EACvB,KAAK,IAAI;AACd;AAEA,SAAS,cAAc,KAAa,OAAe,SAA4C;AAC7F,SAAO,IAAI,MAAM,uBAAuB,KAAK,QAAQ,GAAG,iBAAiB,cAAc,OAAO,CAAC,GAAG;AACpG;AAEA,SAAS,gBAAgB,KAAa,OAAe,SAA4C;AAC/F,QAAM,SAAS,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AAClE,SAAO,IAAI,MAAM,8BAA8B,KAAK,QAAQ,GAAG,aAAa,cAAc,MAAM,CAAC,EAAE;AACrG;AAGA,eAAsB,qBAAqB,KAAa,OAAgC;AACtF,QAAM,UAAU,MAAM,mBAAmB,GAAG;AAC5C,MAAI,UAAU,UAAU;AACtB,UAAM,SAAS,QAAQ,CAAC;AACxB,QAAI,WAAW,QAAW;AACxB,YAAM,cAAc,KAAK,OAAO,OAAO;AAAA,IACzC;AACA,WAAO,KAAK,KAAK,KAAK,OAAO,IAAI;AAAA,EACnC;AACA,QAAM,aAAa,GAAG,KAAK;AAC3B,QAAM,QAAQ,QAAQ,KAAK,CAAC,UAAU,MAAM,SAAS,UAAU;AAC/D,MAAI,UAAU,QAAW;AACvB,WAAO,KAAK,KAAK,KAAK,MAAM,IAAI;AAAA,EAClC;AACA,QAAM,iBAAiB,QAAQ,OAAO,CAAC,UAAU,MAAM,GAAG,WAAW,KAAK,CAAC;AAC3E,MAAI,eAAe,WAAW,GAAG;AAC/B,WAAO,KAAK,KAAK,KAAK,eAAe,CAAC,EAAG,IAAI;AAAA,EAC/C;AACA,MAAI,eAAe,SAAS,GAAG;AAC7B,UAAM,gBAAgB,KAAK,OAAO,cAAc;AAAA,EAClD;AACA,QAAM,cAAc,KAAK,OAAO,OAAO;AACzC;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  logger
3
- } from "./chunk-WNFBIX4E.js";
3
+ } from "./chunk-EDRUZF22.js";
4
4
 
5
5
  // src/util/theme.ts
6
6
  import { readFileSync } from "fs";
@@ -89,4 +89,4 @@ export {
89
89
  notice_flavor,
90
90
  load_theme
91
91
  };
92
- //# sourceMappingURL=chunk-CVX7LZWC.js.map
92
+ //# sourceMappingURL=chunk-QOTECFCN.js.map
@@ -0,0 +1,86 @@
1
+ // src/session/store.ts
2
+ import { appendFile, mkdir, readFile } from "fs/promises";
3
+ import path from "path";
4
+
5
+ // src/util/json.ts
6
+ function safe_json_parse(raw) {
7
+ try {
8
+ return JSON.parse(raw);
9
+ } catch {
10
+ return void 0;
11
+ }
12
+ }
13
+ function safe_stringify(value, space) {
14
+ try {
15
+ return JSON.stringify(value, null, space) ?? String(value);
16
+ } catch {
17
+ return String(value);
18
+ }
19
+ }
20
+ function truncate_text(text, max_chars) {
21
+ if (text.length <= max_chars) {
22
+ return text;
23
+ }
24
+ const omitted = text.length - max_chars;
25
+ return `${text.slice(0, max_chars)}
26
+ [... truncated, ${omitted} chars omitted ...]`;
27
+ }
28
+
29
+ // src/session/store.ts
30
+ var counter_state = { value: 0 };
31
+ var MESSAGE_ROLES = /* @__PURE__ */ new Set(["system", "user", "assistant", "tool"]);
32
+ function slugify_label(label) {
33
+ const slug = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
34
+ return slug.length > 0 ? `-${slug}` : "";
35
+ }
36
+ async function open_session(dir, label) {
37
+ await mkdir(dir, { recursive: true });
38
+ counter_state.value += 1;
39
+ const label_part = label === void 0 ? "" : slugify_label(label);
40
+ const id = `${Date.now().toString(36)}-${counter_state.value}${label_part}`;
41
+ const file_path = path.join(dir, `${id}.jsonl`);
42
+ return {
43
+ id,
44
+ path: file_path,
45
+ append: async (record) => {
46
+ await appendFile(file_path, `${safe_stringify(record)}
47
+ `, "utf8");
48
+ }
49
+ };
50
+ }
51
+ function is_message(value) {
52
+ if (typeof value !== "object" || value === null) {
53
+ return false;
54
+ }
55
+ const role = value.role;
56
+ return typeof role === "string" && MESSAGE_ROLES.has(role);
57
+ }
58
+ async function read_session_messages(file_path) {
59
+ let raw;
60
+ try {
61
+ raw = await readFile(file_path, "utf8");
62
+ } catch {
63
+ return [];
64
+ }
65
+ const messages = [];
66
+ for (const line of raw.split("\n")) {
67
+ const record = safe_json_parse(line);
68
+ if (record?.message !== void 0 && is_message(record.message)) {
69
+ messages.push(record.message);
70
+ }
71
+ }
72
+ const last = messages.at(-1);
73
+ if (last?.role === "user") {
74
+ messages.pop();
75
+ }
76
+ return messages;
77
+ }
78
+
79
+ export {
80
+ safe_json_parse,
81
+ safe_stringify,
82
+ truncate_text,
83
+ open_session,
84
+ read_session_messages
85
+ };
86
+ //# sourceMappingURL=chunk-VKEOHUCB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/session/store.ts","../src/util/json.ts"],"sourcesContent":["/**\n * JSONL transcript persistence for agent sessions. Each session is one\n * append-only .jsonl file; records carry either a message or arbitrary meta.\n */\nimport { appendFile, mkdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Message } from \"../providers/types.js\";\nimport { safe_json_parse, safe_stringify } from \"../util/json.js\";\n\nexport interface SessionRecord {\n ts: string;\n kind: \"message\" | \"meta\";\n message?: Message;\n meta?: Record<string, unknown>;\n}\n\nexport interface SessionHandle {\n id: string;\n path: string;\n append(record: SessionRecord): Promise<void>;\n}\n\n/** Mutable via holder object: conventions require const bindings. */\nconst counter_state = { value: 0 };\n\nconst MESSAGE_ROLES: ReadonlySet<string> = new Set([\"system\", \"user\", \"assistant\", \"tool\"]);\n\nfunction slugify_label(label: string): string {\n const slug = label\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 40);\n return slug.length > 0 ? `-${slug}` : \"\";\n}\n\nexport async function open_session(dir: string, label?: string): Promise<SessionHandle> {\n await mkdir(dir, { recursive: true });\n counter_state.value += 1;\n const label_part = label === undefined ? \"\" : slugify_label(label);\n const id = `${Date.now().toString(36)}-${counter_state.value}${label_part}`;\n const file_path = path.join(dir, `${id}.jsonl`);\n return {\n id,\n path: file_path,\n append: async (record: SessionRecord): Promise<void> => {\n await appendFile(file_path, `${safe_stringify(record)}\\n`, \"utf8\");\n },\n };\n}\n\nfunction is_message(value: unknown): value is Message {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const role = (value as { role?: unknown }).role;\n return typeof role === \"string\" && MESSAGE_ROLES.has(role);\n}\n\nexport async function read_session_messages(file_path: string): Promise<Message[]> {\n let raw: string;\n try {\n raw = await readFile(file_path, \"utf8\");\n } catch {\n return [];\n }\n const messages: Message[] = [];\n for (const line of raw.split(\"\\n\")) {\n const record = safe_json_parse<SessionRecord>(line);\n if (record?.message !== undefined && is_message(record.message)) {\n messages.push(record.message);\n }\n }\n // Provider throw / abort-before-turn can leave a dangling user seed with no\n // assistant reply. Drop it so resume does not start with two consecutive users.\n const last = messages.at(-1);\n if (last?.role === \"user\") {\n messages.pop();\n }\n return messages;\n}","export function safe_json_parse<T>(raw: string): T | undefined {\n try {\n return JSON.parse(raw) as T;\n } catch {\n return undefined;\n }\n}\n\nexport function safe_stringify(value: unknown, space?: number): string {\n try {\n return JSON.stringify(value, null, space) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\nexport function truncate_text(text: string, max_chars: number): string {\n if (text.length <= max_chars) {\n return text;\n }\n const omitted = text.length - max_chars;\n return `${text.slice(0, max_chars)}\\n[... truncated, ${omitted} chars omitted ...]`;\n}"],"mappings":";AAIA,SAAS,YAAY,OAAO,gBAAgB;AAC5C,OAAO,UAAU;;;ACLV,SAAS,gBAAmB,KAA4B;AAC7D,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,eAAe,OAAgB,OAAwB;AACrE,MAAI;AACF,WAAO,KAAK,UAAU,OAAO,MAAM,KAAK,KAAK,OAAO,KAAK;AAAA,EAC3D,QAAQ;AACN,WAAO,OAAO,KAAK;AAAA,EACrB;AACF;AAEO,SAAS,cAAc,MAAc,WAA2B;AACrE,MAAI,KAAK,UAAU,WAAW;AAC5B,WAAO;AAAA,EACT;AACA,QAAM,UAAU,KAAK,SAAS;AAC9B,SAAO,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;AAAA,kBAAqB,OAAO;AAChE;;;ADCA,IAAM,gBAAgB,EAAE,OAAO,EAAE;AAEjC,IAAM,gBAAqC,oBAAI,IAAI,CAAC,UAAU,QAAQ,aAAa,MAAM,CAAC;AAE1F,SAAS,cAAc,OAAuB;AAC5C,QAAM,OAAO,MACV,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE,EACtB,MAAM,GAAG,EAAE;AACd,SAAO,KAAK,SAAS,IAAI,IAAI,IAAI,KAAK;AACxC;AAEA,eAAsB,aAAa,KAAa,OAAwC;AACtF,QAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC,gBAAc,SAAS;AACvB,QAAM,aAAa,UAAU,SAAY,KAAK,cAAc,KAAK;AACjE,QAAM,KAAK,GAAG,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,cAAc,KAAK,GAAG,UAAU;AACzE,QAAM,YAAY,KAAK,KAAK,KAAK,GAAG,EAAE,QAAQ;AAC9C,SAAO;AAAA,IACL;AAAA,IACA,MAAM;AAAA,IACN,QAAQ,OAAO,WAAyC;AACtD,YAAM,WAAW,WAAW,GAAG,eAAe,MAAM,CAAC;AAAA,GAAM,MAAM;AAAA,IACnE;AAAA,EACF;AACF;AAEA,SAAS,WAAW,OAAkC;AACpD,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO;AAAA,EACT;AACA,QAAM,OAAQ,MAA6B;AAC3C,SAAO,OAAO,SAAS,YAAY,cAAc,IAAI,IAAI;AAC3D;AAEA,eAAsB,sBAAsB,WAAuC;AACjF,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,SAAS,WAAW,MAAM;AAAA,EACxC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,QAAM,WAAsB,CAAC;AAC7B,aAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,UAAM,SAAS,gBAA+B,IAAI;AAClD,QAAI,QAAQ,YAAY,UAAa,WAAW,OAAO,OAAO,GAAG;AAC/D,eAAS,KAAK,OAAO,OAAO;AAAA,IAC9B;AAAA,EACF;AAGA,QAAM,OAAO,SAAS,GAAG,EAAE;AAC3B,MAAI,MAAM,SAAS,QAAQ;AACzB,aAAS,IAAI;AAAA,EACf;AACA,SAAO;AACT;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  catalog_by_name
3
- } from "./chunk-WNFBIX4E.js";
3
+ } from "./chunk-EDRUZF22.js";
4
4
 
5
5
  // src/index.ts
6
6
  import { readFileSync } from "fs";
@@ -55,4 +55,4 @@ export {
55
55
  catalog_client_entry,
56
56
  LICH_VERSION
57
57
  };
58
- //# sourceMappingURL=chunk-PZNYVGD4.js.map
58
+ //# sourceMappingURL=chunk-W6JXZBUE.js.map
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ /** MCP-only flags. Values are not logged. */
3
+ interface McpCliFlags {
4
+ command?: string;
5
+ args: string[];
6
+ url?: string;
7
+ project_path?: string;
8
+ }
9
+
10
+ interface CliOptions {
11
+ config_path?: string;
12
+ resume?: string;
13
+ help?: boolean;
14
+ version?: boolean;
15
+ overrides: Record<string, string>;
16
+ positionals: string[];
17
+ mcp_flags: McpCliFlags;
18
+ }
19
+ declare function parse_args(argv: string[]): CliOptions;
2
20
  declare function run_one_shot(config: unknown, input: string): Promise<number>;
3
21
  declare function run_chat(config: unknown): Promise<number>;
4
22
  interface CliEntryInput {
@@ -11,4 +29,4 @@ interface CliEntryInput {
11
29
  declare function is_cli_entry(input: CliEntryInput): boolean;
12
30
  declare function run_cli(argv: string[]): Promise<number>;
13
31
 
14
- export { type CliEntryInput, is_cli_entry, run_chat, run_cli, run_one_shot };
32
+ export { type CliEntryInput, is_cli_entry, parse_args, run_chat, run_cli, run_one_shot };
package/dist/cli.js CHANGED
@@ -2,19 +2,21 @@
2
2
  import {
3
3
  load_theme,
4
4
  notice_flavor
5
- } from "./chunk-CVX7LZWC.js";
5
+ } from "./chunk-QOTECFCN.js";
6
6
  import {
7
7
  LICH_VERSION,
8
8
  catalog_client_entry
9
- } from "./chunk-PZNYVGD4.js";
9
+ } from "./chunk-W6JXZBUE.js";
10
10
  import {
11
11
  DEFAULT_GATEWAY_TOKEN_ENVS,
12
12
  create_agent_with_plugins,
13
13
  is_env_var_name,
14
14
  parse_agent_config,
15
- refuse_mcp_entry,
15
+ refuse_mcp_entry
16
+ } from "./chunk-EDRUZF22.js";
17
+ import {
16
18
  safe_json_parse
17
- } from "./chunk-WNFBIX4E.js";
19
+ } from "./chunk-VKEOHUCB.js";
18
20
 
19
21
  // src/cli.ts
20
22
  import { existsSync as existsSync4, readFileSync as readFileSync2, realpathSync } from "fs";
@@ -831,6 +833,7 @@ function usage_text() {
831
833
  " --api-key-env <NAME> env var holding the api key (default LICH_API_KEY_ENV; unused by ollama)",
832
834
  " --system-prompt <s> system prompt override",
833
835
  " --session-dir <path> session transcript directory",
836
+ " --resume <id|latest> TUI only: load an existing session transcript",
834
837
  " --log-level <level> debug | info | warn | error",
835
838
  " --theme <name> display theme (default lich; files in ~/.lich/themes)",
836
839
  " --command <bin> mcp add: local stdio binary",
@@ -842,6 +845,21 @@ function usage_text() {
842
845
  function error_message(error) {
843
846
  return error instanceof Error ? error.message : String(error);
844
847
  }
848
+ function reject_resume_outside_tui(resume, mode) {
849
+ if (resume === void 0) {
850
+ return;
851
+ }
852
+ throw new Error(`--resume is only supported in TUI mode (not ${mode})`);
853
+ }
854
+ function non_tui_resume_mode(first) {
855
+ if (first === void 0 || first === "tui") {
856
+ return void 0;
857
+ }
858
+ if (first === "init" || first === "config" || first === "mcp" || first === "update" || first === "chat" || first === "gateway") {
859
+ return first;
860
+ }
861
+ return "one-shot";
862
+ }
845
863
  function error_for_mode(mode, base_message) {
846
864
  if (mode === "tui") {
847
865
  return "lich tui: no model configured \u2014 set LICH_MODEL (e.g. glm-5.3-flash:cloud), pass --model, or create .lich/config.json (`lich config` prints a template)";
@@ -879,6 +897,15 @@ function parse_args(argv) {
879
897
  index += 1;
880
898
  continue;
881
899
  }
900
+ if (arg === "--resume") {
901
+ const value2 = argv[index + 1];
902
+ if (value2 === void 0) {
903
+ throw new Error("--resume requires a value");
904
+ }
905
+ options.resume = value2;
906
+ index += 1;
907
+ continue;
908
+ }
882
909
  if (mcp === true) {
883
910
  const consumed = take_mcp_flag(argv, index, options.mcp_flags);
884
911
  if (consumed !== void 0) {
@@ -1155,7 +1182,7 @@ async function run_bare(options) {
1155
1182
  if (stopped !== void 0) {
1156
1183
  return stopped;
1157
1184
  }
1158
- return run_tui_entry(build_config_for(options, "tui"));
1185
+ return run_tui_entry(build_config_for(options, "tui"), options.resume);
1159
1186
  }
1160
1187
  function model_still_placeholder(config) {
1161
1188
  const providers = config["providers"];
@@ -1179,9 +1206,17 @@ function run_init(options) {
1179
1206
  }
1180
1207
  return 0;
1181
1208
  }
1182
- async function run_tui_entry(config) {
1183
- const { run_tui } = await import("./tui-MEJGEILU.js");
1184
- return run_tui(config);
1209
+ async function run_tui_entry(config, resume) {
1210
+ const { run_tui } = await import("./tui-4BP3TI7J.js");
1211
+ if (resume === void 0) {
1212
+ return run_tui(config);
1213
+ }
1214
+ const { resolve_session_path } = await import("./resolve-H22TOVB5.js");
1215
+ const { read_session_messages } = await import("./store-COOLBAHB.js");
1216
+ const transcript = await resolve_session_path(config.session_dir, resume);
1217
+ const initial_history = await read_session_messages(transcript);
1218
+ const resumed_id = path4.basename(transcript, ".jsonl");
1219
+ return run_tui(config, { initial_history, resumed_id });
1185
1220
  }
1186
1221
  function gateway_platforms(config, cli_platforms) {
1187
1222
  if (cli_platforms.length > 0) {
@@ -1191,7 +1226,7 @@ function gateway_platforms(config, cli_platforms) {
1191
1226
  return configured.length === 0 ? ["webhook"] : configured;
1192
1227
  }
1193
1228
  async function run_gateway_entry(config, platforms) {
1194
- const { run_gateway } = await import("./gateway-44QTIJTJ.js");
1229
+ const { run_gateway } = await import("./gateway-WU5G4ODO.js");
1195
1230
  return run_gateway(config, gateway_platforms(config, platforms));
1196
1231
  }
1197
1232
  function is_cli_entry(input) {
@@ -1237,6 +1272,10 @@ async function run_cli(argv) {
1237
1272
  return 0;
1238
1273
  }
1239
1274
  const [first] = options.positionals;
1275
+ const blocked_resume_mode = non_tui_resume_mode(first);
1276
+ if (blocked_resume_mode !== void 0) {
1277
+ reject_resume_outside_tui(options.resume, blocked_resume_mode);
1278
+ }
1240
1279
  if (first === void 0) {
1241
1280
  return run_bare(options);
1242
1281
  }
@@ -1264,7 +1303,7 @@ async function run_cli(argv) {
1264
1303
  return run_chat(build_config_for(options, first));
1265
1304
  }
1266
1305
  if (first === "tui") {
1267
- return run_tui_entry(build_config_for(options, first));
1306
+ return run_tui_entry(build_config_for(options, first), options.resume);
1268
1307
  }
1269
1308
  if (first === "gateway") {
1270
1309
  return run_gateway_entry(build_config_for(options, first), options.positionals.slice(1));
@@ -1283,6 +1322,7 @@ if (is_main_module() === true) {
1283
1322
  }
1284
1323
  export {
1285
1324
  is_cli_entry,
1325
+ parse_args,
1286
1326
  run_chat,
1287
1327
  run_cli,
1288
1328
  run_one_shot