diff --git a/AGENTS.md b/AGENTS.md index 1173031..3cc0566 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Repository Guidelines (Code-Verified) -> Last verified against current codebase: 2026-05-11 +> Last verified against current codebase: 2026-05-15 > Scope: `/Users/jojo/Desktop/agents/正在修复中/agents` 这份文档基于当前仓库实际代码重写。若与未来代码冲突,以代码为准并及时更新本文档。 @@ -19,10 +19,14 @@ - `utils/`: API client、日志、上下文与对话工具等公共函数 - **前端目录** - `static/src/`: Vue 3 + TS 前端 + - `cli/src/`: React 19 + Ink 6 + TypeScript CLI 前端(正在重写中) - 监控动画相关核心文件: - `static/src/components/chat/monitor/MonitorDirector.ts` - `static/src/stores/monitor.ts` - `static/src/components/chat/monitor/*` +- **CLI 相关文档** + - `docs/cli_ui_display_spec.md`: CLI 显示层设计说明 + - `docs/cli_slash_commands_spec.md`: CLI `/` 指令设计说明 - **其他子项目/资源** - `android-webview-app/`: Android WebView 客户端工程 - `easyagent/`: 独立前端子目录(与根目录前端并存) @@ -42,18 +46,32 @@ - 开发监听(当前脚本是 build watch):`npm run dev` - Lint:`npm run lint` +### CLI(React / Ink) +- 安装依赖:`npm --prefix cli install` +- 开发启动:`npm run cli` 或 `npm --prefix cli run dev` +- 构建:`npm run cli:build` +- 类型检查:`npm run cli:typecheck` +- 可执行命令名(构建后):`agents` / `agents-cli` + ## 3) 测试现状(不要再写过时命令) - 当前仓库内可见自动化冒烟:`test/test_server_refactor_smoke.py`(`unittest`) - 运行方式:`python -m unittest test.test_server_refactor_smoke` - `test_system_message.py` 依赖外部 `MOONSHOT_API_KEY` 与网络,不属于离线稳定 CI 用例。 - 当前仓库未发现 `pytest.ini`/`pyproject.toml`/`tox.ini`;不要默认要求 `pytest` 作为唯一入口。 +- CLI 当前最小可复现验证: + - `npm --prefix cli run typecheck` + - `npm --prefix cli run build` +- 若改动 `server/chat.py` 等后端接口适配,补充: + - `python3 -m py_compile server/chat.py` + - `python -m unittest test.test_server_refactor_smoke` ## 4) 代码修改约定(实用版) - **最小改动原则**:只改与需求直接相关文件,避免顺手重构。 - **后端改动优先级**:先改 `modules/`、`server/` 内对应模块,最后才动入口。 - **前端改动优先级**:按 `static/src` 现有分层改(`app/`、`stores/`、`components/`、`composables/`)。 +- **CLI 改动优先级**:优先在 `cli/src/App.tsx`、`cli/src/components.tsx`、`cli/src/eventMapper.ts`、`cli/src/api.ts` 内做最小闭环修改。 - 涉及 monitor 动画/事件联动时,至少同步检查: - `MonitorDirector.ts`(动画与场景执行) - `stores/monitor.ts`(事件队列与状态机) @@ -62,9 +80,18 @@ - Python:4 空格、`snake_case` 函数、`PascalCase` 类,新增公共函数尽量补 type hints。 - Vue/TS:保持现有代码风格,不做无关风格清洗。 +- CLI React/TS:保留现有 Ink 渲染方式与光标修正逻辑,不要轻易重写输入框定位策略。 - 日志:优先复用现有 logger/日志路径,不引入大量临时 `print`。 - 提交前至少做与改动相关的最小验证(命令输出或手工步骤要可复现)。 +### CLI 当前交互约束(2026-05-15) + +- CLI 连接的是现有本地 Web API(默认 `127.0.0.1:8091`),不是独立 agent runtime。 +- 启动 CLI 时应清屏、连接本地服务、创建新会话,并将输入区固定在底部。 +- 当前目录若不在工作区中,应先弹出“是否添加到工作区”的选择。 +- 思考内容当前默认隐藏,只显示“思考中 / 思考完成”标题;相关折叠代码保留,后续可继续修。 +- 不要在未获得用户要求的情况下运行交互式 TUI 压测或长时间模拟输入,以免刷屏占满上下文。 + ## 6) Git 工作流(开发 + Review) ### 6.1 核心原则 diff --git a/README.md b/README.md index 23ffc2f..495afff 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,66 @@ 一个功能完整的 AI 智能体系统,支持多模态交互、子智能体协作、实时终端操作和丰富的工具集成。 +## 当前状态(2026-05) + +- Web 端仍然是主线入口,基于 Flask + Vue 3。 +- 新 CLI 端已开始重写,位于 `cli/`,技术栈为 React 19 + Ink 6 + TypeScript。 +- 当前 CLI 不是独立后端;它会直接连接本地 `8091` Web API,并复用现有会话、任务、权限、工作区等接口。 +- CLI 已实现“启动即连接后端并新建会话”的流程,但仍处于持续打磨阶段,输入法、光标、时间线裁剪等终端细节仍在迭代。 + +## CLI 快速开始 + +### 安装与构建 + +```bash +npm install +npm --prefix cli install +npm run cli:build +``` + +### 开发启动 + +```bash +npm run cli +``` + +或直接: + +```bash +npm --prefix cli run dev +``` + +### 在目标目录启动 CLI + +```bash +cd /path/to/your/project +npm --prefix /Users/jojo/Desktop/agents/正在修复中/agents/cli run dev +``` + +说明: + +- CLI 会默认把“当前终端目录”视为目标工作目录。 +- 若当前目录尚未加入工作区,启动后会先提示是否将该目录加入工作区。 +- CLI 会优先连接 `http://127.0.0.1:8091`;若本地服务未启动,会尝试拉起: + +```bash +python3 -m server.app --path <当前目录> --port 8091 --thinking-mode +``` + +### 当前 CLI 已支持的能力 + +- 启动即创建会话,不必等首条消息发送后再建会话 +- `/new` `/resume` `/model` `/versioning` `/permission` `/execution` +- `/workspace` `/compact` `/tools` `/path` `/skill` `/status` `/help` `/clear` `/exit` +- 基于任务轮询的时间线展示:用户消息、工具调用、模型输出、审批、后台状态 +- 宿主机工作区接入、版本控制开关、权限模式与执行环境切换 + +### 当前 CLI 已知限制 + +- 默认隐藏思考内容,只显示“思考中 / 思考完成”标题 +- 时间线布局、终端输入法光标、长内容换行等细节仍在持续修正 +- 目前更适合本地开发与人工交互,不建议当作完全稳定的自动化终端入口 + ## 核心特性 ### 🎯 多模式运行 diff --git a/cli-react-demo/.gitignore b/cli-react-demo/.gitignore new file mode 100644 index 0000000..5fa7c01 --- /dev/null +++ b/cli-react-demo/.gitignore @@ -0,0 +1,3 @@ +node_modules/ + +.easyagent/ diff --git a/cli-react-demo/demo.jsx b/cli-react-demo/demo.jsx new file mode 100644 index 0000000..ed51b27 --- /dev/null +++ b/cli-react-demo/demo.jsx @@ -0,0 +1,226 @@ +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import {render, Box, Text, useApp, useInput} from 'ink'; + +const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); +const nowTime = () => new Date().toLocaleTimeString('zh-CN', {hour12: false}); + +function Header({running, turn}) { + return ( + + Agents CLI React/Ink Demo + + React 组件渲染到终端 · 支持粘贴多行 · 输入后回车发送 · Ctrl+C 取消当前任务 · /help 或 /exit + + + 状态: {running ? '运行中' : '空闲'} + Turn: #{turn} + + + ); +} + +function Message({item}) { + if (item.type === 'user') { + return ( + + + {item.time} + {item.text} + + ); + } + + if (item.type === 'assistant') { + return ( + + 助手 {item.time} + {item.text || 正在生成...} + + ); + } + + if (item.type === 'tool') { + const statusColor = item.status === 'done' ? 'green' : item.status === 'running' ? 'yellow' : 'red'; + return ( + + {item.status === 'done' ? '✓' : '●'} 工具调用:{item.name} + {item.detail} + {item.output ? {item.output} : null} + + ); + } + + if (item.type === 'system') { + return ◆ {item.text}; + } + + return null; +} + +function Transcript({items}) { + const visible = items.slice(-10); + return ( + + {visible.map(item => )} + + ); +} + +function Composer({value, running}) { + return ( + + + + {value} + + + 快捷键:Enter 发送 · 直接粘贴可保留多行 · Backspace 删除 · Ctrl+C {running ? '取消任务' : '退出'} · /help 查看命令 + + ); +} + +function App() { + const {exit} = useApp(); + const [input, setInput] = useState(''); + const [items, setItems] = useState(() => [ + {id: 'welcome', type: 'system', text: '这是一个本地 React/Ink CLI demo:消息区、工具卡片、输入框和状态栏都是 React 组件。'} + ]); + const [running, setRunning] = useState(false); + const [turn, setTurn] = useState(0); + const cancelRef = useRef(false); + const nextId = useRef(1); + + const append = useCallback(item => { + setItems(prev => [...prev, {id: String(nextId.current++), ...item}]); + }, []); + + const patchLastAssistant = useCallback(delta => { + setItems(prev => { + const copy = [...prev]; + for (let i = copy.length - 1; i >= 0; i--) { + if (copy[i].type === 'assistant') { + copy[i] = {...copy[i], text: (copy[i].text || '') + delta}; + break; + } + } + return copy; + }); + }, []); + + const markLastToolDone = useCallback(output => { + setItems(prev => { + const copy = [...prev]; + for (let i = copy.length - 1; i >= 0; i--) { + if (copy[i].type === 'tool' && copy[i].status === 'running') { + copy[i] = {...copy[i], status: 'done', output}; + break; + } + } + return copy; + }); + }, []); + + const runFakeAgent = useCallback(async prompt => { + setRunning(true); + cancelRef.current = false; + const currentTurn = turn + 1; + setTurn(currentTurn); + + append({type: 'user', text: prompt, time: nowTime()}); + + if (prompt === '/help') { + append({type: 'assistant', time: nowTime(), text: '可用命令:/help、/clear、/exit。普通文本会触发一段模拟 Agent 流程。现在可以直接粘贴多行内容,换行会保留在输入框和提交内容中。'}); + setRunning(false); + return; + } + if (prompt === '/clear') { + setItems([{id: 'cleared', type: 'system', text: '已清屏。'}]); + setRunning(false); + return; + } + if (prompt === '/exit') { + exit(); + return; + } + + append({type: 'assistant', time: nowTime(), text: ''}); + const sentence = '我会先读取项目结构,然后模拟一次工具调用,最后给出结论。这些内容是流式追加的,不会破坏下方输入框。'; + for (const char of sentence) { + if (cancelRef.current) { + patchLastAssistant('\n\n[已取消]'); + setRunning(false); + return; + } + patchLastAssistant(char); + await sleep(28); + } + + append({type: 'tool', status: 'running', name: 'list_files', detail: '扫描当前 workspace 的关键文件...'}); + await sleep(900); + if (cancelRef.current) { + markLastToolDone('任务取消,工具调用未继续。'); + setRunning(false); + return; + } + markLastToolDone('发现 main.py、server/tasks.py、core/main_terminal.py。'); + + append({type: 'assistant', time: nowTime(), text: ''}); + const finalText = '结论:CLI 可以像一个终端 App 一样工作。后续只需要把这里的模拟事件替换成 /api/tasks 的真实事件流。'; + for (const char of finalText) { + if (cancelRef.current) { + patchLastAssistant('\n\n[已取消]'); + setRunning(false); + return; + } + patchLastAssistant(char); + await sleep(24); + } + setRunning(false); + }, [append, exit, markLastToolDone, patchLastAssistant, turn]); + + useInput((chunk, key) => { + if (key.ctrl && chunk === 'c') { + if (running) { + cancelRef.current = true; + append({type: 'system', text: '收到 Ctrl+C:正在取消当前模拟任务...'}); + } else { + exit(); + } + return; + } + + if (running) { + return; + } + + if (key.return) { + const text = input.trim(); + if (!text) return; + setInput(''); + void runFakeAgent(text); + return; + } + + if (key.backspace || key.delete) { + setInput(prev => Array.from(prev).slice(0, -1).join('')); + return; + } + + if (chunk && !key.ctrl && !key.meta) { + // Ink 在 bracketed paste 或快速粘贴时,可能一次性给到包含换行的 chunk。 + // 这里把 CRLF/CR 统一成 LF,并保留多行内容;单独按 Enter 的提交逻辑在上面处理。 + const normalized = chunk.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + setInput(prev => prev + normalized); + } + }); + + return ( + +
+ + + + ); +} + +render(); diff --git a/cli-react-demo/package-lock.json b/cli-react-demo/package-lock.json new file mode 100644 index 0000000..71864c4 --- /dev/null +++ b/cli-react-demo/package-lock.json @@ -0,0 +1,1067 @@ +{ + "name": "agents-cli-react-demo", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agents-cli-react-demo", + "version": "0.1.0", + "dependencies": { + "ink": "^6.5.1", + "react": "^19.2.1", + "tsx": "^4.20.6" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", + "integrity": "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-toolkit": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", + "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz", + "integrity": "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==", + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.2.4", + "ansi-escapes": "^7.3.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.6.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^5.1.1", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.39.10", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.33.0", + "scheduler": "^0.27.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^8.0.0", + "stack-utils": "^2.0.6", + "string-width": "^8.1.1", + "terminal-size": "^4.0.1", + "type-fest": "^5.4.1", + "widest-line": "^6.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@types/react": ">=19.0.0", + "react": ">=19.0.0", + "react-devtools-core": ">=6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", + "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-size": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", + "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-fest": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", + "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz", + "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==", + "license": "MIT", + "dependencies": { + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + } + } +} diff --git a/cli-react-demo/package.json b/cli-react-demo/package.json new file mode 100644 index 0000000..51c6c6e --- /dev/null +++ b/cli-react-demo/package.json @@ -0,0 +1,14 @@ +{ + "name": "agents-cli-react-demo", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "demo": "tsx demo.jsx" + }, + "dependencies": { + "ink": "^6.5.1", + "react": "^19.2.1", + "tsx": "^4.20.6" + } +} diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/cli/package-lock.json b/cli/package-lock.json new file mode 100644 index 0000000..7ae9a01 --- /dev/null +++ b/cli/package-lock.json @@ -0,0 +1,1660 @@ +{ + "name": "agents-cli", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agents-cli", + "version": "0.1.0", + "dependencies": { + "ink": "^6.5.1", + "react": "^19.2.1", + "react-devtools-core": "^6.1.5" + }, + "bin": { + "agents": "dist/main.js", + "agents-cli": "dist/main.js" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "esbuild": "^0.27.0", + "tsx": "^4.20.6", + "typescript": "^5.9.3" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", + "integrity": "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "24.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", + "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-toolkit": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", + "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz", + "integrity": "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==", + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.2.4", + "ansi-escapes": "^7.3.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.6.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^5.1.1", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.39.10", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.33.0", + "scheduler": "^0.27.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^8.0.0", + "stack-utils": "^2.0.6", + "string-width": "^8.1.1", + "terminal-size": "^4.0.1", + "type-fest": "^5.4.1", + "widest-line": "^6.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@types/react": ">=19.0.0", + "react": ">=19.0.0", + "react-devtools-core": ">=6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-devtools-core": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", + "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", + "license": "MIT", + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "node_modules/react-devtools-core/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-reconciler": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", + "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-size": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", + "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tsx": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.0.tgz", + "integrity": "sha512-8ccZMPD69s1AbKXx0C5ddTNZfNjwV04iIKgjZmKfKxMynEtSYcK0Lh7iQFh53fI5Yu4pb9usgAiqyPmEONaALg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/type-fest": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", + "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/widest-line": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz", + "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==", + "license": "MIT", + "dependencies": { + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + } + } +} diff --git a/cli/package.json b/cli/package.json new file mode 100644 index 0000000..409a028 --- /dev/null +++ b/cli/package.json @@ -0,0 +1,27 @@ +{ + "name": "agents-cli", + "version": "0.1.0", + "private": true, + "type": "module", + "bin": { + "agents": "./dist/main.js", + "agents-cli": "./dist/main.js" + }, + "scripts": { + "dev": "tsx src/main.tsx", + "build": "esbuild src/main.tsx --bundle --platform=node --format=esm --target=node20 --outfile=dist/main.js --banner:js='#!/usr/bin/env node' && chmod +x dist/main.js", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "ink": "^6.5.1", + "react": "^19.2.1", + "react-devtools-core": "^6.1.5" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "esbuild": "^0.27.0", + "tsx": "^4.20.6", + "typescript": "^5.9.3" + } +} diff --git a/cli/src/App.tsx b/cli/src/App.tsx new file mode 100644 index 0000000..680f6fd --- /dev/null +++ b/cli/src/App.tsx @@ -0,0 +1,680 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { Box, Text, useApp, useInput, useStdout } from 'ink'; +import { setTimeout as sleep } from 'node:timers/promises'; +import { slashCommands, filterCommands } from './commands.js'; +import { createWorkspaceForPath, findWorkspaceByPath, loadWorkspaceCatalog, repoRoot } from './workspaces.js'; +import type { CliStatus, ExecutionMode, ModelDefinition, PermissionMode, RunMode, SkillDefinition, SlashCommand, TimelineItem, Workspace, WorkspaceCatalog } from './types.js'; +import { nowSessionId, shortPath } from './format.js'; +import { Composer, HelpPanel, Picker, SkillHint, SlashMenu, StatusLine, StatusPanel, Timeline, WelcomePanel, WorkspacePrompt } from './components.js'; +import { ApiClient, createDefaultApiClient } from './api.js'; +import { createEventRenderState, reduceTaskEvent } from './eventMapper.js'; + +type InputMode = 'workspace_prompt' | 'composer' | 'slash_menu' | 'picker' | 'status_panel' | 'help_panel' | 'approval'; + +type PickerOption = { + label: string; + description?: string; + disabled?: boolean; + onSelect: () => void | Promise; +}; + +type ApprovalOption = { + label: string; + description?: string; + decision: 'approved' | 'rejected'; +}; + +const cwd = process.cwd(); +const initialCatalog = loadWorkspaceCatalog(); +const matchedWorkspace = findWorkspaceByPath(initialCatalog, cwd); + +function id(): string { + return globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`; +} + +function initialStatus(workspace: Workspace): CliStatus { + return { + model: '连接中', + runMode: 'thinking', + directory: workspace.path, + workspace, + permissionMode: 'auto_approval', + executionMode: 'sandbox', + sessionId: nowSessionId(), + contextUsed: 0, + contextLimit: 0, + versioningEnabled: false, + backgroundAgents: 0, + backgroundCommands: 0, + }; +} + +function workspaceFromApi(item: any): Workspace { + return { + workspace_id: String(item.workspace_id || item.id || item.label || 'workspace'), + label: String(item.label || item.workspace_id || 'workspace'), + path: String(item.path || ''), + }; +} + +export default function App() { + const { exit } = useApp(); + const { stdout } = useStdout(); + const apiRef = useRef(null); + const runningTaskRef = useRef(null); + const currentConversationRef = useRef(undefined); + const bootstrappedRef = useRef(false); + const [catalog, setCatalog] = useState(initialCatalog); + const [mode, setMode] = useState(matchedWorkspace ? 'composer' : 'workspace_prompt'); + const [workspacePromptIndex, setWorkspacePromptIndex] = useState(0); + const [input, setInput] = useState(''); + const [slashIndex, setSlashIndex] = useState(0); + const [pickerTitle, setPickerTitle] = useState(''); + const [pickerOptions, setPickerOptions] = useState([]); + const [pickerIndex, setPickerIndex] = useState(0); + const [approvalTitle, setApprovalTitle] = useState(''); + const [approvalId, setApprovalId] = useState(''); + const [approvalOptions] = useState([ + { label: '允许一次', decision: 'approved' }, + { label: '拒绝', decision: 'rejected' }, + ]); + const [approvalIndex, setApprovalIndex] = useState(0); + const [timeline, setTimeline] = useState([]); + const [status, setStatus] = useState(() => initialStatus(matchedWorkspace || initialCatalog.workspaces[0]!)); + const [connected, setConnected] = useState(false); + const [connecting, setConnecting] = useState(false); + const [running, setRunning] = useState(false); + const [runningStartedAt, setRunningStartedAt] = useState(null); + const [nowMs, setNowMs] = useState(Date.now()); + const [workingTextTick, setWorkingTextTick] = useState(0); + const [workingDotTick, setWorkingDotTick] = useState(0); + const [timelineDotTick, setTimelineDotTick] = useState(0); + const [models, setModels] = useState([]); + const [skills, setSkills] = useState([]); + const [permissionOptions, setPermissionOptions] = useState([]); + const [executionOptions, setExecutionOptions] = useState([]); + + const commandMatches = useMemo(() => filterCommands(input), [input]); + const runningSeconds = runningStartedAt ? Math.max(0, Math.floor((nowMs - runningStartedAt) / 1000)) : 0; + const hasRunningTimeline = useMemo(() => timeline.some((item) => item.status === 'running'), [timeline]); + + useEffect(() => { + if (!running) return; + const timer = setInterval(() => { + setNowMs(Date.now()); + }, 1000); + return () => clearInterval(timer); + }, [running]); + + useEffect(() => { + if (!running) return; + const timer = setInterval(() => { + setWorkingTextTick((value) => value + 1); + }, 100); + return () => clearInterval(timer); + }, [running]); + + useEffect(() => { + if (!running) return; + const timer = setInterval(() => { + setWorkingDotTick((value) => value + 1); + }, 800); + return () => clearInterval(timer); + }, [running]); + + useEffect(() => { + if (!hasRunningTimeline) return; + let timer: ReturnType | undefined; + const delay = setTimeout(() => { + setTimelineDotTick((value) => value + 1); + timer = setInterval(() => { + setTimelineDotTick((value) => value + 1); + }, 800); + }, 400); + return () => { + clearTimeout(delay); + if (timer) clearInterval(timer); + }; + }, [hasRunningTimeline]); + + useEffect(() => { + if (mode !== 'composer' || bootstrappedRef.current) return; + bootstrappedRef.current = true; + getApi() + .then((api) => bootstrapConversation(api)) + .catch((err) => addItem({ kind: 'tool', title: '初始化失败', status: 'failed', body: String(err.message || err) })); + }, [mode]); + + function addItem(item: Omit) { + setTimeline((prev) => [...prev, { id: id(), ...item }]); + } + + function patchStatusFromServer(raw: any, workspaceOverride?: Workspace) { + const execMode = typeof raw?.execution_mode === 'object' ? raw.execution_mode.mode : raw?.execution_mode; + const contextUsed = Number(raw?.context?.total_size || raw?.context_tokens || raw?.token_statistics?.current_context_tokens || 0); + setStatus((prev) => ({ + ...prev, + model: raw?.model_key || prev.model, + runMode: raw?.run_mode || prev.runMode, + directory: raw?.project_path || workspaceOverride?.path || prev.directory, + workspace: workspaceOverride || prev.workspace, + permissionMode: raw?.permission_mode || prev.permissionMode, + executionMode: execMode || prev.executionMode, + sessionId: raw?.conversation?.current_id || prev.sessionId, + contextUsed: Number.isFinite(contextUsed) && contextUsed > 0 ? contextUsed : prev.contextUsed, + })); + if (raw?.conversation?.current_id) currentConversationRef.current = raw.conversation.current_id; + } + + async function getApi(): Promise { + if (!apiRef.current) apiRef.current = createDefaultApiClient(cwd, repoRoot); + if (!connected && !connecting) { + setConnecting(true); + try { + await apiRef.current.ensureConnected(); + setConnected(true); + } finally { + setConnecting(false); + } + } else if (!connected) { + while (!connected && connecting) await sleep(100); + } + return apiRef.current; + } + + async function syncBackendState(api = apiRef.current) { + if (!api) return; + try { + const wsData = await api.listWorkspaces(); + const workspaces = (wsData.workspaces || []).map(workspaceFromApi); + const current = workspaces.find((ws) => ws.workspace_id === wsData.current_workspace_id) || workspaces.find((ws) => ws.path === status.workspace.path) || status.workspace; + setCatalog({ default_workspace_id: wsData.default_workspace_id, workspaces }); + const rawStatus = await api.getStatus(); + patchStatusFromServer(rawStatus, current); + const conv = await api.getCurrentConversation().catch(() => null); + const convId = conv?.id || rawStatus?.conversation?.current_id; + if (convId) currentConversationRef.current = convId; + await syncRuntimeCatalogs(api, convId).catch(() => undefined); + } catch (err) { + addItem({ kind: 'tool', title: '同步后端状态失败', status: 'failed', body: String((err as Error).message || err) }); + } + } + + async function syncRuntimeCatalogs(api: ApiClient, conversationId?: string) { + const [modelItems, personalization, permission, execution, subAgents, backgroundCommands, tokens, versioning] = await Promise.all([ + api.listModels().catch(() => []), + api.getPersonalization().catch(() => null), + api.getPermissionMode().catch(() => null), + api.getExecutionMode().catch(() => null), + api.listSubAgents().catch(() => []), + api.listBackgroundCommands().catch(() => []), + conversationId ? api.getConversationTokens(conversationId).catch(() => null) : Promise.resolve(null), + conversationId ? api.getConversationVersioning(conversationId).catch(() => null) : Promise.resolve(null), + ]); + if (modelItems.length) setModels(modelItems); + const enabled = new Set((personalization?.data?.enabled_skills || []).map(String)); + const skillItems = (personalization?.skills_catalog || []) + .filter((skill: any) => !enabled.size || enabled.has(String(skill.id))) + .map((skill: any) => ({ id: String(skill.id), label: String(skill.label || skill.id), description: String(skill.description || '') })); + if (skillItems.length) setSkills(skillItems); + const rawCompressionLimit = Number( + personalization?.context_compression_settings?.context_window_tokens + || personalization?.context_compression_settings?.deep_trigger_tokens + || personalization?.data?.deep_compress_trigger_tokens + || 0, + ); + const currentModel = modelItems.find((item) => item.model_key === status.model); + const modelWindow = Number(currentModel?.context_window || 0); + const compressionLimit = rawCompressionLimit > 0 && modelWindow > 0 + ? Math.min(rawCompressionLimit, modelWindow) + : rawCompressionLimit || modelWindow; + if (Array.isArray(permission?.options)) setPermissionOptions(permission.options); + if (Array.isArray(execution?.options)) setExecutionOptions(execution.options); + const runningAgents = subAgents.filter((item: any) => !['completed', 'failed', 'cancelled', 'terminated', 'timeout'].includes(String(item.status || '').toLowerCase())).length; + const runningCommands = backgroundCommands.filter((item: any) => !['completed', 'failed', 'cancelled', 'timeout'].includes(String(item.status || '').toLowerCase())).length; + const tokenUsed = Number(tokens?.total_tokens || 0); + setStatus((prev) => ({ + ...prev, + contextUsed: Number.isFinite(tokenUsed) && tokenUsed > 0 ? tokenUsed : prev.contextUsed, + contextLimit: Number.isFinite(compressionLimit) && compressionLimit > 0 ? compressionLimit : prev.contextLimit, + versioningEnabled: versioning ? Boolean(versioning.enabled) : prev.versioningEnabled, + backgroundAgents: runningAgents, + backgroundCommands: runningCommands, + })); + } + + async function bootstrapConversation(api: ApiClient) { + const result = await api.createConversation(); + const convId = result.conversation_id || result.data?.conversation_id || result.data?.id || result.id; + if (convId) { + currentConversationRef.current = String(convId); + setStatus((prev) => ({ ...prev, sessionId: String(convId), contextUsed: 0 })); + await syncBackendState(api); + await syncRuntimeCatalogs(api, String(convId)).catch(() => undefined); + } + } + + function openPicker(title: string, options: PickerOption[]) { + setPickerTitle(title); + setPickerOptions(options); + setPickerIndex(0); + setMode('picker'); + } + + function closeOverlay() { + setMode('composer'); + setInput(''); + setSlashIndex(0); + setPickerIndex(0); + } + + async function executeCommand(command: SlashCommand) { + const name = command.name; + try { + if (name === '/new') { + const api = await getApi(); + const result = await api.createConversation(); + const convId = result.conversation_id || result.data?.conversation_id; + currentConversationRef.current = convId; + setTimeline([]); + setStatus((prev) => ({ ...prev, sessionId: convId || nowSessionId(), contextUsed: 0 })); + if (convId) await syncRuntimeCatalogs(api, String(convId)).catch(() => undefined); + closeOverlay(); + return; + } + if (name === '/resume') { + const api = await getApi(); + const conversations = await api.listConversations(30); + openPicker('Resume conversation', conversations.map((conv: any) => ({ + label: String(conv.title || conv.id || '未命名对话'), + description: String(conv.updated_at || conv.created_at || conv.id || ''), + onSelect: async () => { + await api.loadConversation(String(conv.id)); + currentConversationRef.current = String(conv.id); + setStatus((prev) => ({ ...prev, sessionId: String(conv.id) })); + await syncRuntimeCatalogs(api, String(conv.id)).catch(() => undefined); + addItem({ kind: 'system', body: `已切换对话:${conv.title || conv.id}` }); + closeOverlay(); + }, + }))); + return; + } + if (name === '/model') { + const api = await getApi(); + const modelItems = (await api.listModels().catch(() => models)).filter((item) => item.model_key); + if (modelItems.length) setModels(modelItems); + const currentRunMode = status.runMode; + openPicker('Model', modelItems.map((model) => ({ + label: model.name || model.model_key, + description: `${model.model_key === status.model ? 'current · ' : ''}${model.description || model.model_key}`, + onSelect: async () => { + await api.setModel(model.model_key); + setStatus((prev) => ({ ...prev, model: model.model_key, contextLimit: Number(model.context_window) || prev.contextLimit })); + addItem({ kind: 'system', body: `已切换模型:${model.name || model.model_key}` }); + const modes = getRunModesForModel(model); + openPicker('Run Mode', modes.map((runMode) => ({ + label: runMode, + description: runMode === currentRunMode ? 'current' : '', + onSelect: async () => { + await api.setRunMode(runMode); + setStatus((prev) => ({ ...prev, runMode })); + await syncBackendState(api); + addItem({ kind: 'system', body: `已切换运行模式:${runMode}` }); + closeOverlay(); + }, + }))); + }, + }))); + return; + } + if (name === '/versioning') { + const api = await getApi(); + const convId = currentConversationRef.current || status.sessionId; + const current = await api.getConversationVersioning(convId).catch(() => null); + const enabled = current ? Boolean(current.enabled) : status.versioningEnabled; + const trackingMode = current?.tracking_mode; + openPicker('Versioning', [ + { label: enabled ? '关闭版本控制' : '开启版本控制', onSelect: async () => { + const result = await api.setConversationVersioning(convId, !enabled, trackingMode); + setStatus((prev) => ({ ...prev, versioningEnabled: Boolean(result.enabled) })); + addItem({ kind: 'system', body: `版本控制:${result.enabled ? '开' : '关'}` }); + closeOverlay(); + } }, + ]); + return; + } + if (name === '/permission') { + const api = await getApi(); + const current = await api.getPermissionMode().catch(() => null); + const modes: PermissionMode[] = Array.isArray(current?.options) && current.options.length ? current.options : (permissionOptions.length ? permissionOptions : [status.permissionMode]); + openPicker('Permission Mode', modes.map((permissionMode) => ({ label: permissionMode, description: permissionMode === status.permissionMode ? 'current' : '', onSelect: async () => { + await api.setPermissionMode(permissionMode); + setStatus((prev) => ({ ...prev, permissionMode })); + addItem({ kind: 'system', body: `权限模式:${permissionMode}` }); + closeOverlay(); + } }))); + return; + } + if (name === '/execution') { + const api = await getApi(); + const current = await api.getExecutionMode().catch(() => null); + const modes: ExecutionMode[] = Array.isArray(current?.options) && current.options.length ? current.options : (executionOptions.length ? executionOptions : [status.executionMode]); + openPicker('Execution Environment', modes.map((executionMode) => ({ label: executionMode, description: executionMode === status.executionMode ? 'current' : executionMode === 'direct' ? '高风险' : '', onSelect: async () => { + await api.setExecutionMode(executionMode); + setStatus((prev) => ({ ...prev, executionMode })); + addItem({ kind: 'system', body: `执行环境:${executionMode}` }); + closeOverlay(); + } }))); + return; + } + if (name === '/workspace') { + const api = await getApi(); + const wsData = await api.listWorkspaces(); + const workspaces = (wsData.workspaces || []).map(workspaceFromApi); + setCatalog({ default_workspace_id: wsData.default_workspace_id, workspaces }); + openPicker('Workspace', workspaces.map((workspace) => ({ label: workspace.label, description: `${shortPath(workspace.path)}${workspace.workspace_id === wsData.current_workspace_id ? ' current' : ''}`, onSelect: async () => { + await api.selectWorkspace(workspace.workspace_id); + setStatus((prev) => ({ ...prev, workspace, directory: workspace.path })); + addItem({ kind: 'system', body: `已切换工作区:${workspace.label}` }); + await syncBackendState(api); + closeOverlay(); + } }))); + return; + } + if (name === '/compact') { + const api = await getApi(); + const convId = currentConversationRef.current || status.sessionId; + const result = await api.compressConversation(convId); + addItem({ kind: 'tool', title: '对话已压缩', body: result.compact_file || '', status: 'success' }); + if (result.compressed_conversation_id) { + currentConversationRef.current = result.compressed_conversation_id; + setStatus((prev) => ({ ...prev, sessionId: result.compressed_conversation_id })); + } + closeOverlay(); + return; + } + if (name === '/tools') { + const api = await getApi(); + const snapshot = await api.getToolSettings(); + const categories = snapshot.categories || {}; + openPicker('Tools', Object.entries(categories).map(([key, value]: [string, any]) => ({ label: `${value.enabled ? '[x]' : '[ ]'} ${key}`, description: value.label || '', onSelect: async () => { + await api.setToolCategory(key, !value.enabled); + addItem({ kind: 'system', body: `工具 ${key}:${!value.enabled ? '启用' : '禁用'}` }); + closeOverlay(); + } }))); + return; + } + if (name === '/path') { + const api = await getApi(); + const auth = await api.getPathAuthorization(); + openPicker('Authorized Paths', [ + { label: '当前可读写路径', description: (auth.writable_paths || []).join(', ') || '无', onSelect: () => closeOverlay() }, + { label: '当前只读路径', description: (auth.readable_extra_paths || []).join(', ') || '无', onSelect: () => closeOverlay() }, + ]); + return; + } + if (name === '/skill') { + const api = await getApi(); + const personalization = await api.getPersonalization().catch(() => null); + const enabled = new Set((personalization?.data?.enabled_skills || []).map(String)); + const skillItems = (personalization?.skills_catalog || skills) + .filter((skill: any) => !enabled.size || enabled.has(String(skill.id))) + .map((skill: any) => ({ id: String(skill.id), label: String(skill.label || skill.id), description: String(skill.description || '') })); + if (skillItems.length) setSkills(skillItems); + openPicker('Skill', skillItems.map((skill: SkillDefinition) => ({ label: skill.label, description: skill.description, onSelect: () => { + setStatus((prev) => ({ ...prev, activeSkill: skill })); + addItem({ kind: 'system', body: `下一条消息将使用 skill:${skill.label}` }); + closeOverlay(); + } })).concat(skillItems.length ? [] : [{ label: '暂无可用 skill', disabled: true, onSelect: () => closeOverlay() }])); + return; + } + if (name === '/status') { + await getApi().then((api) => syncBackendState(api)).catch(() => undefined); + setMode('status_panel'); + setInput(''); + return; + } + if (name === '/help') { + setMode('help_panel'); + setInput(''); + return; + } + if (name === '/clear') { + setTimeline([]); + closeOverlay(); + return; + } + if (name === '/exit') { + if (runningTaskRef.current) await apiRef.current?.cancelTask(runningTaskRef.current).catch(() => undefined); + exit(); + } + } catch (err) { + addItem({ kind: 'tool', title: `命令失败:${name}`, status: 'failed', body: String((err as Error).message || err) }); + closeOverlay(); + } + } + + async function submitUserMessage(text: string) { + addItem({ kind: 'user', body: text }); + const skill = status.activeSkill; + const message = skill ? `${text}\n\n先阅读 ${skill.label} skill` : text; + if (skill) addItem({ kind: 'user', body: `先阅读 ${skill.label} skill` }); + setStatus((prev) => ({ ...prev, activeSkill: undefined })); + setRunning(true); + setRunningStartedAt(Date.now()); + try { + const api = await getApi(); + const task = await api.createTask({ message, conversation_id: currentConversationRef.current, model_key: status.model, run_mode: status.runMode }); + runningTaskRef.current = task.task_id; + if (task.conversation_id) currentConversationRef.current = task.conversation_id; + await pollTask(api, task.task_id); + await syncBackendState(api); + } catch (err) { + addItem({ kind: 'tool', title: '发送失败', status: 'failed', body: String((err as Error).message || err) }); + } finally { + runningTaskRef.current = null; + setRunning(false); + setRunningStartedAt(null); + } + } + + async function pollTask(api: ApiClient, taskId: string) { + let offset = 0; + const renderState = createEventRenderState(); + for (;;) { + const result = await api.pollTask(taskId, offset); + offset = result.next_offset; + for (const event of result.events || []) { + if (event.type === 'tool_approval_required') { + const approval = event.data?.approval || event.data; + if (approval?.approval_id) { + setApprovalId(String(approval.approval_id)); + setApprovalTitle(`需要审批:${approval.tool_name || '工具调用'}`); + setApprovalIndex(0); + setMode('approval'); + } + } + setTimeline((prev) => reduceTaskEvent(prev, renderState, event)); + } + if (!['pending', 'running', 'cancel_requested'].includes(String(result.status))) break; + await sleep(700); + } + } + + useInput((chunk, key) => { + void handleInput(chunk, key); + }); + + async function handleInput(chunk: string, key: any) { + if (mode === 'workspace_prompt') { + if (key.upArrow || chunk === 'k') setWorkspacePromptIndex(0); + else if (key.downArrow || chunk === 'j') setWorkspacePromptIndex(1); + else if (key.return) { + if (workspacePromptIndex === 0) { + const result = createWorkspaceForPath(catalog, cwd); + setCatalog(result.catalog); + setStatus((prev) => ({ ...prev, workspace: result.workspace, directory: result.workspace.path })); + bootstrappedRef.current = true; + setMode('composer'); + getApi().then(async (api) => { + await api.createWorkspace(result.workspace.path, result.workspace.label).catch(() => undefined); + const wsData = await api.listWorkspaces().catch(() => null); + const backendWs = wsData?.workspaces?.find((ws: any) => String(ws.path) === result.workspace.path); + if (backendWs) await api.selectWorkspace(String(backendWs.workspace_id)); + await bootstrapConversation(api); + }).catch((err) => addItem({ kind: 'tool', title: '连接后端失败', status: 'failed', body: String(err.message || err) })); + } else { + exit(); + } + } + return; + } + + if (mode === 'approval') { + if (key.upArrow) setApprovalIndex((prev) => Math.max(0, prev - 1)); + else if (key.downArrow) setApprovalIndex((prev) => Math.min(approvalOptions.length - 1, prev + 1)); + else if (key.return && approvalId) { + const selected = approvalOptions[approvalIndex]!; + await apiRef.current?.decideApproval(approvalId, selected.decision).catch((err) => addItem({ kind: 'tool', title: '审批提交失败', status: 'failed', body: String(err.message || err) })); + addItem({ kind: 'system', body: `审批已${selected.decision === 'approved' ? '允许' : '拒绝'}` }); + setMode('composer'); + } + return; + } + + if (key.escape) { + if (runningTaskRef.current) { + await apiRef.current?.cancelTask(runningTaskRef.current).catch(() => undefined); + addItem({ kind: 'system', body: '已请求停止当前任务。' }); + return; + } + closeOverlay(); + return; + } + + if (mode === 'status_panel' || mode === 'help_panel') { + if (key.return || chunk === 'q') closeOverlay(); + return; + } + + if (mode === 'picker') { + if (key.upArrow) setPickerIndex((prev) => Math.max(0, prev - 1)); + else if (key.downArrow) setPickerIndex((prev) => Math.min(pickerOptions.length - 1, prev + 1)); + else if (key.return) await pickerOptions[pickerIndex]?.onSelect(); + return; + } + + if (key.ctrl && chunk === 'c') { + exit(); + return; + } + + if (mode === 'slash_menu') { + if (key.upArrow) setSlashIndex((prev) => Math.max(0, prev - 1)); + else if (key.downArrow) setSlashIndex((prev) => Math.min(commandMatches.length - 1, prev + 1)); + else if (key.return) { + const command = commandMatches[slashIndex]; + if (command) await executeCommand(command); + } else if (key.backspace || key.delete) { + setInput((prev) => { + const next = Array.from(prev).slice(0, -1).join(''); + if (!next) setMode('composer'); + return next; + }); + setSlashIndex(0); + } else if (chunk && !key.ctrl && !key.meta) { + const normalized = chunk.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + setInput((prev) => prev + normalized); + setSlashIndex(0); + } + return; + } + + if (key.return) { + const text = input.trim(); + if (!text) return; + if (text.startsWith('/')) { + const command = filterCommands(text)[0]; + if (command) await executeCommand(command); + return; + } + setInput(''); + await submitUserMessage(text); + return; + } + + if (key.backspace || key.delete) { + setInput((prev) => Array.from(prev).slice(0, -1).join('')); + return; + } + + if (chunk && !key.ctrl && !key.meta) { + const normalized = chunk.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + setInput((prev) => { + const next = prev + normalized; + if (next.startsWith('/')) { + setMode('slash_menu'); + setSlashIndex(0); + } + return next; + }); + } + } + + if (mode === 'workspace_prompt') { + return ; + } + + return ( + + + {timeline.length === 0 ? : } + {timeline.length > 0 && timeline[timeline.length - 1]?.kind === 'assistant' ? : null} + + + {running ? ( + + + {workingDotTick % 2 === 0 ? '◦' : '•'}{' '} + + {` (${runningSeconds}s • 按 Esc 停止)`} + + + ) : null} + {mode === 'slash_menu' ? : null} + {mode === 'picker' ? : null} + {mode === 'status_panel' ? : null} + {mode === 'help_panel' ? : null} + {mode === 'approval' ? : null} + {mode === 'composer' || mode === 'slash_menu' ? <> : null} + + + + ); +} + +function AnimatedSummary({ text, tick }: { text: string; tick: number }) { + const chars = Array.from(text || '正在执行...'); + const cycle = chars.length + 8; + const head = tick % cycle; + return ( + <> + {chars.map((char, index) => { + const distance = head - index; + const active = head < chars.length + 4 && distance >= 0 && distance < 5; + return ( + + {char === ' ' ? '\u00A0' : char} + + ); + })} + + ); +} + +function getRunModesForModel(model?: ModelDefinition): RunMode[] { + if (!model) return ['fast', 'thinking', 'deep']; + if (model.deep_only) return ['deep']; + if (model.fast_only || !model.supports_thinking) return ['fast']; + return ['fast', 'thinking', 'deep']; +} diff --git a/cli/src/api.ts b/cli/src/api.ts new file mode 100644 index 0000000..65964d6 --- /dev/null +++ b/cli/src/api.ts @@ -0,0 +1,313 @@ +import { spawn, type ChildProcess } from 'node:child_process'; +import { setTimeout as sleep } from 'node:timers/promises'; +import { basename } from 'node:path'; +import type { ExecutionMode, ModelDefinition, PermissionMode, RunMode, Workspace } from './types.js'; + +export type ApiConfig = { + baseUrl: string; + repoRoot: string; + cwd: string; + port: number; +}; + +export type TaskPollResult = { + task_id: string; + status: string; + error?: string; + conversation_id?: string; + events: Array<{ idx: number; type: string; data: any; ts?: number }>; + next_offset: number; +}; + +export class ApiClient { + private cookie = ''; + private csrfToken = ''; + private serverProcess: ChildProcess | null = null; + + constructor(private readonly config: ApiConfig) {} + + get baseUrl(): string { + return this.config.baseUrl; + } + + async ensureConnected(): Promise { + const ok = await this.tryFetchCsrf(); + if (!ok) { + this.startServer(); + await this.waitForServer(); + await this.fetchCsrf(); + } + await this.hostLogin(); + await this.fetchCsrf(); + } + + private startServer(): void { + if (this.serverProcess) return; + const env = { + ...process.env, + TERMINAL_SANDBOX_MODE: 'host', + HOST_PROJECT_PATH: this.config.cwd, + WEB_SERVER_PORT: String(this.config.port), + AGENTS_CLI_STARTED_SERVER: '1', + }; + this.serverProcess = spawn( + process.execPath.includes('node') ? 'python3' : 'python3', + ['-m', 'server.app', '--path', this.config.cwd, '--port', String(this.config.port), '--thinking-mode'], + { + cwd: this.config.repoRoot, + env, + detached: true, + stdio: 'ignore', + }, + ); + this.serverProcess.unref(); + } + + private async waitForServer(): Promise { + const deadline = Date.now() + 20_000; + let lastError: unknown; + while (Date.now() < deadline) { + const ok = await this.tryFetchCsrf().catch((err) => { + lastError = err; + return false; + }); + if (ok) return; + await sleep(500); + } + throw new Error(`无法连接本地 Web 服务:${String(lastError || 'timeout')}`); + } + + private async tryFetchCsrf(): Promise { + try { + await this.fetchCsrf(); + return true; + } catch { + return false; + } + } + + async fetchCsrf(): Promise { + const data = await this.request<{ token: string }>('/api/csrf-token', { method: 'GET', skipCsrf: true, allowUnauthorized: true }); + this.csrfToken = data.token; + return this.csrfToken; + } + + async hostLogin(): Promise { + await this.request('/host-login', { method: 'POST' }); + } + + async getStatus(): Promise { + return this.request('/api/status', { method: 'GET' }); + } + + async listModels(): Promise { + const res = await this.request<{ items?: ModelDefinition[]; data?: any }>('/api/v1/models', { method: 'GET' }); + return res.items || res.data?.items || []; + } + + async getPersonalization(): Promise { + return this.request('/api/personalization', { method: 'GET' }); + } + + async listWorkspaces(): Promise<{ current_workspace_id: string; default_workspace_id: string; workspaces: Workspace[] }> { + const res = await this.request<{ data: any }>('/api/host/workspaces', { method: 'GET' }); + return res.data; + } + + async createWorkspace(path: string, label = basename(path)): Promise<{ workspace: Workspace; created: boolean }> { + const res = await this.request<{ data: any }>('/api/host/workspaces/create', { + method: 'POST', + body: { path, label }, + }); + return res.data; + } + + async selectWorkspace(workspaceId: string): Promise { + await this.request('/api/host/workspaces/select', { + method: 'POST', + body: { workspace_id: workspaceId }, + }); + } + + async createConversation(): Promise { + return this.request('/api/conversations', { method: 'POST', body: {} }); + } + + async listConversations(limit = 20): Promise { + const res = await this.request<{ data: any }>(`/api/conversations?limit=${limit}`, { method: 'GET' }); + return res.data?.conversations || res.data || []; + } + + async loadConversation(conversationId: string): Promise { + return this.request(`/api/conversations/${encodeURIComponent(conversationId)}/load`, { method: 'PUT', body: {} }); + } + + async getCurrentConversation(): Promise { + const res = await this.request<{ data: any }>('/api/conversations/current', { method: 'GET' }); + return res.data; + } + + async compressConversation(conversationId: string): Promise { + return this.request(`/api/conversations/${encodeURIComponent(conversationId)}/compress`, { method: 'POST', body: {} }); + } + + async setModel(modelKey: string): Promise { + return this.request('/api/model', { method: 'POST', body: { model_key: modelKey } }); + } + + async setRunMode(mode: RunMode): Promise { + return this.request('/api/thinking-mode', { method: 'POST', body: { mode } }); + } + + async getPermissionMode(): Promise { + return this.request('/api/permission-mode', { method: 'GET' }); + } + + async setPermissionMode(mode: PermissionMode): Promise { + return this.request('/api/permission-mode', { method: 'POST', body: { mode } }); + } + + async getExecutionMode(): Promise { + return this.request('/api/execution-mode', { method: 'GET' }); + } + + async getConversationVersioning(conversationId: string): Promise { + const res = await this.request<{ data: any }>(`/api/conversations/${encodeURIComponent(conversationId)}/versioning`, { method: 'GET' }); + return res.data; + } + + async setConversationVersioning(conversationId: string, enabled: boolean, trackingMode?: string): Promise { + const res = await this.request<{ data: any }>(`/api/conversations/${encodeURIComponent(conversationId)}/versioning`, { + method: 'POST', + body: { enabled, ...(trackingMode ? { tracking_mode: trackingMode } : {}) }, + }); + return res.data; + } + + async getConversationTokens(conversationId: string): Promise { + const res = await this.request<{ data: any }>(`/api/conversations/${encodeURIComponent(conversationId)}/tokens`, { method: 'GET' }); + return res.data; + } + + async listSubAgents(): Promise { + const res = await this.request<{ data: any[] }>('/api/sub_agents', { method: 'GET' }); + return Array.isArray(res.data) ? res.data : []; + } + + async listBackgroundCommands(): Promise { + const res = await this.request<{ data: any[] }>('/api/background_commands', { method: 'GET' }); + return Array.isArray(res.data) ? res.data : []; + } + + async setExecutionMode(mode: ExecutionMode): Promise { + return this.request('/api/execution-mode', { method: 'POST', body: { mode } }); + } + + async getToolSettings(): Promise { + return this.request('/api/tool-settings', { method: 'GET' }); + } + + async setToolCategory(category: string, enabled: boolean): Promise { + return this.request('/api/tool-settings', { method: 'POST', body: { category, enabled } }); + } + + async getPathAuthorization(): Promise { + return this.request('/api/path-authorization', { method: 'GET' }); + } + + async setPathAuthorization(writablePaths: string[], readableExtraPaths: string[]): Promise { + return this.request('/api/path-authorization', { + method: 'POST', + body: { writable_paths: writablePaths, readable_extra_paths: readableExtraPaths }, + }); + } + + async createTask(payload: { message: string; conversation_id?: string; model_key?: string; run_mode?: RunMode }): Promise { + const res = await this.request<{ data: any }>('/api/tasks', { + method: 'POST', + body: payload, + }); + return res.data; + } + + async pollTask(taskId: string, offset: number): Promise { + const res = await this.request<{ data: TaskPollResult }>(`/api/tasks/${encodeURIComponent(taskId)}?from=${offset}`, { method: 'GET' }); + return res.data; + } + + async cancelTask(taskId: string): Promise { + await this.request(`/api/tasks/${encodeURIComponent(taskId)}/cancel`, { method: 'POST', body: {} }); + } + + async decideApproval(approvalId: string, decision: 'approved' | 'rejected'): Promise { + return this.request(`/api/tool-approvals/${encodeURIComponent(approvalId)}/decision`, { + method: 'POST', + body: { decision }, + }); + } + + private async request(path: string, options: { method: string; body?: any; skipCsrf?: boolean; allowUnauthorized?: boolean }): Promise { + const headers: Record = { Accept: 'application/json' }; + if (this.cookie) headers.Cookie = this.cookie; + if (options.body !== undefined) headers['Content-Type'] = 'application/json'; + if (!options.skipCsrf && options.method !== 'GET' && this.csrfToken) headers['X-CSRF-Token'] = this.csrfToken; + + const response = await fetch(`${this.config.baseUrl}${path}`, { + method: options.method, + headers, + body: options.body !== undefined ? JSON.stringify(options.body) : undefined, + redirect: 'manual', + }); + this.captureCookies(response); + + if (response.status === 401 && !options.allowUnauthorized) { + await this.fetchCsrf(); + await this.hostLogin(); + return this.request(path, { ...options, allowUnauthorized: true }); + } + + const text = await response.text(); + let data: any = {}; + if (text) { + try { + data = JSON.parse(text); + } catch { + data = { success: false, error: text }; + } + } + if (!response.ok || data?.success === false) { + throw new Error(data?.error || data?.message || `HTTP ${response.status}`); + } + return data as T; + } + + private captureCookies(response: Response): void { + const anyHeaders = response.headers as any; + const rawCookies: string[] = typeof anyHeaders.getSetCookie === 'function' + ? anyHeaders.getSetCookie() + : []; + const fallback = response.headers.get('set-cookie'); + if (fallback) rawCookies.push(fallback); + if (!rawCookies.length) return; + + const current = new Map(); + for (const part of this.cookie.split(';')) { + const trimmed = part.trim(); + if (!trimmed) continue; + const eq = trimmed.indexOf('='); + if (eq > 0) current.set(trimmed.slice(0, eq), trimmed.slice(eq + 1)); + } + for (const raw of rawCookies) { + const cookiePair = raw.split(';', 1)[0]; + const eq = cookiePair.indexOf('='); + if (eq > 0) current.set(cookiePair.slice(0, eq), cookiePair.slice(eq + 1)); + } + this.cookie = Array.from(current.entries()).map(([key, value]) => `${key}=${value}`).join('; '); + } +} + +export function createDefaultApiClient(cwd: string, repoRoot: string): ApiClient { + const port = Number(process.env.AGENTS_API_PORT || process.env.WEB_SERVER_PORT || 8091); + const baseUrl = process.env.AGENTS_API_BASE || `http://127.0.0.1:${port}`; + return new ApiClient({ baseUrl, repoRoot, cwd, port }); +} diff --git a/cli/src/commands.ts b/cli/src/commands.ts new file mode 100644 index 0000000..9f6c390 --- /dev/null +++ b/cli/src/commands.ts @@ -0,0 +1,28 @@ +import type { SlashCommand } from './types.js'; + +export const slashCommands: SlashCommand[] = [ + { name: '/new', description: '新建对话', kind: 'immediate' }, + { name: '/resume', description: '查看对话列表并切换对话', kind: 'picker' }, + { name: '/model', description: '切换模型与思考模式', kind: 'picker' }, + { name: '/versioning', description: '管理版本控制', aliases: ['/version'], kind: 'picker' }, + { name: '/permission', description: '切换权限模式', aliases: ['/perm'], kind: 'picker' }, + { name: '/execution', description: '切换执行环境', aliases: ['/exec', '/env'], kind: 'picker' }, + { name: '/workspace', description: '选择或切换工作区', kind: 'picker' }, + { name: '/compact', description: '压缩当前对话上下文', kind: 'immediate' }, + { name: '/tools', description: '启用或禁用工具', aliases: ['/tool'], kind: 'picker' }, + { name: '/path', description: '管理路径授权', kind: 'form' }, + { name: '/skill', description: '选择本轮要使用的 skill', kind: 'picker' }, + { name: '/status', description: '查看当前状态', kind: 'panel' }, + { name: '/help', description: '查看命令帮助', kind: 'panel' }, + { name: '/clear', description: '清空当前 CLI 显示', kind: 'immediate' }, + { name: '/exit', description: '退出 CLI', kind: 'immediate' }, +]; + +export function filterCommands(query: string): SlashCommand[] { + const normalized = query.trim().toLowerCase(); + if (!normalized || normalized === '/') return slashCommands; + return slashCommands.filter((command) => { + const candidates = [command.name, ...(command.aliases || [])]; + return candidates.some((name) => name.toLowerCase().startsWith(normalized) || name.toLowerCase().includes(normalized)); + }); +} diff --git a/cli/src/components.tsx b/cli/src/components.tsx new file mode 100644 index 0000000..bb7ab6c --- /dev/null +++ b/cli/src/components.tsx @@ -0,0 +1,333 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { Box, Text, useCursor, useStdout, type DOMElement } from 'ink'; +import type { CliStatus, SlashCommand, SkillDefinition, TimelineItem, Workspace } from './types.js'; +import { formatContext, shortPath } from './format.js'; + +const SHOW_THINKING_DETAILS = false; + +export function StatusLine({ status }: { status: CliStatus }) { + const skill = status.activeSkill ? ` skill:${status.activeSkill.label}` : ''; + const background = [ + status.backgroundAgents > 0 ? `${status.backgroundAgents}后台智能体` : '', + status.backgroundCommands > 0 ? `${status.backgroundCommands}后台指令` : '', + ].filter(Boolean).join(' · '); + const left = `${status.model} · ${status.workspace.label} · ${status.permissionMode} · ${status.executionMode}${background ? ` · ${background}` : ''} · 版本控制:${status.versioningEnabled ? '开' : '关'}${skill}`; + const right = formatContext(status.contextUsed, status.contextLimit); + return ( + + {left} + {right} + + ); +} + +export function Composer({ value, disabled, marginTop = 1 }: { value: string; disabled?: boolean; marginTop?: number }) { + const { stdout } = useStdout(); + const { setCursorPosition } = useCursor(); + const contentRef = useRef(null); + const [contentOrigin, setContentOrigin] = useState<{ x: number; y: number } | null>(null); + const lines = String(value || '').split(/\r?\n/); + const lastLine = lines[lines.length - 1] || ''; + const fallbackOrigin = { x: 1, y: Math.max(0, (stdout.rows || 24) - 3) }; + const origin = contentOrigin || fallbackOrigin; + setCursorPosition({ + x: origin.x + 2 + displayWidth(lastLine), + y: origin.y + Math.max(0, lines.length - 1), + }); + + useEffect(() => { + const next = getAbsoluteOrigin(contentRef.current); + if (!next) return; + setContentOrigin((prev) => (prev && prev.x === next.x && prev.y === next.y ? prev : next)); + }); + + return ( + + + + {lines.map((line, index) => ( + + {index === 0 ? : } + {line} + {index === lines.length - 1 ? : null} + + ))} + + + + ); +} + +function getAbsoluteOrigin(node: DOMElement | null): { x: number; y: number } | null { + if (!node?.yogaNode) return null; + let x = 0; + let y = 0; + let current: DOMElement | undefined = node; + while (current?.parentNode) { + if (!current.yogaNode) return null; + x += current.yogaNode.getComputedLeft(); + y += current.yogaNode.getComputedTop(); + current = current.parentNode; + } + return { x, y }; +} + +function displayWidth(text: string): number { + let width = 0; + for (const char of Array.from(text)) { + const code = char.codePointAt(0) || 0; + if (code === 0) continue; + if (code < 32 || (code >= 0x7f && code < 0xa0)) continue; + width += isWideCodePoint(code) ? 2 : 1; + } + return width; +} + +function isWideCodePoint(code: number): boolean { + return ( + code >= 0x1100 && ( + code <= 0x115f || + code === 0x2329 || + code === 0x232a || + (code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) || + (code >= 0xac00 && code <= 0xd7a3) || + (code >= 0xf900 && code <= 0xfaff) || + (code >= 0xfe10 && code <= 0xfe19) || + (code >= 0xfe30 && code <= 0xfe6f) || + (code >= 0xff00 && code <= 0xff60) || + (code >= 0xffe0 && code <= 0xffe6) || + (code >= 0x1f300 && code <= 0x1f64f) || + (code >= 0x1f900 && code <= 0x1f9ff) || + (code >= 0x20000 && code <= 0x3fffd) + ) + ); +} + +export function WelcomePanel({ status }: { status: CliStatus }) { + return ( + + Agents + + Welcome back! + {status.model} · {status.runMode} + {shortPath(status.directory)} + + + ); +} + +export function Timeline({ items, pulseTick = 0, width = 78 }: { items: TimelineItem[]; pulseTick?: number; width?: number }) { + return ( + + {items.map((item) => ( + + ))} + + ); +} + +function TimelineRow({ item, pulseTick }: { item: TimelineItem; pulseTick: number }) { + const separator = useFullWidthSeparator(); + if (item.kind === 'user') { + const lines = String(item.body || '').split(/\r?\n/); + return ( + + + {lines.map((line, index) => ( + + {index === 0 ? : } + {line} + + ))} + + + ); + } + if (item.kind === 'assistant') { + return ( + + {separator} + + {wrapIndentedText(String(item.body || ''), 2, separator.length).map((line, index) => ( + {line} + ))} + {item.status === 'success' ? <> {separator} : null} + + ); + } + if (item.kind === 'system') { + return • {item.body}; + } + const running = item.status === 'running'; + const failed = item.status === 'failed'; + const color = running ? (pulseTick % 2 === 0 ? 'gray' : undefined) : failed ? 'red' : item.kind === 'thinking' ? undefined : 'green'; + const dot = running ? (pulseTick % 2 === 0 ? '◦' : '•') : '•'; + const detailColor = item.kind === 'thinking' || item.kind === 'tool' || item.kind === 'sub_agent' ? 'gray' : undefined; + const detailWidth = separator.length; + const showDetails = item.kind === 'thinking' ? SHOW_THINKING_DETAILS : true; + return ( + + {dot} {item.title || item.kind} + {showDetails && !running && item.body ? wrapIndentedText(`└ ${item.body}`, 2, detailWidth).map((line, index) => ( + {line} + )) : null} + {showDetails && (item.kind === 'thinking' || !running) && (item.lines || []).map((line, index) => ( + wrapIndentedText(line, index === 0 && !item.body ? 2 : 4, detailWidth).map((wrapped, wrappedIndex) => ( + + {index === 0 && !item.body && wrappedIndex === 0 ? wrapped.replace(/^ /, ' └ ') : wrapped} + + )) + ))} + + ); +} + +function useFullWidthSeparator(): string { + const { stdout } = useStdout(); + const width = Math.max(2, (stdout.columns || 80) - 2); + return '─'.repeat(width); +} + +function wrapIndentedText(text: string, indent: number, width: number): string[] { + const prefix = ' '.repeat(indent); + const contentWidth = Math.max(10, width - indent); + const result: string[] = []; + for (const rawLine of text.split(/\r?\n/)) { + const line = rawLine || ''; + if (displayWidth(line) <= contentWidth) { + result.push(`${prefix}${line}`); + continue; + } + let current = ''; + let currentWidth = 0; + for (const char of Array.from(line)) { + const charWidth = displayWidth(char); + if (current && currentWidth + charWidth > contentWidth) { + result.push(`${prefix}${current}`); + current = char; + currentWidth = charWidth; + } else { + current += char; + currentWidth += charWidth; + } + } + result.push(`${prefix}${current}`); + } + return result.length ? result : [prefix]; +} + +export function SlashMenu({ query, commands, selectedIndex }: { query: string; commands: SlashCommand[]; selectedIndex: number }) { + const visibleCount = 5; + const start = Math.max(0, selectedIndex - 2); + const end = Math.min(commands.length, start + visibleCount); + const adjustedStart = Math.max(0, end - visibleCount); + const visibleCommands = commands.slice(adjustedStart, end); + return ( + + + {visibleCommands.map((command, visibleIndex) => { + const index = adjustedStart + visibleIndex; + return ( + + {index === selectedIndex ? '› ' : ' '}{command.name.padEnd(13)} {command.description} + + ); + })} + {!commands.length ? 无匹配命令 : null} + + + ); +} + +export function Picker({ title, items, selectedIndex }: { title: string; items: Array<{ label: string; description?: string; disabled?: boolean }>; selectedIndex: number }) { + const visibleCount = 5; + const start = Math.max(0, selectedIndex - 2); + const end = Math.min(items.length, start + visibleCount); + const adjustedStart = Math.max(0, end - visibleCount); + const visibleItems = items.slice(adjustedStart, end); + return ( + + {title} + + {visibleItems.map((item, visibleIndex) => { + const index = adjustedStart + visibleIndex; + return ( + + {index === selectedIndex ? '› ' : ' '}{item.label.padEnd(20)} {item.description || ''} + + ); + })} + {!items.length ? 暂无选项 : null} + + + ); +} + +export function WorkspacePrompt({ cwd, selectedIndex }: { cwd: string; selectedIndex: number }) { + const options = ['确认,添加并继续', '拒绝,退出']; + return ( + + 当前目录尚未添加为工作区: + {shortPath(cwd)} + + Agent 将在此目录中读取文件、执行命令和修改内容。 + 是否将此目录添加为工作区? + + + {options.map((label, index) => ( + + {index === selectedIndex ? '› ' : ' '}{index + 1}. {label} + + ))} + + + ); +} + +export function StatusPanel({ status }: { status: CliStatus }) { + return ( + + {'>_ Agents CLI'} + + + + + + + + + + ); +} + +function StatusField({ label, value }: { label: string; value: string }) { + return {label.padEnd(15)} {value}; +} + +export function HelpPanel({ commands }: { commands: SlashCommand[] }) { + return ( + + Commands + + {commands.map((command) => ( + {command.name.padEnd(13)} {command.description} + ))} + + + ); +} + +export function SkillHint({ skill }: { skill?: SkillDefinition }) { + if (!skill) return null; + return + 先阅读 {skill.label} skill; +} + +export function WorkspaceList({ workspaces, selectedIndex }: { workspaces: Workspace[]; selectedIndex: number }) { + return ( + ({ label: workspace.label, description: shortPath(workspace.path) }))} + /> + ); +} diff --git a/cli/src/eventMapper.ts b/cli/src/eventMapper.ts new file mode 100644 index 0000000..deec0c0 --- /dev/null +++ b/cli/src/eventMapper.ts @@ -0,0 +1,281 @@ +import type { TimelineItem } from './types.js'; +import { lastLines } from './format.js'; + +type ToolState = { + timelineId: string; + name: string; + args: any; + title: string; + summary: string; +}; + +export type EventRenderState = { + thinkingId?: string; + thinkingStartedAt?: number; + thinkingText: string; + thinkingLines: string[]; + assistantId?: string; + assistantText: string; + tools: Map; +}; + +export function createEventRenderState(): EventRenderState { + return { thinkingText: '', thinkingLines: [], assistantText: '', tools: new Map() }; +} + +export function reduceTaskEvent(items: TimelineItem[], state: EventRenderState, event: { type: string; data: any }): TimelineItem[] { + const data = event.data || {}; + switch (event.type) { + case 'thinking_start': { + const timelineId = newId(); + state.thinkingId = timelineId; + state.thinkingStartedAt = Date.now(); + state.thinkingText = ''; + state.thinkingLines = []; + return [...items, { id: timelineId, kind: 'thinking', title: '思考中', status: 'running', lines: [] }]; + } + case 'thinking_chunk': { + const content = String(data.content || ''); + state.thinkingText += content; + const lines = state.thinkingText.split(/\r?\n/).filter((line) => line.length > 0); + state.thinkingLines = lines; + if (!state.thinkingId) { + state.thinkingId = newId(); + state.thinkingStartedAt = Date.now(); + state.thinkingText = content; + items = [...items, { id: state.thinkingId, kind: 'thinking', title: '思考中', status: 'running', lines: [] }]; + } + return patchItem(items, state.thinkingId, { lines: lastLines(state.thinkingLines, 5) }); + } + case 'thinking_end': { + if (!state.thinkingId) return items; + const elapsed = state.thinkingStartedAt ? `${((Date.now() - state.thinkingStartedAt) / 1000).toFixed(1)}s` : ''; + const id = state.thinkingId; + state.thinkingId = undefined; + state.thinkingStartedAt = undefined; + state.thinkingText = ''; + state.thinkingLines = []; + return patchItem(items, id, { + title: `思考完成 ${elapsed}`.trim(), + status: 'success', + body: undefined, + summary: undefined, + lines: [], + }); + } + case 'text_start': { + const timelineId = newId(); + state.assistantId = timelineId; + state.assistantText = ''; + return [...items, { id: timelineId, kind: 'assistant', status: 'running', body: '' }]; + } + case 'text_chunk': { + const content = String(data.content || ''); + if (!state.assistantId) { + state.assistantId = newId(); + state.assistantText = ''; + items = [...items, { id: state.assistantId, kind: 'assistant', status: 'running', body: '' }]; + } + state.assistantText += content; + return patchItem(items, state.assistantId, { body: state.assistantText }); + } + case 'text_end': { + const full = String(data.full_content || state.assistantText || ''); + if (!state.assistantId) return full ? [...items, { id: newId(), kind: 'assistant', title: '完成', status: 'success', body: full }] : items; + const id = state.assistantId; + state.assistantId = undefined; + state.assistantText = ''; + return patchItem(items, id, { status: 'success', body: full }); + } + case 'tool_preparing': + case 'tool_start': { + const toolId = String(data.id || data.execution_id || data.preparing_id || newId()); + const name = String(data.name || 'tool'); + const args = data.arguments || {}; + const title = toolTitle(name, args, 'running'); + const summary = toolSummaryPreview(data, name, args, 'running'); + const existingKey = data.preparing_id ? String(data.preparing_id) : toolId; + const existing = state.tools.get(existingKey); + if (existing) { + state.tools.delete(existingKey); + state.tools.set(toolId, { ...existing, name, args, title, summary }); + return patchItem(items, existing.timelineId, { + title, + summary, + status: 'running', + body: toolInitialBody(name, args), + lines: undefined, + }); + } + const timelineId = newId(); + state.tools.set(toolId, { timelineId, name, args, title, summary }); + return [...items, { id: timelineId, kind: 'tool', title, summary, status: 'running', body: toolInitialBody(name, args) }]; + } + case 'tool_update_action': + case 'update_action': { + const toolId = String(data.id || data.execution_id || data.preparing_id || ''); + const tool = state.tools.get(toolId); + if (!tool) return items; + if (data.result !== undefined || data.status) { + const status = String(data.status || '').toLowerCase() === 'completed' ? 'success' : String(data.status || '').toLowerCase() === 'failed' ? 'failed' : 'running'; + return patchItem(items, tool.timelineId, { + title: toolTitle(tool.name, tool.args, status), + summary: toolSummaryPreview(data, tool.name, tool.args, status), + status, + body: toolResultBody(tool.name, tool.args, data.result), + lines: toolResultLines(tool.name, tool.args, data.result), + }); + } + return items; + } + case 'append_payload': + case 'modify_payload': { + return [...items, renderPayloadEvent(data)]; + } + case 'tool_approval_required': { + const approval = data.approval || data; + return [...items, { id: newId(), kind: 'tool', title: `需要审批:${approval.tool_name || '工具调用'}`, status: 'running', body: approval.preview?.summary || '' }]; + } + case 'tool_approval_resolved': { + return [...items, { id: newId(), kind: 'tool', title: `审批${data.decision === 'approved' ? '通过' : '拒绝'}`, status: data.decision === 'approved' ? 'success' : 'failed', body: data.reason || '' }]; + } + case 'task_complete': { + return items; + } + case 'task_stopped': { + return [...items, { id: newId(), kind: 'tool', title: '任务已停止', status: 'cancelled', body: data.message || '' }]; + } + case 'error': { + return [...items, { id: newId(), kind: 'tool', title: '任务失败', status: 'failed', body: data.message || data.error || '未知错误' }]; + } + case 'token_update': { + return items; + } + default: + return items; + } +} + +function toolTitle(name: string, args: any, status: string): string { + if (name === 'run_command') return `运行 ${args.command || ''}`.trim(); + if (name === 'run_python') return '运行 Python'; + if (name === 'write_file') return `Write ${args.file_path || args.path || ''} ${countWrite(args.content || '')}`.trim(); + if (name === 'edit_file') return `Edited ${args.file_path || args.path || ''} ${countEdit(args)}`.trim(); + if (name === 'read_file' || name === 'read_skill') return `阅读 ${args.path || args.file_path || ''}`.trim(); + if (name === 'terminal_session') return `${status === 'running' ? '创建' : '终端'} ${args.session_name || args.name || ''}`.trim(); + if (name === 'terminal_input') return `终端输入 ${args.command || args.input || ''}`.trim(); + return name; +} + +function toolSummaryPreview(data: any, name: string, args: any, status: string): string { + const intent = data?.intent_rendered || data?.intent_full || data?.intent || args?.intent_rendered || args?.intent_full || args?.intent || ''; + if (intent) return truncateSummaryPreview(firstLine(String(intent))); + if (status === 'success') return '工具执行完成'; + if (status === 'failed') return `${name || '工具'} 执行失败`; + if (data?.status === 'preparing') return `准备调用 ${name || '工具'}...`; + return `正在调用 ${name || '工具'}...`; +} + +function firstLine(text: string): string { + return text.split(/\r?\n/)[0] || ''; +} + +function truncateSummaryPreview(text: string): string { + const value = String(text || ''); + return value.length > 25 ? `${value.slice(0, 25)}...` : value; +} + +function toolInitialBody(name: string, args: any): string { + if (name === 'write_file' || name === 'edit_file') return ''; + return ''; +} + +function toolResultBody(name: string, args: any, result: any): string { + if (typeof result === 'string') return summarizeText(result); + if (!result) return ''; + if (name === 'run_command' || name === 'terminal_input' || name === 'run_python') { + const exitCode = result.exit_code ?? result.returncode; + const text = String(result?.output || result?.stdout || result?.stderr || '').trim(); + if (exitCode !== undefined && exitCode !== 0) return text ? `exit code ${exitCode}` : `exit code ${exitCode}\n(no output)`; + return text ? '' : '(no output)'; + } + if (name === 'read_file' || name === 'read_skill') { + const content = result.content || result.text || ''; + const count = String(content).split(/\r?\n/).filter(Boolean).length; + const type = result.type || 'read'; + if (type === 'extract') return `提取了 ${count} 行`; + return `读取了 ${count} 行`; + } + if (name === 'write_file') return ''; + if (name === 'edit_file') return ''; + if (result.error) return String(result.error); + return result.success === false ? '失败' : '成功'; +} + +function toolResultLines(name: string, args: any, result: any): string[] | undefined { + if (name === 'write_file') { + return String(args.content || '').split('\n').map((line, idx) => `${idx + 1} +${line}`); + } + if (name === 'edit_file') { + return editLines(args); + } + const text = typeof result === 'string' ? result : String(result?.output || result?.stdout || result?.content || result?.text || ''); + if (!text || name === 'read_file' || name === 'read_skill') return undefined; + return summarizeLines(text); +} + +function renderPayloadEvent(data: any): TimelineItem { + const path = data.path || data.file_path || ''; + const lines: string[] = []; + if (Array.isArray(data.old_lines)) lines.push(...data.old_lines.map((line: string, idx: number) => `${idx + 1} -${line}`)); + if (Array.isArray(data.new_lines)) lines.push(...data.new_lines.map((line: string, idx: number) => `${idx + 1} +${line}`)); + return { id: newId(), kind: 'tool', title: `Edited ${path}`, status: 'success', lines }; +} + +function countWrite(content: string): string { + const added = content ? content.split('\n').length : 0; + return `(+${added} -0)`; +} + +function countEdit(args: any): string { + const lines = editLines(args); + const removed = lines.filter((line) => line.includes(' -')).length; + const added = lines.filter((line) => line.includes(' +')).length; + return `(+${added} -${removed})`; +} + +function editLines(args: any): string[] { + const replacements = Array.isArray(args.replacements) ? args.replacements : []; + const result: string[] = []; + const emitPair = (oldText: string, newText: string) => { + const oldLines = oldText.replace(/\\n/g, '\n').split('\n'); + const newLines = newText.replace(/\\n/g, '\n').split('\n'); + oldLines.filter((line) => line.length > 0).forEach((line, idx) => result.push(`${idx + 1} -${line}`)); + newLines.filter((line) => line.length > 0).forEach((line, idx) => result.push(`${idx + 1} +${line}`)); + }; + if (replacements.length) { + for (const rep of replacements) emitPair(String(rep.old_text || rep.old || ''), String(rep.new_text || rep.new || '')); + } else { + emitPair(String(args.old_string || ''), String(args.new_string || '')); + } + return result; +} + +function summarizeText(text: string): string { + const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0); + return summarizeLines(lines.join('\n')).join('\n'); +} + +function summarizeLines(text: string): string[] { + const lines = text.split(/\r?\n/).filter((line) => line.length > 0); + if (lines.length <= 5) return lines; + return [lines[0]!, lines[1]!, `… +${lines.length - 4} lines`, lines[lines.length - 2]!, lines[lines.length - 1]!]; +} + +function patchItem(items: TimelineItem[], id: string, patch: Partial): TimelineItem[] { + return items.map((item) => item.id === id ? { ...item, ...patch } : item); +} + +function newId(): string { + return globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`; +} diff --git a/cli/src/format.ts b/cli/src/format.ts new file mode 100644 index 0000000..04b6262 --- /dev/null +++ b/cli/src/format.ts @@ -0,0 +1,29 @@ +import { homedir } from 'node:os'; + +export function shortPath(path: string): string { + const home = homedir(); + return path.startsWith(home) ? `~${path.slice(home.length)}` : path; +} + +export function formatContext(used: number, limit: number): string { + if (!limit || limit <= 0) return formatTokens(used); + const left = Math.max(0, limit - used); + const pct = limit > 0 ? Math.round((left / limit) * 100) : 0; + return `${pct}% left ${formatTokens(used)} / ${formatTokens(limit)}`; +} + +export function formatTokens(value: number): string { + if (value >= 1000) { + const rounded = Math.round((value / 1000) * 10) / 10; + return `${rounded}K`; + } + return String(value); +} + +export function nowSessionId(): string { + return globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`; +} + +export function lastLines(lines: string[], limit = 5): string[] { + return lines.slice(Math.max(0, lines.length - limit)); +} diff --git a/cli/src/main.tsx b/cli/src/main.tsx new file mode 100644 index 0000000..8916fb7 --- /dev/null +++ b/cli/src/main.tsx @@ -0,0 +1,7 @@ +#!/usr/bin/env node +import React from 'react'; +import { render } from 'ink'; +import App from './App.js'; + +process.stdout.write('\x1b[2J\x1b[3J\x1b[H'); +render(); diff --git a/cli/src/types.ts b/cli/src/types.ts new file mode 100644 index 0000000..d7a1104 --- /dev/null +++ b/cli/src/types.ts @@ -0,0 +1,65 @@ +export type PermissionMode = 'readonly' | 'approval' | 'auto_approval' | 'unrestricted'; +export type ExecutionMode = 'sandbox' | 'direct'; +export type RunMode = 'fast' | 'thinking' | 'deep'; + +export type Workspace = { + workspace_id: string; + label: string; + path: string; +}; + +export type WorkspaceCatalog = { + default_workspace_id: string; + workspaces: Workspace[]; +}; + +export type CliStatus = { + model: string; + runMode: RunMode; + directory: string; + workspace: Workspace; + permissionMode: PermissionMode; + executionMode: ExecutionMode; + sessionId: string; + contextUsed: number; + contextLimit: number; + versioningEnabled: boolean; + backgroundAgents: number; + backgroundCommands: number; + activeSkill?: SkillDefinition; +}; + +export type SkillDefinition = { + id: string; + label: string; + description: string; +}; + +export type ModelDefinition = { + model_key: string; + name: string; + description?: string; + supports_thinking?: boolean; + fast_only?: boolean; + deep_only?: boolean; + context_window?: number; +}; + +export type TimelineItem = { + id: string; + kind: 'system' | 'user' | 'assistant' | 'tool' | 'thinking' | 'sub_agent'; + title?: string; + summary?: string; + body?: string; + status?: 'running' | 'success' | 'failed' | 'cancelled'; + lines?: string[]; +}; + +export type SlashCommandKind = 'immediate' | 'picker' | 'panel' | 'form'; + +export type SlashCommand = { + name: string; + description: string; + aliases?: string[]; + kind: SlashCommandKind; +}; diff --git a/cli/src/workspaces.ts b/cli/src/workspaces.ts new file mode 100644 index 0000000..9cd9135 --- /dev/null +++ b/cli/src/workspaces.ts @@ -0,0 +1,97 @@ +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { dirname, resolve, basename } from 'node:path'; +import type { Workspace, WorkspaceCatalog } from './types.js'; + +export const repoRoot = process.env.AGENTS_REPO_ROOT || resolve(new URL('../..', import.meta.url).pathname); +const configPath = resolve(repoRoot, 'config/host_workspaces.json'); + +function defaultCatalog(): WorkspaceCatalog { + const fallbackPath = resolve(repoRoot, 'project'); + return { + default_workspace_id: 'default', + workspaces: [ + { + workspace_id: 'default', + label: '默认工作区', + path: fallbackPath, + }, + ], + }; +} + +function normalizePath(path: string): string { + return resolve(path); +} + +function slugify(value: string): string { + const slug = value + .trim() + .toLowerCase() + .replace(/[^a-z0-9._-]+/g, '-') + .replace(/-{2,}/g, '-') + .replace(/^[-._]+|[-._]+$/g, ''); + return slug || 'workspace'; +} + +export function getHostWorkspacesConfigPath(): string { + return configPath; +} + +export function loadWorkspaceCatalog(): WorkspaceCatalog { + try { + const raw = JSON.parse(readFileSync(configPath, 'utf8')) as Partial; + const workspaces = Array.isArray(raw.workspaces) ? raw.workspaces : []; + const normalized = workspaces + .filter((item): item is Workspace => Boolean(item && item.workspace_id && item.path)) + .map((item) => ({ + workspace_id: String(item.workspace_id), + label: String(item.label || item.workspace_id), + path: normalizePath(String(item.path)), + })); + if (!normalized.length) return defaultCatalog(); + const defaultId = raw.default_workspace_id && normalized.some((ws) => ws.workspace_id === raw.default_workspace_id) + ? String(raw.default_workspace_id) + : normalized[0]!.workspace_id; + return { default_workspace_id: defaultId, workspaces: normalized }; + } catch { + return defaultCatalog(); + } +} + +export function saveWorkspaceCatalog(catalog: WorkspaceCatalog): void { + mkdirSync(dirname(configPath), { recursive: true }); + writeFileSync(configPath, JSON.stringify(catalog, null, 2), 'utf8'); +} + +export function findWorkspaceByPath(catalog: WorkspaceCatalog, cwd: string): Workspace | undefined { + const target = normalizePath(cwd); + return catalog.workspaces.find((ws) => normalizePath(ws.path) === target); +} + +export function createWorkspaceForPath(catalog: WorkspaceCatalog, cwd: string): { catalog: WorkspaceCatalog; workspace: Workspace; created: boolean } { + const path = normalizePath(cwd); + const existing = findWorkspaceByPath(catalog, path); + if (existing) return { catalog, workspace: existing, created: false }; + + const baseLabel = basename(path) || 'workspace'; + const baseId = slugify(baseLabel); + const used = new Set(catalog.workspaces.map((ws) => ws.workspace_id)); + let workspaceId = baseId; + let suffix = 2; + while (used.has(workspaceId)) { + workspaceId = `${baseId}-${suffix}`; + suffix += 1; + } + + const workspace: Workspace = { + workspace_id: workspaceId, + label: baseLabel, + path, + }; + const nextCatalog: WorkspaceCatalog = { + default_workspace_id: catalog.default_workspace_id || workspaceId, + workspaces: [...catalog.workspaces, workspace], + }; + saveWorkspaceCatalog(nextCatalog); + return { catalog: nextCatalog, workspace, created: true }; +} diff --git a/cli/tsconfig.json b/cli/tsconfig.json new file mode 100644 index 0000000..c08ab58 --- /dev/null +++ b/cli/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "types": ["node", "react"] + }, + "include": ["src/**/*.ts", "src/**/*.tsx"] +} diff --git a/docs/cli_slash_commands_spec.md b/docs/cli_slash_commands_spec.md new file mode 100644 index 0000000..541fbb8 --- /dev/null +++ b/docs/cli_slash_commands_spec.md @@ -0,0 +1,764 @@ +# CLI Slash 命令设计规范(草案) + +> 目标:定义 CLI 中 `/` 命令的命名、交互方式、显示方式与最小功能集。 +> 关联文档:`docs/cli_ui_display_spec.md` + +## 1. 总体原则 + +1. **所有主动操作都从 `/` 命令进入**:避免额外快捷键过多导致记忆负担。 +2. **命令面板统一交互**:输入过滤、上下选择、Enter 确认。 +3. **没有必要的命令不做**:后台内部操作、配置文件管理、文件浏览等不暴露成命令。 +4. **审批不做独立命令**:有审批时直接占用输入栏显示审批交互。 +5. **命令只负责 UI 入口**:真正执行仍通过后端 API / CLI 适配层完成。 + +--- + +## 2. 启动命令与工作区初始化 + +### 2.1 启动命令 + +正式启动命令建议为: + +```bash +agents +``` + +开发期或兼容别名可保留: + +```bash +agents-cli +``` + +命名理由: + +- 和项目名一致 +- 短、好记 +- 类似 `codex` / `claude` 的产品级 CLI 命令 +- 比 `agent` 更不容易和泛称混淆 + +### 2.2 默认工作区目录 + +用户在任意目录执行: + +```bash +cd /Users/jojo/Desktop/test +agents +``` + +CLI 默认使用 `process.cwd()` 作为本次启动的工作区目录。 + +### 2.3 当前目录已是工作区 + +如果当前目录已经存在于工作区列表中: + +1. 自动选择该工作区 +2. 进入 CLI 主界面 + +### 2.4 当前目录不是工作区 + +如果当前目录不在工作区列表中,启动时先显示确认提示: + +```text +当前目录尚未添加为工作区: + + /Users/jojo/Desktop/test + +Agent 将在此目录中读取文件、执行命令和修改内容。 +是否将此目录添加为工作区? + +› 1. 确认,添加并继续 + 2. 拒绝,退出 +``` + +选择 `确认`: + +```text +• 已添加工作区 test + └ /Users/jojo/Desktop/test +``` + +然后进入 CLI 主界面。 + +选择 `拒绝`: + +```text +已取消,退出 Agents CLI。 +``` + +并退出程序。 + +### 2.5 工作区匹配规则 + +v1 只做精确路径匹配: + +- 当前目录路径与已有工作区路径完全一致:视为已有工作区 +- 当前目录是已有工作区的子目录:仍视为新目录,需要确认是否添加 + +这样可以保证行为明确: + +> 在哪个目录启动,Agent 就在哪个目录工作。 + +### 2.6 自动工作区名称 + +自动创建工作区时: + +- 默认名称使用目录 basename +- 如 `/Users/jojo/Desktop/test` → `test` +- 如果名称重复,可自动追加序号,例如 `test-2` + +--- + +## 3. 命令面板交互 + +### 3.1 触发 + +用户在输入栏输入 `/` 后打开命令面板。 + +```text +› / +``` + +输入更多字符后实时过滤: + +```text +› /a + + /compact 压缩当前对话上下文 +› /path 管理路径授权 + /permission 切换权限模式 +``` + +### 3.2 选择 + +- `↑` / `↓` 切换当前选中项 +- 当前选中项使用高亮颜色 +- `Enter` 执行当前命令 +- `Esc` 关闭命令面板 + +### 3.3 展示格式 + +```text +› /mo + +› /model 切换模型与思考模式 +``` + +格式规则: + +- 左侧显示命令名 +- 右侧显示简短说明 +- 说明必须短,不超过一行 + +--- + +## 4. 最终命令清单 + +| 命令 | 说明 | 类型 | +|---|---|---| +| `/new` | 新建对话 | 立即执行 / 可确认 | +| `/resume` | 查看对话列表并切换对话 | 选择器 | +| `/model` | 切换模型与思考模式 | 选择器 | +| `/versioning` | 管理版本控制 | 选择器 | +| `/permission` | 切换权限模式 | 选择器 | +| `/execution` | 切换执行环境 | 选择器 | +| `/workspace` | 选择或切换工作区 | 选择器 | +| `/compact` | 压缩当前对话上下文 | 立即执行 / 可确认 | +| `/tools` | 启用或禁用工具 | 选择器 | +| `/path` | 管理路径授权 | 选择器 / 表单 | +| `/skill` | 选择本轮要使用的 skill | 选择器 | +| `/status` | 查看当前状态 | 信息面板 | +| `/help` | 查看命令帮助 | 信息面板 | +| `/clear` | 清空当前 CLI 显示 | 立即执行 | +| `/exit` | 退出 CLI | 立即执行 / 可确认 | + +--- + +## 5. 不做的命令 + +以下命令不做: + +| 命令 | 不做原因 | +|---|---| +| `/approval` | 有待审批内容时直接在输入栏显示审批交互,无需手动进入 | +| `/memory` | 属于后端/Agent 内部操作,不作为 CLI 常用入口 | +| `/files` | CLI 不做文件浏览器,文件操作由 Agent 工具执行 | +| `/tasks` | 后台任务数量放在底部状态栏;详细逻辑先不暴露 | +| `/focus` | 暂不做额外显示模式 | + +--- + +## 6. 各命令详细设计 + +### 6.1 `/new` + +说明: + +```text +/new 新建对话 +``` + +行为: + +- 创建一个空白对话 +- 清空当前 CLI 消息区 +- 切换到底部输入待命态 + +如果当前有未完成任务: + +```text +当前任务仍在运行,是否新建对话? + +› 1. 新建并停止当前任务 + 2. 等待当前任务完成 + 3. 取消 +``` + +--- + +### 6.2 `/resume` + +说明: + +```text +/resume 查看对话列表并切换对话 +``` + +行为: + +- 打开历史对话选择器 +- 显示最近对话 +- 选择后切换当前会话 + +显示示例: + +```text +Resume conversation + +› 今天 14:32 修复 CLI 设计文档 + 今天 13:10 调研 Claude Code CLI + 昨天 22:45 Android 发布联动 +``` + +字段建议: + +- 时间 +- 对话标题 / 首条用户消息摘要 +- 当前工作区(可选) +- 模型(可选) + +--- + +### 6.3 `/model` + +说明: + +```text +/model 切换模型与思考模式 +``` + +行为: + +- 打开模型选择器 +- 支持模型和运行模式一起切换 + +显示示例: + +```text +Model + +› 1. kimi-k2.5 当前模型 + 2. gpt-5.5 高质量编码 + 3. gpt-5.4-mini 快速低成本 + +Run mode + +› 1. thinking 思考模式 + 2. fast 快速模式 + 3. deep 深度思考 +``` + +规则: + +- 当前选中项标记为 `current` +- 被管理员禁用的模型显示为 disabled,不可选 +- fast-only / deep-only 模型需要显示提示 + +--- + +### 6.4 `/versioning` + +说明: + +```text +/versioning 管理版本控制 +``` + +为什么用 `versioning`: + +- 比 `/version` 更明确是“版本控制” +- 避免和模型版本混淆 + +行为: + +- 打开版本控制设置 / 恢复面板 + +显示示例: + +```text +Versioning + +› 1. 开启版本控制 + 2. 关闭版本控制 + 3. 查看版本记录 + 4. 恢复到选定版本 +``` + +字段建议: + +- 当前是否开启 +- 当前跟踪模式 +- 最近版本点 +- 是否与当前工作区匹配 + +--- + +### 6.5 `/permission` + +说明: + +```text +/permission 切换权限模式 +``` + +行为: + +- 打开权限模式选择器 + +显示示例: + +```text +Permission Mode + +› 1. Readonly 只读,不允许写入 + 2. Approval 需要审批后执行写入/敏感操作 + 3. Auto Approval 由自动审批智能体审核 + 4. Unrestricted 不做权限拦截 +``` + +规则: + +- 当前权限模式显示 `current` +- 高风险项需要二次确认 + +--- + +### 6.6 `/execution` + +说明: + +```text +/execution 切换执行环境 +``` + +为什么用 `execution`: + +- 覆盖 `sandbox` 与 `direct` +- 比 `/env` 更明确 +- 比 `/runtime` 更贴近用户动作 + +行为: + +- 打开执行环境选择器 + +显示示例: + +```text +Execution Environment + +› 1. Sandbox 使用 OS 沙箱执行命令 + 2. Direct 在宿主机直接执行命令 +``` + +规则: + +- `Direct` 需要风险提示 +- 如果 direct 有 TTL,显示剩余时间 + +--- + +### 6.7 `/workspace` + +说明: + +```text +/workspace 选择或切换工作区 +``` + +行为: + +- 打开工作区列表 +- 支持选择已有工作区 +- 支持新建工作区 + +显示示例: + +```text +Workspace + +› 1. agents /Users/jojo/Desktop/agents/正在修复中/agents current + 2. test /Users/jojo/Desktop/test + 3. 新建工作区... +``` + +新建工作区时,输入栏切换成表单: + +```text +Workspace path +› /Users/jojo/Desktop/new-project +``` + +可选第二步: + +```text +Workspace name +› new-project +``` + +--- + +### 6.8 `/compact` + +说明: + +```text +/compact 压缩当前对话上下文 +``` + +行为: + +- 触发当前对话压缩 +- 压缩前可确认 + +显示示例: + +```text +确认压缩当前对话上下文? + +› 1. 压缩 + 2. 取消 +``` + +完成后: + +```text +• 对话已压缩 + └ 当前上下文 24k/200k +``` + +--- + +### 6.9 `/tools` + +说明: + +```text +/tools 启用或禁用工具 +``` + +行为: + +- 打开工具分类选择器 +- 支持启用/禁用工具分类 + +显示示例: + +```text +Tools + +› [x] terminal 终端命令 + [x] file 文件读写 + [ ] web 网络访问 + [x] sub_agent 子智能体 +``` + +规则: + +- 空格切换选中 +- Enter 保存 +- Esc 取消 + +--- + +### 6.10 `/path` + +说明: + +```text +/path 管理路径授权 +``` + +行为: + +- 查看已授权路径 +- 添加可读写路径 +- 添加只读路径 +- 删除授权路径 + +显示示例: + +```text +Authorized Paths + +› 1. 添加可读写路径 + 2. 添加只读路径 + 3. 删除授权路径 + +Current + rw /Users/jojo/Desktop/agents + ro /Users/jojo/Downloads +``` + +添加路径表单: + +```text +Path +› /Users/jojo/Desktop/project +``` + +--- + +### 6.11 `/skill` + +说明: + +```text +/skill 选择本轮要使用的 skill +``` + +这是 CLI 新增能力,Web 端暂无对应入口。 + +行为: + +- 打开 skill 列表 +- 用户选择一个 skill +- 下一次发送用户消息时,CLI 在用户消息下方附加一条 user 消息: + +```text +先阅读 docs skill +``` + +### 显示示例 + +```text +Skill + +› docs 阅读文档相关技能 + frontend 前端开发相关技能 + backend 后端开发相关技能 +``` + +选择后输入栏状态栏显示: + +```text +skill: docs +``` + +发送消息时: + +```text +› 帮我整理一下 CLI 文档 + + 先阅读 docs skill +``` + +规则: + +- v1 先做单选 +- 选择后只作用于下一条用户消息 +- 消息发送后清空当前 skill +- 后续可扩展为多选 + +--- + +### 6.12 `/status` + +说明: + +```text +/status 查看当前状态 +``` + +行为: + +- 打开状态信息面板 +- 类似 Codex CLI 的状态卡 + +显示示例: + +```text +╭────────────────────────────────────────────────────────────────────╮ +│ >_ Agents CLI │ +│ │ +│ Model: kimi-k2.5 │ +│ Directory: ~/Desktop/agents/正在修复中/agents │ +│ Workspace: agents │ +│ Permissions: auto_approval │ +│ Execution: sandbox │ +│ Session: 019e253e-7310-7330-ac02-859338456535 │ +│ Context window: 99% left (14.6K used / 258K) │ +╰────────────────────────────────────────────────────────────────────╯ +``` + +#### 字段说明 + +| 字段 | 说明 | +|---|---| +| `Model` | 当前模型 | +| `Directory` | 当前工作目录 | +| `Workspace` | 当前工作区名称 | +| `Permissions` | 当前权限模式 | +| `Execution` | 当前执行环境 | +| `Session` | 当前会话 ID | +| `Context window` | 上下文窗口使用情况 | + +--- + +### 6.13 `/help` + +说明: + +```text +/help 查看命令帮助 +``` + +显示命令列表: + +```text +Commands + + /new 新建对话 + /resume 查看对话列表并切换对话 + /model 切换模型与思考模式 + /versioning 管理版本控制 + /permission 切换权限模式 + /execution 切换执行环境 + /workspace 选择或切换工作区 + /compact 压缩当前对话上下文 + /tools 启用或禁用工具 + /path 管理路径授权 + /skill 选择本轮要使用的 skill + /status 查看当前状态 + /clear 清空当前 CLI 显示 + /exit 退出 CLI +``` + +--- + +### 6.14 `/clear` + +说明: + +```text +/clear 清空当前 CLI 显示 +``` + +行为: + +- 只清空 CLI 当前显示区 +- 不删除对话历史 +- 不影响后端会话 + +--- + +### 6.15 `/exit` + +说明: + +```text +/exit 退出 CLI +``` + +行为: + +- 如果当前无任务,直接退出 +- 如果有运行任务,显示确认: + +```text +当前任务仍在运行,是否退出? + +› 1. 退出并停止当前任务 + 2. 后台继续运行并退出 + 3. 取消 +``` + +--- + +## 7. 审批交互与命令系统的关系 + +审批不是 `/approval` 命令。 + +当有待审批操作时,输入栏临时切换为审批态: + +```text +需要批准:运行 rm -rf dist + +› 1. 允许一次 + 2. 切换到无限制 + 3. 拒绝 +``` + +审批完成后恢复普通输入态。 + +--- + +## 8. 底部状态栏与 `/status` 的关系 + +底部状态栏只显示短信息: + +```text +5 后台智能体 3 后台指令 版本控制: 开 自动审批 沙箱 24k/200k +``` + +`/status` 显示完整信息卡。 + +--- + +## 9. 实现建议 + +### 9.1 命令定义结构 + +```ts +type SlashCommand = { + name: string; + aliases?: string[]; + description: string; + kind: 'immediate' | 'picker' | 'panel' | 'form'; + run: () => void | Promise; +}; +``` + +### 9.2 命令状态 + +```ts +type CliInputMode = + | 'composer' + | 'slash_menu' + | 'picker' + | 'approval' + | 'form' + | 'status_panel' + | 'help_panel'; +``` + +### 9.3 命令过滤 + +- 前缀匹配优先 +- 模糊匹配其次 +- 当前项默认选第一条 + +--- + +## 10. 一句话总结 + +CLI 的 `/` 命令系统目标是: + +> 用一个统一命令入口覆盖 Web 对话页的主要直接操作,同时保持终端交互简单、可键盘驱动、低噪声。 diff --git a/docs/cli_ui_display_spec.md b/docs/cli_ui_display_spec.md new file mode 100644 index 0000000..a78d970 --- /dev/null +++ b/docs/cli_ui_display_spec.md @@ -0,0 +1,523 @@ +# CLI UI 显示规范(草案) + +> 目标:把 Web 端已有的信息表达方式,压缩成一个低噪声、可扫描、适合终端的 CLI 交互层。 +> 适用范围:CLI 前端展示层,不定义 Agent Runtime 本身。 + +## 1. 设计原则 + +1. **事件流优先**:CLI 只消费结构化事件,不直接消费原始 API 响应。 +2. **极简优先**:正文少、状态清楚、动作明确。 +3. **固定符号**:统一用少量状态符号表达进度与结果。 +4. **完整展示变更**:文件编辑类内容不折叠,直接完整显示。 +5. **思考可见但不冗长**:只保留滚动中的短窗口,不保留完整历史。 +6. **交互独立**:审批、选择、Slash 命令都走独立输入态。 + +--- + +## 2. 总体布局 + +```text +[历史事件区] +[当前思考 / 工具 / 子智能体输出] +[最终结果] +── +[输入栏] +[状态栏] +``` + +### 底部状态栏建议 + +```text +5 后台智能体 3 后台指令 版本控制: 开 自动审批 沙箱 24k/200k +``` + +可扩展字段: + +- 模型名 +- 运行模式(thinking / fast 先不做切换也可保留显示位) +- Git 开关 +- 自动审批状态 +- 沙箱/直连状态 +- 上下文剩余量 + +--- + +## 3. 状态符号 + +| 状态 | 符号 | 颜色 | 说明 | +|---|---|---|---| +| 运行中 | `◦` / `•` 交替 | 黄/灰 | 闪烁 | +| 完成 | `•` | 绿 | 静态 | +| 失败 | `•` | 红 | 静态 | +| 用户输入 | `›` | 白/青 | 静态 | +| 审批中 | `◦` | 黄 | 慢闪 | +| 已取消 | `•` | 灰 | 静态 | + +--- + +## 4. 思考块显示 + +思考块只保留一种样式:**5 行滚动窗口**。 + +### 4.1 运行中 + +```text +◦ 思考中 + └ 第一行 + 第二行 + 第三行 + 第四行 + 第五行 +``` + +当加入第 6 行时,自动滚动: + +```text +◦ 思考中 + └ 第二行 + 第三行 + 第四行 + 第五行 + 第六行 +``` + +### 4.2 完成后 + +```text +• 思考完成 4.2s +``` + +不做折叠展开,不保留完整思考历史。 + +### 4.3 规则 + +- 只显示最新 5 行 +- 只显示当前思考内容 +- 完成后只留完成状态 +- 不显示“思考全文” + +--- + +## 5. 子智能体前台显示 + +子智能体前台展示方式与思考块一致,也是 5 行滚动窗口。 + +```text +◦ 子智能体 review-agent... + └ 运行 ls -la && pwd + 搜索 python 新版本 + 第三行 + 第四行 + 第五行 +``` + +完成后: + +```text +• 子智能体完成 8.1s +``` + +### 规则 + +- 子智能体只显示前台进度,不显示全部历史 +- 只保留最新 5 行 +- 完成态只显示耗时和结果摘要 + +--- + +## 6. 工具块通用格式 + +统一使用: + +```text +• 动作摘要 + └ 结果摘要 +``` + +### 6.1 运行中 + +```text +◦ 运行 npm run build + └ 正在执行... +``` + +### 6.2 完成 + +```text +• 运行 npm run build + └ 成功,用时 8.2s +``` + +### 6.3 失败 + +```text +• 运行 npm run build + └ 失败,exit code 2 +``` + +--- + +## 7. 工具显示规范 + +下面定义 `└` 后面的内容包含哪些字段、如何显示。 + +### 7.1 运行命令类 + +#### 适用工具 + +- `run_command` +- `terminal_input` +- `terminal_session` +- `terminal_snapshot` + +#### 显示字段 + +- `command` / `input` +- `timeout` +- `exit_code` +- `stdout` / `output` +- `stderr`(若可分离) + +#### 显示规则 + +```text +• 运行 printf '' > test.txt && ls -l test.txt + └ -rw-r--r-- 1 jojo staff 0 May 14 14:48 test.txt +``` + +长输出: + +```text +• 运行 ls -la && pwd + └ 第 1 行内容 + 第 2 行内容 + … +6 lines + 第 9 行内容 + 第 10 行内容 +``` + +#### 约束 + +- 第一行优先显示结果中最有信息量的一行 +- 长文本可局部截断,但不要丢掉关键错误 +- 如果有 exit code,必须显示 + +--- + +### 7.2 文件编辑类 + +#### 适用工具 + +- `write_file` +- `edit_file` + +#### 原则 + +**必须完整显示,不折叠。** + +因为: + +- 这是文件内容本身,不是工具摘要 +- 单次替换通常不会太大 +- 终端里保留完整 + / - 最有价值 + +#### 7.2.1 Write + +显示字段: + +- `path` +- `mode`(append / overwrite) +- `status` +- `content` + +显示模板: + +```text +• Write test.txt (+3 -0) + 1 +这是第一行测试内容。 + 2 +这是第二行测试内容。 + 3 +这是第三行测试内容。 +``` + +#### 7.2.2 Edit + +显示字段: + +- `path` +- `replace_all` +- `found` +- `replacements` +- 每个 replacement 的 `old` / `new` + +显示模板: + +```text +• Edited main.py (+2 -2) + 14 -old line 1 + 15 -old line 2 + 14 +new line 1 + 15 +new line 2 +``` + +#### 7.2.3 规则 + +- 不折叠 +- 不显示上下文行 +- 只显示参数里已有的变更信息 +- 若存在多段替换,按替换顺序逐段输出 + +--- + +### 7.3 阅读类 + +#### 适用工具 + +- `read_file` +- `read_skill` + +#### 显示字段 + +- `path` +- `status` +- `size` +- `read_type` +- `content` +- `search` / `extract` 子模式信息 + +#### 显示模板 + +普通读取: + +```text +• 阅读 test.txt + └ 读取 3 行 +``` + +提取模式: + +```text +• 阅读 test.txt + └ 提取第 12-28 行 +``` + +搜索模式(如果未来恢复): + +```text +• 搜索 handle_task + └ 找到 12 处,位于 5 个文件 +``` + +#### 规则 + +- 正文尽量压缩成“读取了多少 / 提取了什么 / 找到了什么” +- 需要看全文时再走展开命令 + +--- + +### 7.4 搜索类 + +当前 CLI v1 **不做搜索工具**。 + +如果未来恢复,显示方式参考阅读类: + +```text +• 搜索 "xxx" + └ 找到 N 处,位于 M 个文件 +``` + +--- + +### 7.5 文件创建 / 删除 / 重命名 + +当前 CLI v1 **不做这些工具块**,可直接忽略。 + +--- + +### 7.6 子智能体 / 后台任务类 + +#### 显示字段 + +- `name` +- `status` +- `latest_progress_lines` +- `elapsed` + +#### 显示模板 + +```text +◦ 子智能体 review-agent... + └ 运行 ls -la && pwd + 搜索 python 新版本 + 第三行 + 第四行 + 第五行 +``` + +完成后: + +```text +• 子智能体完成 8.1s +``` + +#### 规则 + +- 只显示最新 5 行 +- 运行中用 `◦` +- 完成后用 `•` + +--- + +### 7.7 审批 / 交互类 + +#### 适用场景 + +- 权限审批 +- 需要用户确认的危险操作 +- 自定义选择 +- Slash 命令补全 + +#### 显示方式 + +使用**输入栏扩展 + 选项列表**,不是普通消息流。 + +示例: + +```text +┌────────────────────────────────────┐ +│ 需要批准:运行 rm -rf dist │ +│ 原因:删除构建目录 │ +│ │ +│ › 1. Allow once │ +│ 2. Always allow │ +│ 3. Deny │ +└────────────────────────────────────┘ +``` + +#### 规则 + +- 上下键切换 +- 当前行高亮 +- Enter 确认 +- Esc 或拒绝关闭 + +--- + +## 8. Slash 命令规范 + +所有操作都通过 `/` 输入。 + +### 8.1 触发方式 + +输入 `/` 或 `/a` 后,打开命令建议列表。 + +### 8.2 显示方式 + +```text +› /approve 简要说明 + /allow 简要说明 + /assist 简要说明 +``` + +### 8.3 规则 + +- 支持模糊过滤 +- 上下键选择 +- 当前项变色 +- Enter 执行 + +--- + +## 9. 最终输出区 + +任务完成后,使用固定分隔结构: + +```text +── + +• 已创建 test.txt 文件。 + + 1. 修改文件列表:test.txt + 2. 核心变更点:在 /Users/jojo/Desktop/test 下创建了一个空文本文件 + 3. 验证结果:已执行 ls -l test.txt,文件存在。 + +── +``` + +### 最终结果字段 + +建议统一输出: + +1. **修改文件列表** +2. **核心变更点** +3. **验证结果** + +如果有失败: + +- 失败原因 +- 已做的尝试 +- 下一步建议 + +--- + +## 10. 信息优先级 + +从高到低: + +1. 当前交互态(审批 / 命令选择) +2. 当前思考 / 当前子智能体进度 +3. 当前工具输出 +4. 最终结论 +5. 低优先级元信息 + +--- + +## 11. 参考的 Web 端表现 + +Web 端已经存在的对应参考: + +- `static/src/components/chat/actions/ThinkingAction.vue` + - 思考块 + - 运行中 / 完成两态 + - 可折叠 +- `static/src/components/chat/actions/ToolAction.vue` + - 工具块 + - `renderEnhancedToolResult()` + - 文件编辑 / 读取 / 命令 / 图片等不同分支 +- `static/src/components/chat/StackedBlocks.vue` + - 思考 + 工具的堆叠展示 +- `static/src/components/chat/MinimalBlocks.vue` + - 摘要组展示 + - 最终只保留当前步骤预览 +- `static/src/components/panels/ToolApprovalPanel.vue` + - 审批面板 + - 编辑 diff、写入内容、命令预览 + +CLI 的目标不是复制这些视觉细节,而是抽取其中最有信息价值的字段。 + +--- + +## 12. 推荐的 CLI 默认策略 + +### 默认开启 + +- 5 行思考滚动 +- 5 行子智能体滚动 +- 文件编辑完整展示 +- 命令输出摘要展示 +- Slash 命令补全 +- 审批选择弹层 + +### 默认关闭 + +- 完整思考历史 +- 文件创建 / 删除 / 重命名显示 +- 搜索工具 +- 折叠展开复杂交互 + +--- + +## 13. 一句话总结 + +CLI 的目标是: + +> 用最少的符号,表达最多的有效信息;用固定的状态和结构,让用户能一眼看懂 Agent 在做什么。 diff --git a/package.json b/package.json index c17df04..acd269d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,10 @@ "build": "tsc --noEmit && vite build", "lint": "eslint \"static/src/**/*.{ts,tsx,js,vue}\" --max-warnings=0", "format": "prettier --write \"static/src/**/*.{ts,tsx,js,vue,css}\"", - "preview": "vite preview" + "preview": "vite preview", + "cli": "npm --prefix cli run dev", + "cli:build": "npm --prefix cli run build", + "cli:typecheck": "npm --prefix cli run typecheck" }, "dependencies": { "@types/html2canvas": "^0.5.35", diff --git a/server/chat.py b/server/chat.py index 4bc0157..83abcf6 100644 --- a/server/chat.py +++ b/server/chat.py @@ -15,6 +15,7 @@ import secrets from config import THINKING_FAST_INTERVAL, MAX_UPLOAD_SIZE, OUTPUT_FORMATS from modules.personalization_manager import ( load_personalization_config, + resolve_context_compression_settings, save_personalization_config, THINKING_INTERVAL_MIN, THINKING_INTERVAL_MAX, @@ -28,6 +29,7 @@ from modules.upload_security import UploadSecurityError from modules.host_sandbox_policy import load_policy, save_policy from modules.user_manager import UserWorkspace from core.web_terminal import WebTerminal +from config.model_profiles import get_model_context_window from .auth_helpers import api_login_required, resolve_admin_policy, get_current_user_record, get_current_username from .context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response, ensure_conversation_loaded, get_or_create_usage_tracker @@ -244,11 +246,23 @@ def get_personalization_settings(terminal: WebTerminal, workspace: UserWorkspace ) data_out = dict(data) data_out["enabled_skills"] = enabled_skills + compression_settings = resolve_context_compression_settings(data_out) + try: + model_window = get_model_context_window(getattr(terminal, "model_key", None) or "kimi-k2.5") + except Exception: + model_window = None return jsonify({ "success": True, "data": data_out, "tool_categories": terminal.get_tool_settings_snapshot(), "skills_catalog": skills_catalog, + "context_compression_settings": { + **compression_settings, + "context_window_tokens": min( + int(compression_settings.get("deep_trigger_tokens") or 0), + int(model_window or compression_settings.get("deep_trigger_tokens") or 0), + ), + }, "thinking_interval_default": THINKING_FAST_INTERVAL, "thinking_interval_range": { "min": THINKING_INTERVAL_MIN, @@ -314,11 +328,23 @@ def update_personalization_settings(terminal: WebTerminal, workspace: UserWorksp debug_log(f"应用个性化偏好失败: {exc}") config_out = dict(config) config_out["enabled_skills"] = enabled_skills + compression_settings = resolve_context_compression_settings(config_out) + try: + model_window = get_model_context_window(getattr(terminal, "model_key", None) or "kimi-k2.5") + except Exception: + model_window = None return jsonify({ "success": True, "data": config_out, "tool_categories": terminal.get_tool_settings_snapshot(), "skills_catalog": skills_catalog, + "context_compression_settings": { + **compression_settings, + "context_window_tokens": min( + int(compression_settings.get("deep_trigger_tokens") or 0), + int(model_window or compression_settings.get("deep_trigger_tokens") or 0), + ), + }, "thinking_interval_default": THINKING_FAST_INTERVAL, "thinking_interval_range": { "min": THINKING_INTERVAL_MIN,