@24klynx/cli 0.1.1 → 0.1.2

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.
@@ -1,7 +1,7 @@
1
- import { resolvePaths } from "@lynx/core";
1
+ import { resolvePaths } from "@24klynx/core";
2
2
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
3
  import { dirname } from "node:path";
4
- import { createAnthropicProvider, createDeepSeekProvider, createOpenAiProvider } from "@lynx/llm";
4
+ import { createAnthropicProvider, createDeepSeekProvider, createOpenAiProvider } from "@24klynx/llm";
5
5
  //#region src/commands/config.ts
6
6
  /**
7
7
  * Config command — 统一配置文件管理与 Provider 工厂。
@@ -144,4 +144,4 @@ async function handleConfigCommand(opts) {
144
144
  //#endregion
145
145
  export { saveConfig as a, resolveProvider as i, loadConfig as n, resolveConfigEnv as r, handleConfigCommand as t };
146
146
 
147
- //# sourceMappingURL=config-Des0z-k9.mjs.map
147
+ //# sourceMappingURL=config-Braj-aBw.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-Braj-aBw.mjs","names":[],"sources":["../src/commands/config.ts"],"sourcesContent":["/**\n * Config command — 统一配置文件管理与 Provider 工厂。\n *\n * 读取 ~/.lynx/config.json,提供类型安全的结构化配置。\n * 同时输出 resolveConfigEnv(env 注入)和 resolveProvider(按模型名自动选型)。\n */\n\nimport { readFileSync, writeFileSync, existsSync, renameSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { resolvePaths } from \"@24klynx/core\";\nimport type { LlmProvider } from \"@24klynx/llm\";\nimport { createDeepSeekProvider, createAnthropicProvider, createOpenAiProvider } from \"@24klynx/llm\";\n\n// ── Types ────────────────────────────────────────────\n\n/** Lynx 完整配置结构。对应 ~/.lynx/config.json。 */\nexport interface LynxConfig {\n /** 模型标识符(如 deepseek-v4-pro、claude-sonnet-4-6)。 */\n model: string;\n /** UI 主题(dark / light)。 */\n theme: string;\n /** 环境变量注入 — key 不存在于 process.env 时自动设置。 */\n env: Record<string, string>;\n /** 权限白/黑名单。格式 \"ToolName(args)\"。 */\n permissions: {\n allow: string[];\n deny: string[];\n };\n}\n\nexport interface ConfigCommandOptions {\n show?: boolean;\n set?: string;\n value?: string;\n path?: boolean;\n}\n\n// ── Defaults ─────────────────────────────────────────\n\nconst DEFAULT_CONFIG: LynxConfig = {\n model: \"deepseek-v4-pro\",\n theme: \"dark\",\n env: {},\n permissions: { allow: [], deny: [] },\n};\n\n// ── Helpers ──────────────────────────────────────────\n\nfunction configPath(): string {\n const paths = resolvePaths();\n return paths.configFile;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 从磁盘加载配置,缺失字段填默认值。\n * 文件损坏时自动备份为 .bak 并返回默认配置。\n */\nexport function loadConfig(): LynxConfig {\n const path = configPath();\n if (!existsSync(path))\n return { ...DEFAULT_CONFIG, env: {}, permissions: { allow: [], deny: [] } };\n try {\n const raw = JSON.parse(readFileSync(path, \"utf-8\")) as Partial<LynxConfig>;\n return {\n model: raw.model ?? DEFAULT_CONFIG.model,\n theme: raw.theme ?? DEFAULT_CONFIG.theme,\n env: raw.env ?? {},\n permissions: {\n allow: raw.permissions?.allow ?? [],\n deny: raw.permissions?.deny ?? [],\n },\n };\n } catch {\n const backupPath = path + \".bak\";\n try {\n renameSync(path, backupPath);\n } catch {\n // 备份失败不阻塞\n }\n process.stderr.write(\n `[lynx] 警告:配置文件已损坏,已备份到 ${backupPath}。正在使用默认配置。\\n`,\n );\n return { ...DEFAULT_CONFIG, env: {}, permissions: { allow: [], deny: [] } };\n }\n}\n\n/**\n * 将 config.env 中的键注入 process.env(仅补未设的 key,不覆盖已有环境变量)。\n *\n * 优先级:已存在的 process.env > config.json env\n */\nexport function resolveConfigEnv(config: LynxConfig): void {\n for (const [key, value] of Object.entries(config.env)) {\n if (process.env[key] === undefined && value !== undefined && value !== \"\") {\n process.env[key] = value;\n }\n }\n}\n\n/**\n * 按模型名自动匹配 LLM Provider。\n *\n * 匹配规则:\n * - 含 `claude` → Anthropic(需 ANTHROPIC_AUTH_TOKEN 或 ANTHROPIC_API_KEY)\n * - 含 `gpt` 或 `o1`/`o3` → OpenAI(需 OPENAI_API_KEY)\n * - 其他 → DeepSeek(需 DEEPSEEK_API_KEY)\n *\n * 环境变量优先于 config.json env(resolveConfigEnv 已处理)。\n *\n * @throws 找不到对应 API key 时抛出中文错误。\n */\nexport function resolveProvider(config: LynxConfig): LlmProvider {\n const model = process.env.LYNX_MODEL ?? config.model ?? \"deepseek-v4-pro\";\n\n if (model.includes(\"claude\")) {\n const token = process.env.ANTHROPIC_AUTH_TOKEN ?? process.env.ANTHROPIC_API_KEY;\n if (!token) {\n throw new Error(\n \"未配置 Anthropic API 密钥。请在 config.json 的 env.ANTHROPIC_AUTH_TOKEN 中设置,\" +\n \"或设置环境变量 ANTHROPIC_API_KEY。\",\n );\n }\n return createAnthropicProvider({\n apiKey: token,\n baseUrl: process.env.ANTHROPIC_BASE_URL,\n });\n }\n\n if (model.includes(\"gpt\") || model.includes(\"o1\") || model.includes(\"o3\")) {\n const token = process.env.OPENAI_API_KEY;\n if (!token) {\n throw new Error(\n \"未配置 OpenAI API 密钥。请在 config.json 的 env.OPENAI_API_KEY 中设置,\" +\n \"或设置环境变量 OPENAI_API_KEY。\",\n );\n }\n return createOpenAiProvider({ apiKey: token });\n }\n\n // 默认:DeepSeek\n const token = process.env.DEEPSEEK_API_KEY;\n if (!token) {\n throw new Error(\n \"未配置 DeepSeek API 密钥。请在 config.json 的 env.DEEPSEEK_API_KEY 中设置,\" +\n \"或设置环境变量 DEEPSEEK_API_KEY。\",\n );\n }\n return createDeepSeekProvider({\n apiKey: token,\n baseUrl: process.env.DEEPSEEK_BASE_URL,\n });\n}\n\n/** 原子写入配置到磁盘(temp file + rename)。 */\nexport function saveConfig(config: LynxConfig): void {\n const path = configPath();\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n const tmpPath = path + \".tmp\";\n // 排序键以保证 diff 友好\n const ordered = {\n model: config.model,\n theme: config.theme,\n env: config.env,\n permissions: config.permissions,\n };\n writeFileSync(tmpPath, JSON.stringify(ordered, null, 2) + \"\\n\", \"utf-8\");\n renameSync(tmpPath, path);\n}\n\n// ── CLI handler ──────────────────────────────────────\n\n/** 处理 `lynx config` 命令。 */\nexport async function handleConfigCommand(opts: ConfigCommandOptions): Promise<void> {\n if (opts.path) {\n process.stdout.write(`${configPath()}\\n`);\n return;\n }\n\n if (opts.set) {\n const config = loadConfig();\n // 简单顶层 key 直写;嵌套 key 暂不支持\n const key = opts.set as keyof LynxConfig;\n if (key === \"model\" || key === \"theme\") {\n config[key] = opts.value ?? \"\";\n } else if (key === \"env\" || key === \"permissions\") {\n process.stderr.write(`[lynx] 警告:${opts.set} 是嵌套对象,请直接编辑 config.json。\\n`);\n return;\n }\n saveConfig(config);\n process.stdout.write(`${opts.set} = ${opts.value ?? \"\"}\\n`);\n return;\n }\n\n // Default: --show\n const config = loadConfig();\n const keys = Object.keys(config);\n if (keys.length === 0) {\n process.stdout.write(\"No configuration values set.\\n\");\n } else {\n for (const [key, val] of Object.entries(config)) {\n process.stdout.write(`${key} = ${JSON.stringify(val)}\\n`);\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { t as handleConfigCommand } from "./config-Braj-aBw.mjs";
2
+ export { handleConfigCommand };
@@ -1 +1 @@
1
- {"version":3,"file":"context-BmZ8VEan.mjs","names":[],"sources":["../src/commands/context.ts"],"sourcesContent":["/**\n * /context — 显示 IDE 连接状态和活跃文件信息。\n *\n * 扫描 ~/.lynx/ide/*.lock 检测正在运行的 IDE,\n * 读取 context.json 获取活跃文件、光标位置等上下文信息,\n * 以表格形式输出。\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { existsSync, readFileSync, readdirSync } from \"node:fs\";\n\n// ── Types ────────────────────────────────────────────\n\n/** IDE lockfile 内容格式(与 @lynx/ide adapter 一致)。 */\ninterface LockfileData {\n pid: number;\n workspace: string;\n transport: \"sse\" | \"ws\";\n ideName: string;\n authToken?: string;\n}\n\n/** context.json 中记录的 IDE 上下文状态。 */\ninterface IdeContext {\n activeFile?: string;\n cursorPosition?: { line: number; column: number };\n selectedText?: string;\n openFiles?: string[];\n}\n\n// ── Helpers ──────────────────────────────────────────\n\n/** IDE 名称到显示名称的映射。 */\nconst IDE_DISPLAY_NAMES: Record<string, string> = {\n vscode: \"VS Code\",\n cursor: \"Cursor\",\n windsurf: \"Windsurf\",\n zed: \"Zed\",\n};\n\n/** 解析 lockfile 目录路径,优先 ~/.lynx/ide,回退 ~/.claude/ide。 */\nfunction resolveIdeDir(): string | null {\n const lynxIde = join(homedir(), \".lynx\", \"ide\");\n if (existsSync(lynxIde)) return lynxIde;\n\n const claudeIde = join(homedir(), \".claude\", \"ide\");\n if (existsSync(claudeIde)) return claudeIde;\n\n return null;\n}\n\n/** 读取并解析单个 lockfile,失败返回 null。 */\nfunction readLockfile(filePath: string): { port: number; data: LockfileData } | null {\n try {\n const raw = readFileSync(filePath, \"utf-8\");\n const data = JSON.parse(raw) as LockfileData;\n const portMatch = filePath.match(/(\\d+)\\.lock$/);\n if (!portMatch) return null;\n const port = Number.parseInt(portMatch[1], 10);\n if (Number.isNaN(port)) return null;\n return { port, data };\n } catch {\n return null;\n }\n}\n\n/** 读取 IDE 上下文状态文件。 */\nfunction readIdeContext(): IdeContext | null {\n const contextPath = join(homedir(), \".lynx\", \"ide\", \"context.json\");\n try {\n if (!existsSync(contextPath)) return null;\n const raw = readFileSync(contextPath, \"utf-8\");\n return JSON.parse(raw) as IdeContext;\n } catch {\n return null;\n }\n}\n\n/** 格式化 Unix 时间戳为可读的时间字符串。 */\nfunction formatTime(timestamp: number): string {\n const date = new Date(timestamp);\n const pad = (n: number) => String(n).padStart(2, \"0\");\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /context 命令。\n *\n * 扫描 IDE lockfile 和 context.json,格式化输出 IDE 连接状态。\n * 不返回 instruction(这是 local 类型命令,直接在 CLI 进程中执行)。\n */\nexport function handleContextCommand(): { output: string } {\n const lines: string[] = [];\n const separator = \"━━━━━━━━━━━━━━━━━━━━━━━━\";\n\n lines.push(\"IDE 连接状态\");\n lines.push(separator);\n\n const ideDir = resolveIdeDir();\n\n if (!ideDir) {\n // 无 IDE 目录 — 从未连接过\n lines.push(\"连接状态: 未连接\");\n lines.push(\"提示: 在 VS Code 中安装 Lynx 扩展以启用 IDE 集成\");\n lines.push(separator);\n return { output: lines.join(\"\\n\") };\n }\n\n // 扫描所有 lockfile\n let entries: string[];\n try {\n entries = readdirSync(ideDir);\n } catch {\n lines.push(\"连接状态: 未连接\");\n lines.push(\"提示: 在 VS Code 中安装 Lynx 扩展以启用 IDE 集成\");\n lines.push(separator);\n return { output: lines.join(\"\\n\") };\n }\n\n const lockFiles = entries.filter((f) => f.endsWith(\".lock\"));\n const ideInstances: Array<{ port: number; data: LockfileData }> = [];\n for (const file of lockFiles) {\n const result = readLockfile(join(ideDir, file));\n if (result) {\n ideInstances.push(result);\n }\n }\n\n if (ideInstances.length === 0) {\n lines.push(\"连接状态: 未连接\");\n lines.push(\"提示: 在 VS Code 中安装 Lynx 扩展以启用 IDE 集成\");\n lines.push(separator);\n return { output: lines.join(\"\\n\") };\n }\n\n // 取第一个连接的 IDE(或与当前工作目录匹配的)\n const cwd = process.cwd();\n const matched = ideInstances.find((inst) => inst.data.workspace === cwd) ?? ideInstances[0];\n const { data } = matched;\n\n const displayName = IDE_DISPLAY_NAMES[data.ideName] ?? data.ideName;\n\n lines.push(`连接状态: 已连接`);\n lines.push(`IDE 类型: ${displayName} (${data.ideName})`);\n lines.push(`工作区: ${data.workspace}`);\n\n // 尝试读取上下文状态\n const context = readIdeContext();\n\n if (context?.activeFile) {\n const pos = context.cursorPosition;\n const posStr = pos ? `:${pos.line}` : \"\";\n lines.push(`活跃文件: ${context.activeFile}${posStr}`);\n }\n\n if (context?.cursorPosition) {\n const { line, column } = context.cursorPosition;\n lines.push(`光标位置: 行 ${line}, 列 ${column}`);\n }\n\n if (context?.selectedText !== undefined) {\n const len = context.selectedText.length;\n lines.push(`选中文本: (${len} 字符)`);\n }\n\n if (context?.openFiles && context.openFiles.length > 0) {\n const openCount = context.openFiles.length;\n lines.push(`打开文件: ${openCount} 个`);\n const maxDisplay = 20;\n const displayFiles = context.openFiles.slice(0, maxDisplay);\n for (const file of displayFiles) {\n lines.push(` - ${file}`);\n }\n if (context.openFiles.length > maxDisplay) {\n lines.push(` ... 还有 ${context.openFiles.length - maxDisplay} 个文件`);\n }\n }\n\n lines.push(separator);\n return { output: lines.join(\"\\n\") };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"context-BmZ8VEan.mjs","names":[],"sources":["../src/commands/context.ts"],"sourcesContent":["/**\n * /context — 显示 IDE 连接状态和活跃文件信息。\n *\n * 扫描 ~/.lynx/ide/*.lock 检测正在运行的 IDE,\n * 读取 context.json 获取活跃文件、光标位置等上下文信息,\n * 以表格形式输出。\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { existsSync, readFileSync, readdirSync } from \"node:fs\";\n\n// ── Types ────────────────────────────────────────────\n\n/** IDE lockfile 内容格式(与 @24klynx/ide adapter 一致)。 */\ninterface LockfileData {\n pid: number;\n workspace: string;\n transport: \"sse\" | \"ws\";\n ideName: string;\n authToken?: string;\n}\n\n/** context.json 中记录的 IDE 上下文状态。 */\ninterface IdeContext {\n activeFile?: string;\n cursorPosition?: { line: number; column: number };\n selectedText?: string;\n openFiles?: string[];\n}\n\n// ── Helpers ──────────────────────────────────────────\n\n/** IDE 名称到显示名称的映射。 */\nconst IDE_DISPLAY_NAMES: Record<string, string> = {\n vscode: \"VS Code\",\n cursor: \"Cursor\",\n windsurf: \"Windsurf\",\n zed: \"Zed\",\n};\n\n/** 解析 lockfile 目录路径,优先 ~/.lynx/ide,回退 ~/.claude/ide。 */\nfunction resolveIdeDir(): string | null {\n const lynxIde = join(homedir(), \".lynx\", \"ide\");\n if (existsSync(lynxIde)) return lynxIde;\n\n const claudeIde = join(homedir(), \".claude\", \"ide\");\n if (existsSync(claudeIde)) return claudeIde;\n\n return null;\n}\n\n/** 读取并解析单个 lockfile,失败返回 null。 */\nfunction readLockfile(filePath: string): { port: number; data: LockfileData } | null {\n try {\n const raw = readFileSync(filePath, \"utf-8\");\n const data = JSON.parse(raw) as LockfileData;\n const portMatch = filePath.match(/(\\d+)\\.lock$/);\n if (!portMatch) return null;\n const port = Number.parseInt(portMatch[1], 10);\n if (Number.isNaN(port)) return null;\n return { port, data };\n } catch {\n return null;\n }\n}\n\n/** 读取 IDE 上下文状态文件。 */\nfunction readIdeContext(): IdeContext | null {\n const contextPath = join(homedir(), \".lynx\", \"ide\", \"context.json\");\n try {\n if (!existsSync(contextPath)) return null;\n const raw = readFileSync(contextPath, \"utf-8\");\n return JSON.parse(raw) as IdeContext;\n } catch {\n return null;\n }\n}\n\n/** 格式化 Unix 时间戳为可读的时间字符串。 */\nfunction formatTime(timestamp: number): string {\n const date = new Date(timestamp);\n const pad = (n: number) => String(n).padStart(2, \"0\");\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /context 命令。\n *\n * 扫描 IDE lockfile 和 context.json,格式化输出 IDE 连接状态。\n * 不返回 instruction(这是 local 类型命令,直接在 CLI 进程中执行)。\n */\nexport function handleContextCommand(): { output: string } {\n const lines: string[] = [];\n const separator = \"━━━━━━━━━━━━━━━━━━━━━━━━\";\n\n lines.push(\"IDE 连接状态\");\n lines.push(separator);\n\n const ideDir = resolveIdeDir();\n\n if (!ideDir) {\n // 无 IDE 目录 — 从未连接过\n lines.push(\"连接状态: 未连接\");\n lines.push(\"提示: 在 VS Code 中安装 Lynx 扩展以启用 IDE 集成\");\n lines.push(separator);\n return { output: lines.join(\"\\n\") };\n }\n\n // 扫描所有 lockfile\n let entries: string[];\n try {\n entries = readdirSync(ideDir);\n } catch {\n lines.push(\"连接状态: 未连接\");\n lines.push(\"提示: 在 VS Code 中安装 Lynx 扩展以启用 IDE 集成\");\n lines.push(separator);\n return { output: lines.join(\"\\n\") };\n }\n\n const lockFiles = entries.filter((f) => f.endsWith(\".lock\"));\n const ideInstances: Array<{ port: number; data: LockfileData }> = [];\n for (const file of lockFiles) {\n const result = readLockfile(join(ideDir, file));\n if (result) {\n ideInstances.push(result);\n }\n }\n\n if (ideInstances.length === 0) {\n lines.push(\"连接状态: 未连接\");\n lines.push(\"提示: 在 VS Code 中安装 Lynx 扩展以启用 IDE 集成\");\n lines.push(separator);\n return { output: lines.join(\"\\n\") };\n }\n\n // 取第一个连接的 IDE(或与当前工作目录匹配的)\n const cwd = process.cwd();\n const matched = ideInstances.find((inst) => inst.data.workspace === cwd) ?? ideInstances[0];\n const { data } = matched;\n\n const displayName = IDE_DISPLAY_NAMES[data.ideName] ?? data.ideName;\n\n lines.push(`连接状态: 已连接`);\n lines.push(`IDE 类型: ${displayName} (${data.ideName})`);\n lines.push(`工作区: ${data.workspace}`);\n\n // 尝试读取上下文状态\n const context = readIdeContext();\n\n if (context?.activeFile) {\n const pos = context.cursorPosition;\n const posStr = pos ? `:${pos.line}` : \"\";\n lines.push(`活跃文件: ${context.activeFile}${posStr}`);\n }\n\n if (context?.cursorPosition) {\n const { line, column } = context.cursorPosition;\n lines.push(`光标位置: 行 ${line}, 列 ${column}`);\n }\n\n if (context?.selectedText !== undefined) {\n const len = context.selectedText.length;\n lines.push(`选中文本: (${len} 字符)`);\n }\n\n if (context?.openFiles && context.openFiles.length > 0) {\n const openCount = context.openFiles.length;\n lines.push(`打开文件: ${openCount} 个`);\n const maxDisplay = 20;\n const displayFiles = context.openFiles.slice(0, maxDisplay);\n for (const file of displayFiles) {\n lines.push(` - ${file}`);\n }\n if (context.openFiles.length > maxDisplay) {\n lines.push(` ... 还有 ${context.openFiles.length - maxDisplay} 个文件`);\n }\n }\n\n lines.push(separator);\n return { output: lines.join(\"\\n\") };\n}\n"],"mappings":";;;;;;;;;;;;AAkCA,MAAM,oBAA4C;CAChD,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,KAAK;AACP;;AAGA,SAAS,gBAA+B;CACtC,MAAM,UAAU,KAAK,QAAQ,GAAG,SAAS,KAAK;CAC9C,IAAI,WAAW,OAAO,GAAG,OAAO;CAEhC,MAAM,YAAY,KAAK,QAAQ,GAAG,WAAW,KAAK;CAClD,IAAI,WAAW,SAAS,GAAG,OAAO;CAElC,OAAO;AACT;;AAGA,SAAS,aAAa,UAA+D;CACnF,IAAI;EACF,MAAM,MAAM,aAAa,UAAU,OAAO;EAC1C,MAAM,OAAO,KAAK,MAAM,GAAG;EAC3B,MAAM,YAAY,SAAS,MAAM,cAAc;EAC/C,IAAI,CAAC,WAAW,OAAO;EACvB,MAAM,OAAO,OAAO,SAAS,UAAU,IAAI,EAAE;EAC7C,IAAI,OAAO,MAAM,IAAI,GAAG,OAAO;EAC/B,OAAO;GAAE;GAAM;EAAK;CACtB,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAS,iBAAoC;CAC3C,MAAM,cAAc,KAAK,QAAQ,GAAG,SAAS,OAAO,cAAc;CAClE,IAAI;EACF,IAAI,CAAC,WAAW,WAAW,GAAG,OAAO;EACrC,MAAM,MAAM,aAAa,aAAa,OAAO;EAC7C,OAAO,KAAK,MAAM,GAAG;CACvB,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;AAiBA,SAAgB,uBAA2C;CACzD,MAAM,QAAkB,CAAC;CACzB,MAAM,YAAY;CAElB,MAAM,KAAK,UAAU;CACrB,MAAM,KAAK,SAAS;CAEpB,MAAM,SAAS,cAAc;CAE7B,IAAI,CAAC,QAAQ;EAEX,MAAM,KAAK,WAAW;EACtB,MAAM,KAAK,qCAAqC;EAChD,MAAM,KAAK,SAAS;EACpB,OAAO,EAAE,QAAQ,MAAM,KAAK,IAAI,EAAE;CACpC;CAGA,IAAI;CACJ,IAAI;EACF,UAAU,YAAY,MAAM;CAC9B,QAAQ;EACN,MAAM,KAAK,WAAW;EACtB,MAAM,KAAK,qCAAqC;EAChD,MAAM,KAAK,SAAS;EACpB,OAAO,EAAE,QAAQ,MAAM,KAAK,IAAI,EAAE;CACpC;CAEA,MAAM,YAAY,QAAQ,QAAQ,MAAM,EAAE,SAAS,OAAO,CAAC;CAC3D,MAAM,eAA4D,CAAC;CACnE,KAAK,MAAM,QAAQ,WAAW;EAC5B,MAAM,SAAS,aAAa,KAAK,QAAQ,IAAI,CAAC;EAC9C,IAAI,QACF,aAAa,KAAK,MAAM;CAE5B;CAEA,IAAI,aAAa,WAAW,GAAG;EAC7B,MAAM,KAAK,WAAW;EACtB,MAAM,KAAK,qCAAqC;EAChD,MAAM,KAAK,SAAS;EACpB,OAAO,EAAE,QAAQ,MAAM,KAAK,IAAI,EAAE;CACpC;CAGA,MAAM,MAAM,QAAQ,IAAI;CAExB,MAAM,EAAE,SADQ,aAAa,MAAM,SAAS,KAAK,KAAK,cAAc,GAAG,KAAK,aAAa;CAGzF,MAAM,cAAc,kBAAkB,KAAK,YAAY,KAAK;CAE5D,MAAM,KAAK,WAAW;CACtB,MAAM,KAAK,WAAW,YAAY,IAAI,KAAK,QAAQ,EAAE;CACrD,MAAM,KAAK,QAAQ,KAAK,WAAW;CAGnC,MAAM,UAAU,eAAe;CAE/B,IAAI,SAAS,YAAY;EACvB,MAAM,MAAM,QAAQ;EACpB,MAAM,SAAS,MAAM,IAAI,IAAI,SAAS;EACtC,MAAM,KAAK,SAAS,QAAQ,aAAa,QAAQ;CACnD;CAEA,IAAI,SAAS,gBAAgB;EAC3B,MAAM,EAAE,MAAM,WAAW,QAAQ;EACjC,MAAM,KAAK,WAAW,KAAK,MAAM,QAAQ;CAC3C;CAEA,IAAI,SAAS,iBAAiB,KAAA,GAAW;EACvC,MAAM,MAAM,QAAQ,aAAa;EACjC,MAAM,KAAK,UAAU,IAAI,KAAK;CAChC;CAEA,IAAI,SAAS,aAAa,QAAQ,UAAU,SAAS,GAAG;EACtD,MAAM,YAAY,QAAQ,UAAU;EACpC,MAAM,KAAK,SAAS,UAAU,GAAG;EACjC,MAAM,aAAa;EACnB,MAAM,eAAe,QAAQ,UAAU,MAAM,GAAG,UAAU;EAC1D,KAAK,MAAM,QAAQ,cACjB,MAAM,KAAK,OAAO,MAAM;EAE1B,IAAI,QAAQ,UAAU,SAAS,YAC7B,MAAM,KAAK,YAAY,QAAQ,UAAU,SAAS,WAAW,KAAK;CAEtE;CAEA,MAAM,KAAK,SAAS;CACpB,OAAO,EAAE,QAAQ,MAAM,KAAK,IAAI,EAAE;AACpC"}
@@ -1,4 +1,4 @@
1
- import { resolvePaths } from "@lynx/core";
1
+ import { resolvePaths } from "@24klynx/core";
2
2
  import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  //#region src/commands/env.ts
@@ -52,4 +52,4 @@ function handleEnvCommand() {
52
52
  //#endregion
53
53
  export { handleEnvCommand };
54
54
 
55
- //# sourceMappingURL=env-CeeZcoDI.mjs.map
55
+ //# sourceMappingURL=env-jKIO_4zX.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-jKIO_4zX.mjs","names":[],"sources":["../src/commands/env.ts"],"sourcesContent":["/**\n * /env — 显示 Lynx 环境变量和运行环境信息。\n *\n * 读取所有 LYNX_* 环境变量、Node.js 版本、操作系统平台\n * 和 Lynx 主目录,以表格形式输出。\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { resolvePaths } from \"@24klynx/core\";\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /env 命令。\n *\n * 收集并格式化输出所有 Lynx 相关的环境信息。\n * 不返回 instruction(这是 local 类型命令,直接在 CLI 进程中执行)。\n */\nexport function handleEnvCommand(): { output: string } {\n const lines: string[] = [];\n\n // ── LYNX_* 环境变量 ──────────────────────────────\n const lynxVars = Object.keys(process.env)\n .filter((key) => key.startsWith(\"LYNX_\"))\n .sort();\n\n lines.push(\"【Lynx 环境变量】\");\n lines.push(\"\");\n\n if (lynxVars.length === 0) {\n lines.push(\" (未设置任何 LYNX_* 环境变量)\");\n } else {\n for (const key of lynxVars) {\n const value = process.env[key];\n const display = value && value.length > 0 ? value : \"(空)\";\n lines.push(` ${key.padEnd(30)} = ${display}`);\n }\n }\n\n // ── 已定义但未设置的常见变量 ──────────────────────\n const knownVars = [\"LYNX_HOME\", \"LYNX_CONFIG_PATH\", \"LYNX_LOG_LEVEL\", \"LYNX_MODEL\"];\n const missingVars = knownVars.filter((v) => !lynxVars.includes(v));\n if (missingVars.length > 0) {\n lines.push(\"\");\n lines.push(\"【未设置的常见变量】\");\n lines.push(\"\");\n for (const key of missingVars) {\n lines.push(` ${key.padEnd(30)} = (未设置,使用默认值)`);\n }\n }\n\n // ── 运行环境 ──────────────────────────────────────\n const paths = resolvePaths();\n\n lines.push(\"\");\n lines.push(\"【运行环境】\");\n lines.push(\"\");\n lines.push(` ${\"Node.js 版本\".padEnd(30)} = ${process.version}`);\n lines.push(` ${\"操作系统\".padEnd(30)} = ${process.platform} (${process.arch})`);\n lines.push(` ${\"Lynx 主目录\".padEnd(30)} = ${join(homedir(), \".lynx\")}`);\n lines.push(` ${\"配置文件路径\".padEnd(30)} = ${paths.configFile}`);\n lines.push(` ${\"数据库路径\".padEnd(30)} = ${paths.stateDb}`);\n lines.push(` ${\"工作目录\".padEnd(30)} = ${process.cwd()}`);\n\n return { output: lines.join(\"\\n\") };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmBA,SAAgB,mBAAuC;CACrD,MAAM,QAAkB,CAAC;CAGzB,MAAM,WAAW,OAAO,KAAK,QAAQ,GAAG,CAAC,CACtC,QAAQ,QAAQ,IAAI,WAAW,OAAO,CAAC,CAAC,CACxC,KAAK;CAER,MAAM,KAAK,aAAa;CACxB,MAAM,KAAK,EAAE;CAEb,IAAI,SAAS,WAAW,GACtB,MAAM,KAAK,uBAAuB;MAElC,KAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,QAAQ,QAAQ,IAAI;EAC1B,MAAM,UAAU,SAAS,MAAM,SAAS,IAAI,QAAQ;EACpD,MAAM,KAAK,KAAK,IAAI,OAAO,EAAE,EAAE,KAAK,SAAS;CAC/C;CAKF,MAAM,cAAc;EADD;EAAa;EAAoB;EAAkB;CAC1C,CAAC,CAAC,QAAQ,MAAM,CAAC,SAAS,SAAS,CAAC,CAAC;CACjE,IAAI,YAAY,SAAS,GAAG;EAC1B,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,YAAY;EACvB,MAAM,KAAK,EAAE;EACb,KAAK,MAAM,OAAO,aAChB,MAAM,KAAK,KAAK,IAAI,OAAO,EAAE,EAAE,eAAe;CAElD;CAGA,MAAM,QAAQ,aAAa;CAE3B,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,QAAQ;CACnB,MAAM,KAAK,EAAE;CACb,MAAM,KAAK,KAAK,aAAa,OAAO,EAAE,EAAE,KAAK,QAAQ,SAAS;CAC9D,MAAM,KAAK,KAAK,OAAO,OAAO,EAAE,EAAE,KAAK,QAAQ,SAAS,IAAI,QAAQ,KAAK,EAAE;CAC3E,MAAM,KAAK,KAAK,WAAW,OAAO,EAAE,EAAE,KAAK,KAAK,QAAQ,GAAG,OAAO,GAAG;CACrE,MAAM,KAAK,KAAK,SAAS,OAAO,EAAE,EAAE,KAAK,MAAM,YAAY;CAC3D,MAAM,KAAK,KAAK,QAAQ,OAAO,EAAE,EAAE,KAAK,MAAM,SAAS;CACvD,MAAM,KAAK,KAAK,OAAO,OAAO,EAAE,EAAE,KAAK,QAAQ,IAAI,GAAG;CAEtD,OAAO,EAAE,QAAQ,MAAM,KAAK,IAAI,EAAE;AACpC"}
@@ -1,6 +1,6 @@
1
- import { d as runPhase2WithContext, f as bootstrap, t as installProcessLifecycle } from "./process-lifecycle-Dg6n2QS-.mjs";
2
- import { i as resolveProvider, n as loadConfig, r as resolveConfigEnv } from "./config-Des0z-k9.mjs";
3
- import { asMessageId, resolvePaths } from "@lynx/core";
1
+ import { d as runPhase2WithContext, f as bootstrap, t as installProcessLifecycle } from "./process-lifecycle-kdV53L3o.mjs";
2
+ import { i as resolveProvider, n as loadConfig, r as resolveConfigEnv } from "./config-Braj-aBw.mjs";
3
+ import { asMessageId, resolvePaths } from "@24klynx/core";
4
4
  import { dirname, join } from "node:path";
5
5
  import "node:os";
6
6
  import { fileURLToPath } from "node:url";
@@ -168,4 +168,4 @@ async function startHeadless(config = {}) {
168
168
  //#endregion
169
169
  export { startHeadless };
170
170
 
171
- //# sourceMappingURL=headless-launcher-I8NWyD6k.mjs.map
171
+ //# sourceMappingURL=headless-launcher-CnSrw3bm.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headless-launcher-CnSrw3bm.mjs","names":[],"sources":["../src/headless-launcher.ts"],"sourcesContent":["/**\n * headless-launcher — non-interactive mode for Lynx.\n *\n * Used when `lynx start --headless` is invoked. Starts the agent engine\n * and optionally a messaging channel (飞书 etc.) for remote interaction.\n * No TUI is rendered — all I/O goes through the channel adapter.\n *\n * Responsibility:\n * 1. Bootstrap the application (same as tui-launcher)\n * 2. Register channel adapters (飞书 etc.)\n * 3. Process incoming messages through the engine\n * 4. Stream responses back through the channel\n */\n\nimport { resolvePaths, asMessageId } from \"@24klynx/core\";\nimport { loadConfig, resolveConfigEnv, resolveProvider } from \"./commands/config.js\";\nimport { bootstrap } from \"./bootstrap.js\";\nimport { runPhase2WithContext } from \"./startup.js\";\nimport { installProcessLifecycle } from \"./process-lifecycle.js\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { homedir } from \"node:os\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface HeadlessConfig {\n /** Model to use (overrides LYNX_MODEL env var). */\n model?: string;\n /** Enable verbose logging. */\n verbose?: boolean;\n /** 飞书 channel configuration. */\n feishu?: { appId: string; appSecret: string };\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Start Lynx in headless (non-interactive) mode.\n *\n * If a 飞书 configuration is provided, the agent listens for\n * incoming messages from the 飞书 channel and responds via\n * the same channel. Otherwise, runs a simple REPL on stdin/stdout.\n */\nexport async function startHeadless(config: HeadlessConfig = {}): Promise<void> {\n const paths = resolvePaths();\n\n // 加载配置 → 注入 env → 按模型名自动选 Provider\n const appConfig = loadConfig();\n resolveConfigEnv(appConfig);\n const provider = resolveProvider(appConfig);\n\n // Resolve built‑in skills directory relative to the lynx-agent package\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const builtinSkillsDir = join(__dirname, \"..\", \"..\", \"lynx-agent\", \"skills\");\n\n const ctx = bootstrap({\n homeDir: paths.home,\n provider,\n model: config.model ?? process.env.LYNX_MODEL ?? appConfig.model,\n skillsDir: builtinSkillsDir,\n workspace: process.cwd(),\n feishu: config.feishu,\n });\n\n const sessions = ctx.sessionMgr.list();\n const activeSession =\n sessions.length > 0 ? sessions[0]! : ctx.sessionMgr.create(\"default\", process.cwd());\n\n // ── Abort layer state ────────────────────────────\n let abortLayer = 0;\n let isStreaming = false;\n\n const getAbortLayer = () => abortLayer;\n const incrementAbortLayer = () => {\n abortLayer++;\n return abortLayer;\n };\n const resetAbortLayer = () => {\n abortLayer = 0;\n };\n\n // ── Process lifecycle ───────────────────────────\n installProcessLifecycle({\n ctx,\n onAbortLl: () => ctx.engine.abort(),\n onAbortTool: () => ctx.engine.abort(),\n getAbortLayer,\n incrementAbortLayer,\n resetAbortLayer,\n isStreaming: () => isStreaming,\n });\n\n // ── Phase 2: background tasks ───────────────────\n runPhase2WithContext(ctx, paths).catch(() => {\n // Errors are already logged by the task runner\n });\n\n // ── Message handler (shared across all channels) ─\n async function processMessage(msg: { text: string }): Promise<string> {\n isStreaming = true;\n resetAbortLayer();\n\n const userMsg = {\n id: asMessageId(crypto.randomUUID()),\n role: \"user\" as const,\n content: [{ type: \"text\" as const, text: msg.text }],\n timestamp: Date.now(),\n turnIndex: activeSession.messages.length,\n };\n\n // Add user message to the session\n activeSession.messages.push(userMsg);\n\n const controller = new AbortController();\n let responseText = \"\";\n\n try {\n for await (const event of ctx.engine.submit(activeSession, userMsg, controller.signal)) {\n switch (event.type) {\n case \"text_delta\":\n responseText += event.text;\n break;\n case \"reasoning_delta\":\n // Reasoning tokens — skip in headless mode (not sent to channel)\n break;\n case \"tool_use_start\":\n if (config.verbose) {\n process.stderr.write(`[headless] Tool call: ${event.name}\\n`);\n }\n break;\n case \"tool_result\":\n if (config.verbose) {\n const truncated =\n event.content.length > 200 ? event.content.slice(0, 200) + \"...\" : event.content;\n process.stderr.write(`[headless] Tool result: ${truncated}\\n`);\n }\n break;\n case \"error\":\n responseText += `\\nError: ${event.message}`;\n break;\n case \"done\":\n break;\n }\n }\n } catch (err) {\n responseText += `\\nError: ${err instanceof Error ? err.message : String(err)}`;\n } finally {\n isStreaming = false;\n }\n\n // Add assistant response to the session\n if (responseText) {\n const reply = {\n id: asMessageId(crypto.randomUUID()),\n role: \"assistant\" as const,\n content: [{ type: \"text\" as const, text: responseText }],\n timestamp: Date.now(),\n turnIndex: activeSession.messages.length,\n };\n activeSession.messages.push(reply);\n }\n\n return responseText;\n }\n\n // ── Channel or stdin mode ───────────────────────\n const channelIds = ctx.channelRegistry.list();\n\n if (channelIds.length > 0) {\n // Channel mode — use the first registered channel for I/O.\n // The registry wires each adapter's onMessage() to the centralized handler.\n // Since Message doesn't carry a channelId, we get the adapter directly\n // and bypass the centralized handler for request/response pairing.\n process.stderr.write(`[headless] Listening on channels: ${channelIds.join(\", \")}\\n`);\n\n const primaryChannelId = channelIds[0]!;\n const adapter = ctx.channelRegistry.get(primaryChannelId);\n\n if (!adapter) {\n process.stderr.write(`[headless] Channel \"${primaryChannelId}\" not found\\n`);\n process.exitCode = 1;\n return;\n }\n\n // Listen for incoming messages on the primary channel\n adapter.onMessage((msg) => {\n const textContent = msg.content\n .filter((c) => c.type === \"text\")\n .map((c) => (c as { text: string }).text)\n .join(\"\\n\");\n\n if (!textContent) return;\n\n processMessage({ text: textContent })\n .then(async (replyText) => {\n if (replyText) {\n const replyMsg = {\n id: asMessageId(crypto.randomUUID()),\n role: \"assistant\" as const,\n content: [{ type: \"text\" as const, text: replyText }],\n timestamp: Date.now(),\n turnIndex: activeSession.messages.length,\n };\n await adapter.sendMessage(replyMsg);\n }\n })\n .catch((err) => {\n process.stderr.write(\n `[headless] Channel message processing failed: ` +\n `${err instanceof Error ? err.message : String(err)}\\n`,\n );\n });\n });\n\n // Keep process alive — channels run indefinitely\n await new Promise<void>(() => {\n // Never resolves — process runs until SIGTERM/SIGINT\n });\n } else {\n // Stdin mode — read one message and respond\n process.stderr.write(\"[headless] No channels configured — stdin mode\\n\");\n process.stderr.write(\"[headless] Enter your message (Ctrl+D to end):\\n\");\n\n const chunks: string[] = [];\n process.stdin.setEncoding(\"utf-8\");\n\n for await (const chunk of process.stdin) {\n chunks.push(chunk as string);\n }\n\n const text = chunks.join(\"\").trim();\n if (!text) {\n process.stderr.write(\"[headless] Empty input — exiting\\n\");\n process.exitCode = 1;\n return;\n }\n\n const replyText = await processMessage({ text });\n process.stdout.write(replyText + \"\\n\");\n }\n}\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,12 +1,12 @@
1
- import { Database, LynxPaths } from "@lynx/core";
2
- import { SessionManager } from "@lynx/session";
3
- import { ToolRegistry } from "@lynx/tools";
4
- import { HookRegistry, ManifestRegistry, PluginLoader, PredictiveLoader } from "@lynx/plugins";
5
- import { AgentConfig, McpManager, McpServerConfig, MemoryManager, QueryEngine, SkillDefinition, SkillRegistry } from "@lynx/agent";
6
- import { RuleEngine } from "@lynx/permissions";
7
- import { ChannelRegistry, FeishuConfig } from "@lynx/channels";
1
+ import { Database, LynxPaths } from "@24klynx/core";
2
+ import { SessionManager } from "@24klynx/session";
3
+ import { ToolRegistry } from "@24klynx/tools";
4
+ import { HookRegistry, ManifestRegistry, PluginLoader, PredictiveLoader } from "@24klynx/plugins";
5
+ import { AgentConfig, McpManager, McpServerConfig, MemoryManager, QueryEngine, SkillDefinition, SkillRegistry } from "@24klynx/agent";
6
+ import { RuleEngine } from "@24klynx/permissions";
7
+ import { ChannelRegistry, FeishuConfig } from "@24klynx/channels";
8
8
  import { Command } from "commander";
9
- import { LlmProvider } from "@lynx/llm";
9
+ import { LlmProvider } from "@24klynx/llm";
10
10
  import { EventEmitter } from "node:events";
11
11
 
12
12
  //#region src/bootstrap.d.ts
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import { a as endSync, c as withSync, d as runPhase2WithContext, f as bootstrap, i as beginSync, l as runPhase1, n as onCleanup, o as enterFullscreen, r as uninstallProcessLifecycle, s as exitFullscreen, t as installProcessLifecycle, u as runPhase2 } from "./process-lifecycle-Dg6n2QS-.mjs";
2
- import { a as saveConfig, i as resolveProvider, n as loadConfig, r as resolveConfigEnv, t as handleConfigCommand } from "./config-Des0z-k9.mjs";
1
+ import { a as endSync, c as withSync, d as runPhase2WithContext, f as bootstrap, i as beginSync, l as runPhase1, n as onCleanup, o as enterFullscreen, r as uninstallProcessLifecycle, s as exitFullscreen, t as installProcessLifecycle, u as runPhase2 } from "./process-lifecycle-kdV53L3o.mjs";
2
+ import { a as saveConfig, i as resolveProvider, n as loadConfig, r as resolveConfigEnv, t as handleConfigCommand } from "./config-Braj-aBw.mjs";
3
3
  import { createRequire } from "node:module";
4
- import { asSessionId, migrate, openDatabase, resolvePaths } from "@lynx/core";
5
- import { createSessionManager, getLastSession } from "@lynx/session";
6
- import { assembleSystemPrompt } from "@lynx/agent";
4
+ import { asSessionId, migrate, openDatabase, resolvePaths } from "@24klynx/core";
5
+ import { createSessionManager, getLastSession } from "@24klynx/session";
6
+ import { assembleSystemPrompt } from "@24klynx/agent";
7
7
  import { accessSync, constants, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
8
8
  import { dirname, join } from "node:path";
9
9
  import { cpus, homedir, platform, release } from "node:os";
@@ -55,7 +55,7 @@ function createProgram() {
55
55
  await handleSessionCommand(opts);
56
56
  });
57
57
  program.command("config").description("显示或设置配置项").option("--show", "Show current configuration").option("--set <key>", "Set a configuration key").option("--value <value>", "Value for --set").option("--path", "Show config file path").action(async (opts) => {
58
- const { handleConfigCommand } = await import("./config-D-xVXTXi.mjs");
58
+ const { handleConfigCommand } = await import("./config-D9DbomLt.mjs");
59
59
  await handleConfigCommand(opts);
60
60
  });
61
61
  program.command("start").description("以无头后台模式启动(无 TUI)").option("--headless", "以无头模式运行(守护进程模式必需)").option("--feishu-app-id <id>", "飞书 App ID(覆盖 FEISHU_APP_ID 环境变量)").option("--feishu-app-secret <secret>", "飞书 App Secret(覆盖 FEISHU_APP_SECRET 环境变量)").option("--model <model>", "要使用的模型").option("--verbose", "启用详细日志").action(async (opts) => {
@@ -65,7 +65,7 @@ function createProgram() {
65
65
  process.exitCode = 1;
66
66
  return;
67
67
  }
68
- const { startHeadless } = await import("./headless-launcher-I8NWyD6k.mjs");
68
+ const { startHeadless } = await import("./headless-launcher-CnSrw3bm.mjs");
69
69
  const feishu = opts.feishuAppId && opts.feishuAppSecret ? {
70
70
  appId: opts.feishuAppId,
71
71
  appSecret: opts.feishuAppSecret
@@ -109,7 +109,7 @@ function createProgram() {
109
109
  process.stdout.write(`${result.instruction}\n`);
110
110
  });
111
111
  program.command("env").description("显示 Lynx 环境变量和运行环境信息").action(async () => {
112
- const { handleEnvCommand } = await import("./env-CeeZcoDI.mjs");
112
+ const { handleEnvCommand } = await import("./env-jKIO_4zX.mjs");
113
113
  const result = handleEnvCommand();
114
114
  process.stdout.write(`${result.output}\n`);
115
115
  });
@@ -1718,7 +1718,7 @@ async function launchTui() {
1718
1718
  enterFullscreen();
1719
1719
  const React = await import("react");
1720
1720
  const { render } = await import("ink");
1721
- const { App } = await import("@lynx/tui");
1721
+ const { App } = await import("@24klynx/tui");
1722
1722
  const { unmount, waitUntilExit } = render(React.createElement(App, {
1723
1723
  callbacks,
1724
1724
  session: activeSession,