@24klynx/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/break-cache-B716oddK.mjs +71 -0
- package/dist/break-cache-B716oddK.mjs.map +1 -0
- package/dist/bughunter-DeAizlBM.mjs +32 -0
- package/dist/bughunter-DeAizlBM.mjs.map +1 -0
- package/dist/clear-C1dFE5aD.mjs +24 -0
- package/dist/clear-C1dFE5aD.mjs.map +1 -0
- package/dist/config-D-xVXTXi.mjs +2 -0
- package/dist/config-Des0z-k9.mjs +147 -0
- package/dist/config-Des0z-k9.mjs.map +1 -0
- package/dist/context-BmZ8VEan.mjs +128 -0
- package/dist/context-BmZ8VEan.mjs.map +1 -0
- package/dist/context-viz-2ZZaTL2C.mjs +61 -0
- package/dist/context-viz-2ZZaTL2C.mjs.map +1 -0
- package/dist/env-CeeZcoDI.mjs +55 -0
- package/dist/env-CeeZcoDI.mjs.map +1 -0
- package/dist/git-branch-Dn1CP6An.mjs +96 -0
- package/dist/git-branch-Dn1CP6An.mjs.map +1 -0
- package/dist/headless-launcher-I8NWyD6k.mjs +171 -0
- package/dist/headless-launcher-I8NWyD6k.mjs.map +1 -0
- package/dist/index.d.mts +970 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3243 -0
- package/dist/index.mjs.map +1 -0
- package/dist/memory-gnURjOnQ.mjs +199 -0
- package/dist/memory-gnURjOnQ.mjs.map +1 -0
- package/dist/privacy-B6Rm1Xck.mjs +114 -0
- package/dist/privacy-B6Rm1Xck.mjs.map +1 -0
- package/dist/process-lifecycle-Dg6n2QS-.mjs +784 -0
- package/dist/process-lifecycle-Dg6n2QS-.mjs.map +1 -0
- package/dist/sandbox-toggle-9akjTw3h.mjs +64 -0
- package/dist/sandbox-toggle-9akjTw3h.mjs.map +1 -0
- package/dist/stats-DjKezhTJ.mjs +73 -0
- package/dist/stats-DjKezhTJ.mjs.map +1 -0
- package/dist/status-B3Tw-Ef4.mjs +92 -0
- package/dist/status-B3Tw-Ef4.mjs.map +1 -0
- package/dist/upgrade-CREWRNeC.mjs +72 -0
- package/dist/upgrade-CREWRNeC.mjs.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-lifecycle-Dg6n2QS-.mjs","names":[],"sources":["../src/mcp-loader.ts","../src/bootstrap.ts","../src/startup.ts","../src/terminal-mode.ts","../src/process-lifecycle.ts"],"sourcesContent":["/**\n * MCP configuration loader — reads MCP server configurations\n * from Lynx settings files (3‑layer merge: global → project → local).\n *\n * Settings layers (later overrides earlier for same‑named servers):\n * 1. ~/.lynx/settings.json — global\n * 2. .lynx/settings.json — project (committed)\n * 3. .lynx/settings.local.json — project local (gitignored)\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { resolvePaths } from \"@lynx/core\";\nimport type { McpServerConfig } from \"@lynx/agent\";\nimport { join } from \"node:path\";\n\n// ── Types ────────────────────────────────────────────\n\ninterface SettingsFile {\n mcpServers?: McpServerConfig[];\n enableAllProjectMcpServers?: boolean;\n enabledMcpjsonServers?: string[];\n disabledMcpjsonServers?: string[];\n}\n\n/** Where we found the config for a server. */\nexport type McpConfigSource = \"global\" | \"project\" | \"project-local\";\n\nexport interface McpServerEntry {\n config: McpServerConfig;\n source: McpConfigSource;\n}\n\nexport interface McpLoadResult {\n /** Merged server configs (project overrides global for same name). */\n servers: McpServerConfig[];\n /** Per‑server metadata, keyed by server name. */\n entries: Map<string, McpServerEntry>;\n}\n\n// ── Helpers ──────────────────────────────────────────\n\n/**\n * Try to read and parse a JSON file. Returns undefined if the file\n * does not exist or is malformed.\n */\nfunction tryReadJson<T>(filePath: string): T | undefined {\n if (!existsSync(filePath)) return undefined;\n try {\n return JSON.parse(readFileSync(filePath, \"utf-8\")) as T;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Find the project root by walking up from cwd looking for `.lynx/`.\n * Returns undefined if no project root is found.\n */\nfunction findProjectRoot(startDir: string): string | undefined {\n let dir = startDir;\n for (let i = 0; i < 50; i++) {\n if (existsSync(join(dir, \".lynx\"))) return dir;\n const parent = join(dir, \"..\");\n if (parent === dir) return undefined;\n dir = parent;\n }\n return undefined;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Load MCP server configurations from all settings layers.\n *\n * Merge order: global → project → project‑local.\n * Same‑named servers in later layers override earlier ones entirely.\n */\nexport function loadMcpConfigs(workspaceDir?: string): McpLoadResult {\n const paths = resolvePaths();\n\n // 1. Global settings\n const globalSettings = tryReadJson<SettingsFile>(paths.settingsFile);\n\n // 2. Project settings\n const projectRoot = workspaceDir ? findProjectRoot(workspaceDir) : findProjectRoot(process.cwd());\n const projectSettings = projectRoot\n ? tryReadJson<SettingsFile>(join(projectRoot, \".lynx\", \"settings.json\"))\n : undefined;\n\n // 3. Project‑local settings\n const localSettings = projectRoot\n ? tryReadJson<SettingsFile>(join(projectRoot, \".lynx\", \"settings.local.json\"))\n : undefined;\n\n // Merge: global → project → local (later wins)\n const merged = new Map<string, McpServerEntry>();\n\n function addServers(servers: McpServerConfig[] | undefined, source: McpConfigSource): void {\n if (!servers) return;\n for (const server of servers) {\n merged.set(server.name, { config: server, source });\n }\n }\n\n addServers(globalSettings?.mcpServers, \"global\");\n addServers(projectSettings?.mcpServers, \"project\");\n addServers(localSettings?.mcpServers, \"project-local\");\n\n return {\n servers: Array.from(merged.values()).map((e) => e.config),\n entries: merged,\n };\n}\n\n/**\n * Convenience: load and validate MCP configs for bootstrap.\n *\n * Returns an array of valid McpServerConfig objects, filtering out\n * entries that have neither a `command` nor a `url`.\n */\nexport function loadAndValidateMcpConfigs(workspaceDir?: string): McpServerConfig[] {\n const result = loadMcpConfigs(workspaceDir);\n return result.servers.filter((s) => {\n const isValid = !!(s.command || s.url);\n if (!isValid) {\n // Logged at trace level — most callers don't need to see this\n }\n return isValid;\n });\n}\n","/**\n * Bootstrap — the single assembly point for all Lynx dependencies.\n *\n * Every service is created via factory functions and wired together\n * here. No DI container — dependencies are passed explicitly.\n *\n * Order: infrastructure → core services → engine → TUI bridge.\n */\n\nimport type { Database } from \"@lynx/core\";\nimport { openDatabase, migrate } from \"@lynx/core\";\nimport type { SessionManager } from \"@lynx/session\";\nimport { createSessionManager } from \"@lynx/session\";\nimport type { ToolRegistry, ToolHandler, ToolDescriptor } from \"@lynx/tools\";\nimport {\n createToolRegistry,\n registerBuiltinTools,\n createMemoryWriteHandler,\n memoryWriteDescriptor,\n injectTaskManager,\n mcpAuthDescriptor,\n createMcpAuthHandler,\n sendMessageDescriptor,\n createSendMessageHandler,\n} from \"@lynx/tools\";\nimport type { LlmProvider } from \"@lynx/llm\";\nimport type { ManifestRegistry, PluginLoader, HookRegistry } from \"@lynx/plugins\";\nimport {\n createManifestRegistry,\n createPluginLoader,\n createHookRegistry,\n PredictiveLoader,\n} from \"@lynx/plugins\";\nimport type {\n QueryEngine,\n AgentConfig,\n SkillRegistry,\n SkillDefinition,\n McpManager,\n McpServerConfig,\n} from \"@lynx/agent\";\nimport {\n createQueryEngine,\n createSkillRegistry,\n createSkillToolHandler,\n createMcpManager,\n createMemoryManager,\n createTaskManager,\n} from \"@lynx/agent\";\nimport { getBaseSystemPrompt } from \"@lynx/agent\";\nimport type { MemoryManager, TaskManager } from \"@lynx/agent\";\nimport type { RuleEngine } from \"@lynx/permissions\";\nimport {\n createRuleEngine,\n createRulesLoader,\n createDenialTracker,\n loadFromDisk,\n} from \"@lynx/permissions\";\nimport { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { randomUUID } from \"node:crypto\";\nimport { loadAndValidateMcpConfigs } from \"./mcp-loader.js\";\nimport { createChannelRegistry, createFeishuAdapter } from \"@lynx/channels\";\nimport type { ChannelRegistry, FeishuConfig } from \"@lynx/channels\";\n\n// ── Types ────────────────────────────────────────────\n\n/** Bridge between the agent engine's permission checks and the TUI's permission dialog. */\nexport interface PermissionBridge {\n /**\n * Called by the agent engine before executing a tool.\n * Returns true if the tool is allowed, false if denied.\n * May await user interaction via the TUI dialog.\n */\n requestPermission(\n toolName: string,\n safety: \"Safe\" | \"WorkspaceSafe\" | \"RequiresApproval\" | \"Dangerous\",\n description: string,\n ): Promise<boolean>;\n /**\n * Called by the TUI's onPermissionReply callback.\n * Resolves the pending permission Promise.\n */\n handleReply(requestId: string, approved: boolean): void;\n /**\n * Called by the TUI after mount to register the dialog trigger.\n * The handler pushes a permission view onto the TUI stack.\n */\n setTuiHandler(\n handler:\n | ((req: {\n requestId: string;\n toolName: string;\n description: string;\n safety: \"Safe\" | \"WorkspaceSafe\" | \"RequiresApproval\" | \"Dangerous\";\n }) => void)\n | null,\n ): void;\n}\n\nexport interface AppContext {\n db: Database;\n sessionMgr: SessionManager;\n toolRegistry: ToolRegistry;\n pluginRegistry: {\n manifestRegistry: ManifestRegistry;\n loader: PluginLoader;\n hooks: HookRegistry;\n };\n ruleEngine: RuleEngine;\n engine: QueryEngine;\n provider: LlmProvider;\n /** Mutable agent config — model can be changed at runtime without recreating the engine. */\n agentConfig: AgentConfig;\n /** Permission bridge for tool approval flow. */\n permissionBridge: PermissionBridge;\n /** Skill registry for progressive disclosure (built‑in + user skills). */\n skillRegistry: SkillRegistry;\n /** Current skill definitions for prompt injection. */\n skills: SkillDefinition[];\n /** MCP connection manager for external tool servers. */\n mcpManager: McpManager;\n /** Predictive loader — pre‑warms plugin modules during idle time. */\n predictiveLoader: PredictiveLoader;\n /** Channel registry — manages messaging channel adapters (飞书 etc.). */\n channelRegistry: ChannelRegistry;\n /** Memory manager for persistent cross-session recall. */\n memoryManager: MemoryManager;\n /** Persistent memory facts loaded from disk. */\n memoryFacts: string[];\n /** Textual rules loaded from disk for prompt injection. */\n rules: string[];\n /** Destroy all resources (close DB, stop timers, etc.). */\n destroy(): void;\n}\n\nexport interface BootstrapConfig {\n homeDir: string;\n provider: LlmProvider;\n model: string;\n /** Workspace directory (defaults to cwd). Used for loading project-level memory/rules/skills. */\n workspace?: string;\n /** Path to built‑in skills directory (SKILL.md files). */\n skillsDir?: string;\n /** MCP server configurations to connect on startup. */\n mcpServers?: McpServerConfig[];\n /** 飞书 channel configuration (optional — channel not created if omitted). */\n feishu?: FeishuConfig;\n}\n\n// ── Permission bridge factory ─────────────────────────\n\n/** Timeout for auto‑denying a permission request when the user doesn't respond. */\nconst PERMISSION_TIMEOUT_MS = 60_000;\n\ninterface PendingPermission {\n resolve: (approved: boolean) => void;\n timer: NodeJS.Timeout;\n}\n\n/**\n * Create a permission bridge that mediates between the agent engine\n * and the TUI's permission dialog.\n *\n * The bridge uses a Promise Map pattern:\n * 1. Agent calls `requestPermission()` → Promise created + stored\n * 2. TUI handler fires → shows permission dialog\n * 3. User responds → `handleReply()` resolves the Promise\n * 4. Agent resumes (or skips the tool)\n *\n * Safety levels \"Safe\" and \"WorkspaceSafe\" are auto‑allowed without\n * showing the dialog.\n */\nfunction createPermissionBridge(): PermissionBridge {\n const pending = new Map<string, PendingPermission>();\n let tuiHandler:\n | ((req: {\n requestId: string;\n toolName: string;\n description: string;\n safety: \"Safe\" | \"WorkspaceSafe\" | \"RequiresApproval\" | \"Dangerous\";\n }) => void)\n | null = null;\n\n const bridge: PermissionBridge = {\n async requestPermission(toolName, safety, description): Promise<boolean> {\n // Auto‑allow safe operations\n if (safety === \"Safe\" || safety === \"WorkspaceSafe\") return true;\n\n return new Promise<boolean>((resolve) => {\n const requestId = randomUUID();\n const timer = setTimeout(() => {\n pending.delete(requestId);\n resolve(false);\n }, PERMISSION_TIMEOUT_MS);\n\n pending.set(requestId, { resolve, timer });\n\n // Signal the TUI to show the permission dialog\n if (tuiHandler) {\n tuiHandler({ requestId, toolName, description, safety });\n } else {\n // No TUI attached (headless mode) → auto‑deny\n clearTimeout(timer);\n pending.delete(requestId);\n resolve(false);\n }\n });\n },\n\n handleReply(requestId, approved): void {\n const entry = pending.get(requestId);\n if (entry) {\n clearTimeout(entry.timer);\n pending.delete(requestId);\n entry.resolve(approved);\n }\n },\n\n setTuiHandler(handler): void {\n tuiHandler = handler;\n },\n };\n\n return bridge;\n}\n\n// ── Helpers ────────────────────────────────────────────\n\n/**\n * Load all .md files from a directory as an array of file contents.\n * Silently returns [] if the directory doesn't exist or is unreadable.\n */\nfunction loadTextFilesFromDir(dir: string): string[] {\n const facts: string[] = [];\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return facts;\n }\n for (const entry of entries) {\n if (!entry.endsWith(\".md\")) continue;\n const fullPath = join(dir, entry);\n try {\n if (!statSync(fullPath).isFile()) continue;\n facts.push(readFileSync(fullPath, \"utf-8\"));\n } catch {\n // Skip unreadable files\n }\n }\n return facts;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Bootstrap the entire Lynx application.\n *\n * This is the ONLY place where cross‑package wiring happens.\n * Every other module only talks to its immediate dependencies\n * through explicit factory‑injected interfaces.\n */\nexport function bootstrap(config: BootstrapConfig): AppContext {\n const dbPath = join(config.homeDir, \"state.db\");\n const workspace = config.workspace ?? process.cwd();\n\n // 1. Infrastructure\n const db = openDatabase({ dbPath });\n migrate(db);\n\n // 2. Core services\n const sessionMgr = createSessionManager(db);\n const toolRegistry = createToolRegistry();\n const ruleEngine = createRuleEngine();\n const rulesLoader = createRulesLoader(ruleEngine);\n\n // Register built‑in tools (files, agent, mode, interact)\n registerBuiltinTools(toolRegistry);\n\n // 3. Skills — progressive disclosure system with multi‑directory support\n // Default built‑in skills directory relative to lynx‑agent package\n const skillsDir =\n config.skillsDir ?? join(config.homeDir, \"..\", \"packages\", \"lynx-agent\", \"skills\");\n const skillRegistry = createSkillRegistry(skillsDir);\n\n // Also scan user and project skill directories\n const userSkillsDir = join(homedir(), \".lynx\", \"skills\");\n skillRegistry.addDirectory(userSkillsDir);\n const projectSkillsDir = join(workspace, \".claude\", \"skills\");\n skillRegistry.addDirectory(projectSkillsDir);\n\n const skills = skillRegistry.list();\n\n // Register the Skill tool so the model can request full skill bodies\n const skillToolDescriptor: ToolDescriptor = {\n name: \"Skill\",\n description:\n \"加载技能完整指令。支持 load(加载技能内容)、list(列出所有技能)、\" +\n \"search(搜索技能)、reload(重新加载技能目录)四种操作。\",\n inputSchema: {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"load\", \"list\", \"search\", \"reload\"],\n description:\n \"操作类型:load 加载技能内容、list 列出所有技能、search 搜索技能、reload 重新加载\",\n },\n skill: { type: \"string\", description: \"要加载的技能名称(load 操作时使用)\" },\n args: { type: \"string\", description: \"传递给技能的可选参数\" },\n query: { type: \"string\", description: \"搜索关键词(search 操作时使用)\" },\n },\n },\n kind: \"ReadOnly\",\n safety: \"Safe\",\n availability: { type: \"always\" },\n executor: \"Skill\",\n owner: \"core\",\n };\n toolRegistry.register(skillToolDescriptor, createSkillToolHandler(skillRegistry));\n\n // 3a. MCP — connection manager for external tool servers\n const mcpManager = createMcpManager();\n\n // Load MCP servers from settings files (global → project → local)\n const settingsMcpServers = loadAndValidateMcpConfigs();\n\n // Merge programmatic config with settings-loaded configs\n // Programmatic config takes precedence for same‑named servers\n const programmaticServers = config.mcpServers ?? [];\n const settingsOnly = settingsMcpServers.filter(\n (s) => !programmaticServers.some((p) => p.name === s.name),\n );\n const allMcpServers = [...programmaticServers, ...settingsOnly];\n\n // Connect all configured MCP servers (non‑blocking — tools appear when connected)\n for (const serverConfig of allMcpServers) {\n mcpManager.connect(serverConfig).catch(() => {\n // Connection failures are tracked via McpConnection.status;\n // the agent can call reconnect() to retry.\n });\n }\n\n // 3b. Channel registry — messaging channels (飞书 etc.)\n const channelRegistry = createChannelRegistry();\n\n // Register 飞书 adapter if configured\n if (config.feishu?.appId && config.feishu?.appSecret) {\n const feishuAdapter = createFeishuAdapter(config.feishu);\n channelRegistry.register(feishuAdapter);\n }\n\n // Rebuild toolHandlers map to include the Skill tool\n // Keys are tool NAMES (not executors) because loop.ts looks up by call.name\n const toolHandlers = new Map<string, ToolHandler>();\n const allTools = toolRegistry.listAll();\n for (const desc of allTools) {\n try {\n const handler = toolRegistry.resolveExecutor(desc);\n toolHandlers.set(desc.name, handler);\n } catch {\n // Handler not registered — skip (will be reported as \"unknown tool\" at runtime)\n }\n }\n\n // Register MCP dispatcher — each MCP tool gets a handler that forwards calls\n // to the McpManager, which routes to the correct server process.\n const mcpDispatcher: ToolHandler = {\n async handle(invocation, _signal) {\n const result = await mcpManager.callTool(invocation.toolName, invocation.payload);\n return {\n content: result.content,\n success: !result.isError,\n };\n },\n };\n // Register MCP tools — warn on name conflicts instead of silently overwriting\n for (const mcpTool of mcpManager.getAllTools()) {\n if (toolHandlers.has(mcpTool.name)) {\n process.stderr.write(`[lynx] 警告:MCP 工具 \"${mcpTool.name}\" 与现有工具冲突 — 已跳过\\n`);\n continue;\n }\n toolHandlers.set(mcpTool.name, mcpDispatcher);\n }\n\n // Merge MCP tools into the allTools catalog so they are visible to the model.\n // Filter out tools that were skipped due to name conflicts.\n const mcpToolNames = new Set(mcpManager.getAllTools().map((t) => t.name));\n const allToolsWithMcp = [\n ...allTools,\n ...mcpManager.getAllTools().filter((t) => !allTools.some((b) => b.name === t.name)),\n ];\n\n // 4. Plugin system\n const manifestRegistry = createManifestRegistry();\n const hookRegistry = createHookRegistry();\n const pluginLoader = createPluginLoader();\n const pluginRegistry = { manifestRegistry, loader: pluginLoader, hooks: hookRegistry };\n\n // 4a. Predictive loader — pre‑warms plugin modules during Phase 2 idle time\n const predictiveLoader = new PredictiveLoader(async (pluginId: string) => {\n // Dynamic import of plugin modules; pluginId is an npm package name\n // or a file path prefixed with \"file:\" for local extensions\n return import(pluginId);\n });\n\n // 4b. Load permission rules from disk (settings.json + permissions.json)\n loadFromDisk(rulesLoader, workspace);\n\n // 4c. Denial tracker — circuit breaker for consecutive denials\n const denialTracker = createDenialTracker();\n\n // 5. Permission bridge — shared between engine and TUI\n const permissionBridge = createPermissionBridge();\n\n // 5a. Memory management\n const userMemoryDir = join(homedir(), \".lynx\", \"memory\");\n const projectMemoryDir = join(workspace, \".claude\", \"memory\");\n const memoryManager = createMemoryManager(userMemoryDir);\n const memoryFacts = [\n ...(memoryManager\n .list()\n .map((e) => memoryManager.get(e.name)?.content)\n .filter(Boolean) as string[]),\n ...loadTextFilesFromDir(projectMemoryDir),\n ];\n\n // Register memory_write tool — needs MemoryManager injection\n const memoryWriteHandler = createMemoryWriteHandler(memoryManager);\n toolRegistry.register(memoryWriteDescriptor, memoryWriteHandler);\n toolHandlers.set(memoryWriteDescriptor.name, memoryWriteHandler);\n allTools.push(memoryWriteDescriptor);\n\n // Register McpAuthTool — needs McpManager injection for reconnection\n const mcpAuthOps = {\n reconnect: async (serverName: string) => {\n const conn = await mcpManager.reconnect(serverName);\n return { status: conn.status };\n },\n };\n const mcpAuthHandler = createMcpAuthHandler(mcpAuthOps);\n toolRegistry.register(mcpAuthDescriptor, mcpAuthHandler);\n toolHandlers.set(mcpAuthDescriptor.name, mcpAuthHandler);\n allTools.push(mcpAuthDescriptor);\n\n // Register SendMessageTool — needs channel registry injection\n const sendMessageSender = {\n async send(channel: string, content: string, recipients?: string[]) {\n const adapter = channelRegistry.get(channel);\n if (!adapter) throw new Error(`未注册的消息通道:${channel}`);\n const message = {\n id: randomUUID() as unknown as import(\"@lynx/core\").MessageId,\n role: \"assistant\" as const,\n content: [{ type: \"text\" as const, text: content }],\n timestamp: Date.now(),\n turnIndex: 0,\n };\n const messageId = await adapter.sendMessage(message);\n return { messageId };\n },\n };\n const sendMessageHandler = createSendMessageHandler(sendMessageSender);\n toolRegistry.register(sendMessageDescriptor, sendMessageHandler);\n toolHandlers.set(sendMessageDescriptor.name, sendMessageHandler);\n allTools.push(sendMessageDescriptor);\n\n // 全局用户指令(~/.lynx/LYNX.md)— 优先级最高,在所有项目中生效\n const globalLynxMd = join(homedir(), \".lynx\", \"LYNX.md\");\n const globalInstructions: string[] = [];\n try {\n if (existsSync(globalLynxMd) && statSync(globalLynxMd).isFile()) {\n globalInstructions.push(`# LYNX.md — 全局用户指令\\n${readFileSync(globalLynxMd, \"utf-8\")}`);\n }\n } catch {\n // 文件不存在或不可读 — 跳过\n }\n\n const userRulesDir = join(homedir(), \".lynx\", \"rules\");\n const projectRulesDir = join(workspace, \".claude\", \"rules\");\n const rules = [\n ...globalInstructions,\n ...loadTextFilesFromDir(userRulesDir),\n ...loadTextFilesFromDir(projectRulesDir),\n ];\n\n // 6. Agent engine — config is a mutable object so model can be swapped at runtime\n const agentConfig: AgentConfig = {\n provider: config.provider,\n model: config.model,\n systemPrompt: getBaseSystemPrompt(),\n maxTokens: 8192,\n maxCompactionFailures: 3,\n budget: { maxTokens: 200_000, maxUsd: 10, maxTurns: 100 },\n };\n\n const engine = createQueryEngine({\n config: agentConfig,\n provider: config.provider,\n toolHandlers,\n allTools: allToolsWithMcp,\n skills,\n memoryFacts,\n rules,\n checkPermission: async (toolName, safety, description) => {\n // Circuit breaker: auto‑deny if user denied 3 consecutive high‑safety tools\n if (denialTracker.isTripped()) return false;\n\n const safetyLevel = safety as \"Safe\" | \"WorkspaceSafe\" | \"RequiresApproval\" | \"Dangerous\";\n const approved = await permissionBridge.requestPermission(toolName, safetyLevel, description);\n\n // Only track user-facing decisions (Safe/WorkspaceSafe auto‑approve without dialog)\n if (safetyLevel === \"RequiresApproval\" || safetyLevel === \"Dangerous\") {\n if (approved) {\n denialTracker.recordApproval();\n } else {\n denialTracker.recordDenial();\n }\n }\n return approved;\n },\n });\n\n // 7. Task manager — background task lifecycle for the task tool\n const taskManager = createTaskManager();\n injectTaskManager(taskManager);\n\n const ctx: AppContext = {\n db,\n sessionMgr,\n toolRegistry,\n pluginRegistry,\n ruleEngine,\n engine,\n provider: config.provider,\n agentConfig,\n permissionBridge,\n skillRegistry,\n skills,\n mcpManager,\n predictiveLoader,\n channelRegistry,\n memoryManager,\n memoryFacts,\n rules,\n\n destroy(): void {\n taskManager.destroy();\n channelRegistry.destroyAll();\n mcpManager.destroy();\n engine.destroy();\n sessionMgr.destroy();\n db.close();\n },\n };\n\n return ctx;\n}\n","/**\n * Two‑phase startup — split blocking init from background work.\n *\n * Phase 1 (≤ 1.5s): version check → config load → SQLite open → migrate.\n * Phase 2 (background): plugin discovery → MCP connect → memory load.\n *\n * The phases are split so the TUI can render as soon as Phase 1\n * finishes, giving the user immediate feedback.\n */\n\nimport type { Database } from \"@lynx/core\";\nimport { openDatabase, migrate, resolvePaths, type LynxPaths } from \"@lynx/core\";\nimport type { PluginRuntimeContext } from \"@lynx/plugins\";\nimport type { AppContext } from \"./bootstrap.js\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface Phase1Result {\n paths: LynxPaths;\n db: Database;\n /** Wall‑clock ms for Phase 1. */\n elapsedMs: number;\n}\n\nexport interface StartupResult extends Phase1Result {\n /** Wall‑clock ms for Phase 2. */\n phase2ElapsedMs: number;\n}\n\n/** Result of a single Phase 2 background task. */\ninterface Phase2TaskResult {\n name: string;\n ok: boolean;\n error?: string;\n elapsedMs: number;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Run Phase 1 — blocking startup that must finish before\n * the user sees anything useful.\n *\n * Returns a database handle the rest of the app can use.\n */\nexport function runPhase1(): Phase1Result {\n const started = Date.now();\n\n const paths = resolvePaths();\n const db = openDatabase({ dbPath: paths.stateDb });\n migrate(db);\n\n const elapsedMs = Date.now() - started;\n return { paths, db, elapsedMs };\n}\n\n/**\n * Run Phase 2 — background work that executes after the TUI is rendered.\n *\n * Tasks run sequentially with a setImmediate gap between each so the\n * event loop stays responsive. Each task is independently try‑catched\n * so a single failure doesn't block the rest.\n *\n * Returns the elapsed time in ms.\n */\nexport async function runPhase2(result: Phase1Result): Promise<Phase2TaskResult[]> {\n return runPhase2Tasks(null, result.paths);\n}\n\n/**\n * Run Phase 2 with the full AppContext (preferred path).\n *\n * When called from the TUI launcher, the AppContext provides access\n * to all initialized services (plugin registry, MCP manager, etc.).\n *\n * Tasks:\n * 1. scanExtensions — discover plugins in the extensions directory\n * 2. preloadSkills — index skills for autocomplete\n * 3. connectMcpServers — establish MCP connections\n * 4. loadMemory — load memory files into the agent context\n */\nexport async function runPhase2WithContext(\n ctx: AppContext,\n paths: LynxPaths,\n): Promise<Phase2TaskResult[]> {\n return runPhase2Tasks(ctx, paths);\n}\n\n// ── Internals ──────────────────────────────────────────\n\ninterface Phase2Logger {\n info(msg: string): void;\n warn(msg: string): void;\n}\n\nfunction createSilentLogger(): Phase2Logger {\n return {\n info: () => {},\n warn: (msg) => process.stderr.write(`[phase2] ${msg}\\n`),\n };\n}\n\nasync function runPhase2Tasks(\n ctx: AppContext | null,\n paths: LynxPaths,\n): Promise<Phase2TaskResult[]> {\n const logger = createSilentLogger();\n const results: Phase2TaskResult[] = [];\n\n const tasks: Array<() => Promise<void>> = [\n async () => {\n await scanExtensions(ctx, paths, logger);\n },\n async () => {\n await preloadSkills(ctx, paths, logger);\n },\n async () => {\n await connectMcpServers(ctx, logger);\n },\n async () => {\n await loadMemory(ctx, paths, logger);\n },\n async () => {\n await initChannels(ctx, logger);\n },\n ];\n\n for (const task of tasks) {\n // Yield to the event loop so the TUI stays responsive\n await new Promise((resolve) => setImmediate(resolve));\n\n const started = Date.now();\n const name = task.name || \"unknown\";\n try {\n await task();\n results.push({ name, ok: true, elapsedMs: Date.now() - started });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.warn(`${name} failed: ${message}`);\n results.push({ name, ok: false, error: message, elapsedMs: Date.now() - started });\n }\n }\n\n if (results.some((r) => !r.ok)) {\n const failures = results\n .filter((r) => !r.ok)\n .map((r) => r.name)\n .join(\", \");\n logger.warn(`Phase 2 completed with failures: ${failures}`);\n } else {\n logger.info(`Phase 2 completed: ${results.length} tasks OK`);\n }\n\n return results;\n}\n\n// ── Task implementations ──────────────────────────────\n\n/**\n * Scan the extensions directory for plugins, register manifests,\n * and execute plugin code via the PluginLoader.\n *\n * Looks in:\n * 1. <lynx-home>/extensions/ (user extensions)\n * 2. <project>/.lynx/extensions/ (project extensions)\n * 3. <lynx-install>/extensions/ (built‑in extensions)\n */\nasync function scanExtensions(\n ctx: AppContext | null,\n paths: LynxPaths,\n logger: Phase2Logger,\n): Promise<void> {\n if (!ctx) return; // No plugin registry available\n\n const { readdirSync, statSync } = await import(\"node:fs\");\n const { join } = await import(\"node:path\");\n\n const scanDirs = [join(paths.home, \"extensions\"), join(process.cwd(), \".lynx\", \"extensions\")];\n\n for (const dir of scanDirs) {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n continue; // Directory doesn't exist — skip\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n try {\n if (!statSync(fullPath).isDirectory()) continue;\n } catch {\n continue;\n }\n\n // Each extension directory should have a manifest.json\n const manifestPath = join(fullPath, \"manifest.json\");\n try {\n statSync(manifestPath);\n } catch {\n continue; // No manifest — not a valid extension\n }\n\n try {\n const discovered = ctx.pluginRegistry.manifestRegistry.scan([fullPath]);\n // Actually load and execute each discovered plugin\n for (const manifestEntry of discovered) {\n const pluginCtx: PluginRuntimeContext = {\n pluginId: manifestEntry.manifest.name,\n logger: {\n info: (msg) => logger.info(`[plugin:${manifestEntry.manifest.name}] ${msg}`),\n warn: (msg) => logger.warn(`[plugin:${manifestEntry.manifest.name}] ${msg}`),\n error: (msg) => logger.warn(`[plugin:${manifestEntry.manifest.name}] ${msg}`),\n },\n config: {},\n storage: createPluginStorage(),\n };\n ctx.pluginRegistry.loader.load(manifestEntry, pluginCtx).catch((err) => {\n logger.warn(`Plugin \"${manifestEntry.manifest.name}\" failed to load: ${String(err)}`);\n });\n }\n } catch {\n // Duplicate registration or invalid manifest — skip\n }\n }\n }\n}\n\n/**\n * Create a simple in‑memory KV store for plugin storage.\n *\n * In Phase 5 this will be backed by the SQLite database\n * for persistence across sessions.\n */\nfunction createPluginStorage(): PluginRuntimeContext[\"storage\"] {\n const store = new Map<string, unknown>();\n return {\n get<T>(key: string): T | undefined {\n return store.get(key) as T | undefined;\n },\n set<T>(key: string, value: T): void {\n store.set(key, value);\n },\n delete(key: string): void {\n store.delete(key);\n },\n clear(): void {\n store.clear();\n },\n };\n}\n\n/**\n * Preload skill definitions for autocomplete and quick access.\n *\n * Skills are already loaded during bootstrap (Phase 1). This Phase 2 task\n * enqueues plugins for predictive warming through the PredictiveLoader,\n * reducing first‑use latency for plugins that are likely to be needed.\n */\nasync function preloadSkills(\n ctx: AppContext | null,\n _paths: LynxPaths,\n _logger: Phase2Logger,\n): Promise<void> {\n if (!ctx) return;\n\n const loader = ctx.predictiveLoader;\n\n // Warm plugins based on input‑prefix heuristics\n loader.onInputPrefix(\"/\"); // skills via slash commands\n loader.onInputPrefix(\"@\"); // model/provider picker\n\n // Process the warmup queue (non‑blocking, yields between imports)\n await loader.processQueue();\n}\n\n/**\n * Connect to configured MCP servers.\n *\n * MCP connections are established during bootstrap (Phase 1) via\n * McpManager.connect() for all configured servers. Connection\n * failures are tracked via McpConnection.status.\n *\n * This Phase 2 task exists as a hook point for re‑connection or\n * health check logic in future phases.\n */\nasync function connectMcpServers(_ctx: AppContext | null, _logger: Phase2Logger): Promise<void> {\n // MCP connections are established in bootstrap.\n // Future: health‑check + auto‑reconnect here.\n}\n\n/**\n * Load memory files into the agent's context.\n *\n * Memory is loaded during bootstrap (Phase 1) from:\n * 1. ~/.lynx/memory/\n * 2. <workspace>/.claude/memory/\n *\n * Content flows through AppContext.memoryFacts → EngineDeps →\n * LoopDeps → assembleSystemPrompt (\"Memory\" section).\n *\n * This Phase 2 task exists as a hook point for runtime memory\n * reload (e.g. after file changes).\n */\nasync function loadMemory(\n _ctx: AppContext | null,\n paths: LynxPaths,\n _logger: Phase2Logger,\n): Promise<void> {\n // Memory is already loaded in bootstrap.\n // Future: watch for file changes and reload memory into ctx.\n void paths; // keep parameter for future file‑watching use\n}\n\n/**\n * Initialize registered channel adapters (飞书 etc.).\n *\n * Channels are registered during bootstrap (Phase 1) but their\n * init() (auth, WS connect) is deferred to Phase 2 so the TUI\n * is already visible before network I/O starts.\n */\nasync function initChannels(ctx: AppContext | null, logger: Phase2Logger): Promise<void> {\n if (!ctx) return;\n const channelIds = ctx.channelRegistry.list();\n if (channelIds.length === 0) {\n logger.info(\"No channel adapters registered — skipping channel init\");\n return;\n }\n logger.info(`Initializing channels: ${channelIds.join(\", \")}`);\n await ctx.channelRegistry.initAll();\n logger.info(`Channels initialized: ${channelIds.join(\", \")}`);\n}\n","/**\n * Terminal mode management — alt buffer, mouse tracking, DEC sync.\n *\n * Provides functions to enter/exit the alternate screen buffer,\n * enable/disable mouse tracking, and wrap output with DEC\n * Synchronized Update markers for flicker‑free rendering.\n *\n * All escape sequences are no‑ops on unsupported terminals\n * (graceful degradation).\n */\n\n// ── Escape sequences ──────────────────────────────────\n\n/** Enter alternate screen buffer. */\nconst ALT_ENTER = \"\\x1b[?1049h\";\n/** Exit alternate screen buffer. */\nconst ALT_EXIT = \"\\x1b[?1049l\";\n\n/** Begin DEC Synchronized Update — subsequent output is buffered. */\nconst BSU = \"\\x1b[?2026h\";\n/** End DEC Synchronized Update — flush buffered output atomically. */\nconst ESU = \"\\x1b[?2026l\";\n\n// ── Public API ───────────────────────────────────────\n\n/** Enter fullscreen mode: alt buffer only (no mouse tracking). */\nexport function enterFullscreen(): void {\n process.stdout.write(ALT_ENTER);\n}\n\n/** Exit fullscreen mode: restore main buffer. */\nexport function exitFullscreen(): void {\n process.stdout.write(ALT_EXIT);\n}\n\n/**\n * Wrap a synchronous callback with DEC Synchronized Update markers.\n *\n * All output written to stdout during the callback is buffered by the\n * terminal and rendered atomically, eliminating flicker during re‑renders.\n *\n * Does NOT nest — calling beginSync inside an active sync region\n * is a no‑op (tracked via module‑level flag).\n */\nlet syncActive = false;\n\nexport function beginSync(): void {\n if (syncActive) return;\n syncActive = true;\n process.stdout.write(BSU);\n}\n\nexport function endSync(): void {\n if (!syncActive) return;\n syncActive = false;\n process.stdout.write(ESU);\n}\n\n/**\n * Execute a callback within a DEC synchronized update region.\n * Exceptions propagate; sync is always ended.\n */\nexport function withSync<T>(fn: () => T): T {\n beginSync();\n try {\n return fn();\n } finally {\n endSync();\n }\n}\n","/**\n * Process lifecycle — signal handling, terminal loss detection,\n * graceful shutdown, and force‑exit timer.\n *\n * Integrates with the 3‑layer abort system:\n * SIGINT 1 → abort LLM request\n * SIGINT 2 → abort running tool\n * SIGINT 3 → process.exit(1)\n *\n * Provides:\n * - SIGINT / SIGTERM / SIGHUP handlers\n * - Terminal loss detection (stdin/stdout close)\n * - Force exit timer (2s after shutdown starts)\n * - Graceful cleanup: flush draft, close DB\n */\n\nimport type { AppContext } from \"./bootstrap.js\";\nimport { exitFullscreen } from \"./terminal-mode.js\";\nimport { loadConfig } from \"./commands/config.js\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface LifecycleConfig {\n /** AppContext for graceful cleanup. */\n ctx: AppContext;\n /** Callback to abort the current LLM request. */\n onAbortLl: () => void;\n /** Callback to abort the current tool execution. */\n onAbortTool: () => void;\n /** Current abort layer counter (0‑3). Gets incremented by SIGINT. */\n getAbortLayer: () => number;\n /** Increment and return the next abort layer. */\n incrementAbortLayer: () => number;\n /** Reset abort layer counter. */\n resetAbortLayer: () => void;\n /** Whether the TUI is currently streaming (in an active turn). */\n isStreaming: () => boolean;\n}\n\n// ── State ────────────────────────────────────────────\n\nlet _cleanupHandler: (() => void) | null = null;\nlet hardExitTimer: NodeJS.Timeout | null = null;\nlet installed = false;\n\n/** Maximum time (ms) allowed for graceful shutdown before force exit. */\nconst FORCE_EXIT_MS = 2_000;\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Install process lifecycle handlers.\n *\n * Only call once. Subsequent calls are no‑ops.\n * Handles SIGINT (3‑layer abort), SIGTERM (graceful shutdown),\n * SIGHUP (graceful shutdown), and terminal loss.\n */\nexport function installProcessLifecycle(config: LifecycleConfig): void {\n if (installed) return;\n installed = true;\n\n const { ctx, onAbortLl, onAbortTool, incrementAbortLayer, isStreaming } = config;\n\n // ── SIGINT — 3‑layer abort ──────────────────────\n const onSigint = () => {\n const layer = incrementAbortLayer();\n\n if (layer >= 3) {\n // Hard exit\n exitFullscreen();\n ctx.destroy();\n process.exit(1);\n }\n\n if (!isStreaming()) {\n // No active stream → exit immediately\n exitFullscreen();\n ctx.destroy();\n process.exit(0);\n }\n\n if (layer === 1) {\n onAbortLl();\n } else if (layer === 2) {\n onAbortTool();\n }\n };\n\n process.on(\"SIGINT\", onSigint);\n\n // ── SIGTERM — graceful shutdown ────────────────\n const gracefulShutdown = () => {\n startForceExitTimer();\n\n try {\n exitFullscreen();\n } catch {\n // Terminal may already be gone\n }\n\n try {\n ctx.destroy();\n } catch {\n // Best effort\n }\n\n process.exit(0);\n };\n\n process.on(\"SIGTERM\", gracefulShutdown);\n\n // ── SIGHUP — reload configuration ───────────────\n // Unix convention: SIGHUP = reload, not kill.\n // Falls back to graceful shutdown if reload fails.\n process.on(\"SIGHUP\", () => {\n try {\n const newCfg = loadConfig();\n // Apply model change if present\n if (typeof newCfg.model === \"string\" && newCfg.model !== ctx.agentConfig.model) {\n ctx.agentConfig.model = newCfg.model;\n process.stderr.write(`[lynx] SIGHUP:模型已切换 → ${newCfg.model}\\n`);\n }\n // Theme changes are handled by the TUI on next render\n if (typeof newCfg.theme === \"string\") {\n process.stderr.write(`[lynx] SIGHUP:主题已切换 → ${newCfg.theme}\\n`);\n }\n process.stderr.write(\"[lynx] SIGHUP:配置已重新加载\\n\");\n } catch (reloadErr) {\n process.stderr.write(\n `[lynx] SIGHUP 重新加载失败:${reloadErr instanceof Error ? reloadErr.message : String(reloadErr)} — 回退到关闭流程\\n`,\n );\n gracefulShutdown();\n }\n });\n\n // ── Terminal loss detection ─────────────────────\n const onTerminalLost = () => {\n // stdin/stdout closed — terminal was killed\n startForceExitTimer();\n try {\n ctx.destroy();\n } catch {\n // Best effort\n }\n process.exit(0);\n };\n\n process.stdin.on(\"end\", onTerminalLost);\n process.stdin.on(\"close\", onTerminalLost);\n process.stdout.on(\"close\", onTerminalLost);\n\n // ── Unhandled rejection — log and exit ──────────\n process.on(\"unhandledRejection\", (reason) => {\n process.stderr.write(\n `[lynx] 未处理的 Promise 拒绝:${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}\\n`,\n );\n startForceExitTimer();\n exitFullscreen();\n ctx.destroy();\n process.exit(1);\n });\n\n // ── Uncaught exception — log and exit ───────────\n process.on(\"uncaughtException\", (err) => {\n process.stderr.write(`[lynx] 未捕获的异常:${err.stack ?? err.message}\\n`);\n startForceExitTimer();\n exitFullscreen();\n ctx.destroy();\n process.exit(1);\n });\n}\n\n/**\n * Register a cleanup handler that runs during graceful shutdown.\n * Replaces any previously registered handler.\n */\nexport function onCleanup(handler: () => void): void {\n _cleanupHandler = handler;\n}\n\n/**\n * Remove all process lifecycle handlers (for testing).\n */\nexport function uninstallProcessLifecycle(): void {\n installed = false;\n process.removeAllListeners(\"SIGINT\");\n process.removeAllListeners(\"SIGTERM\");\n process.removeAllListeners(\"SIGHUP\");\n if (hardExitTimer) {\n clearTimeout(hardExitTimer);\n hardExitTimer = null;\n }\n _cleanupHandler = null;\n}\n\n// ── Internals ──────────────────────────────────────────\n\nfunction startForceExitTimer(): void {\n if (hardExitTimer) return;\n hardExitTimer = setTimeout(() => {\n process.stderr.write(\"[lynx] 超时后强制退出\\n\");\n process.exit(1);\n }, FORCE_EXIT_MS);\n hardExitTimer.unref(); // Don't block the event loop\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
//#region src/commands/sandbox-toggle.ts
|
|
5
|
+
/**
|
|
6
|
+
* /sandbox-toggle — 切换 Bash 沙箱模式开关。
|
|
7
|
+
*
|
|
8
|
+
* 读写 ~/.lynx/sandbox.json 文件,控制命令执行沙箱的启用状态。
|
|
9
|
+
* 支持三种模式:
|
|
10
|
+
* - 无参数:切换当前状态
|
|
11
|
+
* - --enable:强制启用
|
|
12
|
+
* - --disable:强制禁用
|
|
13
|
+
*
|
|
14
|
+
* 沙箱文件格式:{ "enabled": true }
|
|
15
|
+
*/
|
|
16
|
+
/** 获取 sandbox.json 的完整路径。 */
|
|
17
|
+
function sandboxConfigPath() {
|
|
18
|
+
return join(homedir(), ".lynx", "sandbox.json");
|
|
19
|
+
}
|
|
20
|
+
/** 读取当前沙箱配置。文件不存在时默认启用。 */
|
|
21
|
+
function loadSandboxConfig() {
|
|
22
|
+
const path = sandboxConfigPath();
|
|
23
|
+
if (!existsSync(path)) return { enabled: true };
|
|
24
|
+
try {
|
|
25
|
+
const raw = readFileSync(path, "utf-8");
|
|
26
|
+
const parsed = JSON.parse(raw);
|
|
27
|
+
return { enabled: typeof parsed?.enabled === "boolean" ? parsed.enabled : true };
|
|
28
|
+
} catch {
|
|
29
|
+
return { enabled: true };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** 保存沙箱配置到磁盘。 */
|
|
33
|
+
function saveSandboxConfig(config) {
|
|
34
|
+
const path = sandboxConfigPath();
|
|
35
|
+
const dir = dirname(path);
|
|
36
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
37
|
+
mode: 448,
|
|
38
|
+
recursive: true
|
|
39
|
+
});
|
|
40
|
+
writeFileSync(path, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 处理 /sandbox-toggle 命令。
|
|
44
|
+
*
|
|
45
|
+
* 根据参数切换、启用或禁用 Bash 沙箱。
|
|
46
|
+
* 返回操作后的当前状态。
|
|
47
|
+
*/
|
|
48
|
+
function handleSandboxToggleCommand(args) {
|
|
49
|
+
const current = loadSandboxConfig();
|
|
50
|
+
let newState;
|
|
51
|
+
if (args.enable) newState = true;
|
|
52
|
+
else if (args.disable) newState = false;
|
|
53
|
+
else newState = !current.enabled;
|
|
54
|
+
saveSandboxConfig({ enabled: newState });
|
|
55
|
+
const statusText = newState ? "已启用" : "已禁用";
|
|
56
|
+
const statusIcon = newState ? "开" : "关";
|
|
57
|
+
const previousText = current.enabled ? "启用" : "禁用";
|
|
58
|
+
if (args.enable || args.disable) return { output: `沙箱模式:${statusIcon}\n状态已从「${previousText}」切换为「${statusText}」。` };
|
|
59
|
+
return { output: `沙箱模式:${statusIcon}\n状态已切换:${previousText} → ${statusText}。` };
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { handleSandboxToggleCommand };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=sandbox-toggle-9akjTw3h.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox-toggle-9akjTw3h.mjs","names":[],"sources":["../src/commands/sandbox-toggle.ts"],"sourcesContent":["/**\n * /sandbox-toggle — 切换 Bash 沙箱模式开关。\n *\n * 读写 ~/.lynx/sandbox.json 文件,控制命令执行沙箱的启用状态。\n * 支持三种模式:\n * - 无参数:切换当前状态\n * - --enable:强制启用\n * - --disable:强制禁用\n *\n * 沙箱文件格式:{ \"enabled\": true }\n */\n\nimport { homedir } from \"node:os\";\nimport { join, dirname } from \"node:path\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface SandboxToggleArgs {\n /** 强制启用沙箱。 */\n enable?: boolean;\n /** 强制禁用沙箱。 */\n disable?: boolean;\n}\n\ninterface SandboxConfig {\n enabled: boolean;\n}\n\n// ── Helpers ──────────────────────────────────────────\n\n/** 获取 sandbox.json 的完整路径。 */\nfunction sandboxConfigPath(): string {\n return join(homedir(), \".lynx\", \"sandbox.json\");\n}\n\n/** 读取当前沙箱配置。文件不存在时默认启用。 */\nfunction loadSandboxConfig(): SandboxConfig {\n const path = sandboxConfigPath();\n if (!existsSync(path)) return { enabled: true };\n try {\n const raw = readFileSync(path, \"utf-8\");\n const parsed = JSON.parse(raw);\n return {\n enabled: typeof parsed?.enabled === \"boolean\" ? parsed.enabled : true,\n };\n } catch {\n return { enabled: true };\n }\n}\n\n/** 保存沙箱配置到磁盘。 */\nfunction saveSandboxConfig(config: SandboxConfig): void {\n const path = sandboxConfigPath();\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { mode: 0o700, recursive: true });\n writeFileSync(path, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /sandbox-toggle 命令。\n *\n * 根据参数切换、启用或禁用 Bash 沙箱。\n * 返回操作后的当前状态。\n */\nexport function handleSandboxToggleCommand(args: SandboxToggleArgs): { output: string } {\n const current = loadSandboxConfig();\n let newState: boolean;\n\n if (args.enable) {\n newState = true;\n } else if (args.disable) {\n newState = false;\n } else {\n // 无参数:切换\n newState = !current.enabled;\n }\n\n saveSandboxConfig({ enabled: newState });\n\n const statusText = newState ? \"已启用\" : \"已禁用\";\n const statusIcon = newState ? \"开\" : \"关\";\n const previousText = current.enabled ? \"启用\" : \"禁用\";\n\n if (args.enable || args.disable) {\n return {\n output: `沙箱模式:${statusIcon}\\n状态已从「${previousText}」切换为「${statusText}」。`,\n };\n }\n\n return {\n output: `沙箱模式:${statusIcon}\\n状态已切换:${previousText} → ${statusText}。`,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgCA,SAAS,oBAA4B;CACnC,OAAO,KAAK,QAAQ,GAAG,SAAS,cAAc;AAChD;;AAGA,SAAS,oBAAmC;CAC1C,MAAM,OAAO,kBAAkB;CAC/B,IAAI,CAAC,WAAW,IAAI,GAAG,OAAO,EAAE,SAAS,KAAK;CAC9C,IAAI;EACF,MAAM,MAAM,aAAa,MAAM,OAAO;EACtC,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,OAAO,EACL,SAAS,OAAO,QAAQ,YAAY,YAAY,OAAO,UAAU,KACnE;CACF,QAAQ;EACN,OAAO,EAAE,SAAS,KAAK;CACzB;AACF;;AAGA,SAAS,kBAAkB,QAA6B;CACtD,MAAM,OAAO,kBAAkB;CAC/B,MAAM,MAAM,QAAQ,IAAI;CACxB,IAAI,CAAC,WAAW,GAAG,GAAG,UAAU,KAAK;EAAE,MAAM;EAAO,WAAW;CAAK,CAAC;CACrE,cAAc,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,MAAM,OAAO;AACrE;;;;;;;AAUA,SAAgB,2BAA2B,MAA6C;CACtF,MAAM,UAAU,kBAAkB;CAClC,IAAI;CAEJ,IAAI,KAAK,QACP,WAAW;MACN,IAAI,KAAK,SACd,WAAW;MAGX,WAAW,CAAC,QAAQ;CAGtB,kBAAkB,EAAE,SAAS,SAAS,CAAC;CAEvC,MAAM,aAAa,WAAW,QAAQ;CACtC,MAAM,aAAa,WAAW,MAAM;CACpC,MAAM,eAAe,QAAQ,UAAU,OAAO;CAE9C,IAAI,KAAK,UAAU,KAAK,SACtB,OAAO,EACL,QAAQ,QAAQ,WAAW,SAAS,aAAa,OAAO,WAAW,IACrE;CAGF,OAAO,EACL,QAAQ,QAAQ,WAAW,UAAU,aAAa,KAAK,WAAW,GACpE;AACF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/commands/stats.ts
|
|
2
|
+
/**
|
|
3
|
+
* 处理 /stats 命令。
|
|
4
|
+
*
|
|
5
|
+
* 生成一段中文指令,引导模型收集并格式化会话统计数据。
|
|
6
|
+
* 模型会根据自己的消息历史和工具调用记录来生成报告。
|
|
7
|
+
*/
|
|
8
|
+
function handleStatsCommand(args) {
|
|
9
|
+
const period = args.period ?? "all";
|
|
10
|
+
const lines = [
|
|
11
|
+
`# 会话统计(${period === "today" ? "今日" : period === "week" ? "本周" : "全部"})`,
|
|
12
|
+
"",
|
|
13
|
+
"请根据当前会话的消息历史和工具调用记录,生成一份统计摘要。",
|
|
14
|
+
"",
|
|
15
|
+
"## 需要统计的内容",
|
|
16
|
+
"",
|
|
17
|
+
"1. **Token 消耗**:估算已消耗的输入 token 和输出 token 总量。",
|
|
18
|
+
" 如果有精确的 token 计数数据请使用,否则根据消息长度合理估算。",
|
|
19
|
+
"",
|
|
20
|
+
"2. **对话轮次**:统计 user ↔ assistant 的往返次数。",
|
|
21
|
+
"",
|
|
22
|
+
"3. **工具调用**:按工具名称分组统计调用次数,列举如下:",
|
|
23
|
+
" - 工具名称:调用次数",
|
|
24
|
+
" - 按调用次数降序排列",
|
|
25
|
+
"",
|
|
26
|
+
"4. **对话时长**:估算从第一条消息到现在的总时长。",
|
|
27
|
+
"",
|
|
28
|
+
"5. **文件操作**:统计读写文件的数量(如果有的话)。",
|
|
29
|
+
"",
|
|
30
|
+
"6. **最常用工具 Top 3**:列出调用最多的三个工具。",
|
|
31
|
+
"",
|
|
32
|
+
"## 输出格式",
|
|
33
|
+
"",
|
|
34
|
+
"请用以下格式输出(用中文):",
|
|
35
|
+
"",
|
|
36
|
+
"```",
|
|
37
|
+
"=== 会话统计(${periodLabel}) ===",
|
|
38
|
+
"",
|
|
39
|
+
"Token 消耗",
|
|
40
|
+
" 输入:xxx tokens",
|
|
41
|
+
" 输出:xxx tokens",
|
|
42
|
+
" 合计:xxx tokens",
|
|
43
|
+
"",
|
|
44
|
+
"对话轮次",
|
|
45
|
+
" xxx 轮",
|
|
46
|
+
"",
|
|
47
|
+
"对话时长",
|
|
48
|
+
" xxx 分钟",
|
|
49
|
+
"",
|
|
50
|
+
"工具调用统计",
|
|
51
|
+
" tool_a:xx 次",
|
|
52
|
+
" tool_b:xx 次",
|
|
53
|
+
" ...",
|
|
54
|
+
"",
|
|
55
|
+
"文件操作",
|
|
56
|
+
" 读取:xx 个文件",
|
|
57
|
+
" 写入:xx 个文件",
|
|
58
|
+
"",
|
|
59
|
+
"最常用工具",
|
|
60
|
+
" 1. tool_a — xx 次",
|
|
61
|
+
" 2. tool_b — xx 次",
|
|
62
|
+
" 3. tool_c — xx 次",
|
|
63
|
+
"```",
|
|
64
|
+
"",
|
|
65
|
+
"如果某些数据无法精确统计,请标注「估算」并用合理范围表示。"
|
|
66
|
+
];
|
|
67
|
+
if (args.sessionId) lines.push("", `目标会话:${args.sessionId}`);
|
|
68
|
+
return { instruction: lines.join("\n") };
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { handleStatsCommand };
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=stats-DjKezhTJ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats-DjKezhTJ.mjs","names":[],"sources":["../src/commands/stats.ts"],"sourcesContent":["/**\n * /stats — 会话统计信息。\n *\n * 返回一段中文指令,引导模型分析当前会话的各种统计数据:\n * token 消耗、工具调用频率、对话时长、最常用工具等。\n */\n\n// ── Types ────────────────────────────────────────────\n\nexport interface StatsCommandArgs {\n /** 可选,指定要统计的会话 ID(默认当前会话)。 */\n sessionId?: string;\n /** 统计时间范围:today / week / all(默认 all)。 */\n period?: \"today\" | \"week\" | \"all\";\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /stats 命令。\n *\n * 生成一段中文指令,引导模型收集并格式化会话统计数据。\n * 模型会根据自己的消息历史和工具调用记录来生成报告。\n */\nexport function handleStatsCommand(args: StatsCommandArgs): { instruction: string } {\n const period = args.period ?? \"all\";\n const periodLabel = period === \"today\" ? \"今日\" : period === \"week\" ? \"本周\" : \"全部\";\n\n const lines: string[] = [\n `# 会话统计(${periodLabel})`,\n \"\",\n \"请根据当前会话的消息历史和工具调用记录,生成一份统计摘要。\",\n \"\",\n \"## 需要统计的内容\",\n \"\",\n \"1. **Token 消耗**:估算已消耗的输入 token 和输出 token 总量。\",\n \" 如果有精确的 token 计数数据请使用,否则根据消息长度合理估算。\",\n \"\",\n \"2. **对话轮次**:统计 user ↔ assistant 的往返次数。\",\n \"\",\n \"3. **工具调用**:按工具名称分组统计调用次数,列举如下:\",\n \" - 工具名称:调用次数\",\n \" - 按调用次数降序排列\",\n \"\",\n \"4. **对话时长**:估算从第一条消息到现在的总时长。\",\n \"\",\n \"5. **文件操作**:统计读写文件的数量(如果有的话)。\",\n \"\",\n \"6. **最常用工具 Top 3**:列出调用最多的三个工具。\",\n \"\",\n \"## 输出格式\",\n \"\",\n \"请用以下格式输出(用中文):\",\n \"\",\n \"```\",\n \"=== 会话统计(${periodLabel}) ===\",\n \"\",\n \"Token 消耗\",\n \" 输入:xxx tokens\",\n \" 输出:xxx tokens\",\n \" 合计:xxx tokens\",\n \"\",\n \"对话轮次\",\n \" xxx 轮\",\n \"\",\n \"对话时长\",\n \" xxx 分钟\",\n \"\",\n \"工具调用统计\",\n \" tool_a:xx 次\",\n \" tool_b:xx 次\",\n \" ...\",\n \"\",\n \"文件操作\",\n \" 读取:xx 个文件\",\n \" 写入:xx 个文件\",\n \"\",\n \"最常用工具\",\n \" 1. tool_a — xx 次\",\n \" 2. tool_b — xx 次\",\n \" 3. tool_c — xx 次\",\n \"```\",\n \"\",\n \"如果某些数据无法精确统计,请标注「估算」并用合理范围表示。\",\n ];\n\n if (args.sessionId) {\n lines.push(\"\", `目标会话:${args.sessionId}`);\n }\n\n return { instruction: lines.join(\"\\n\") };\n}\n"],"mappings":";;;;;;;AAwBA,SAAgB,mBAAmB,MAAiD;CAClF,MAAM,SAAS,KAAK,UAAU;CAG9B,MAAM,QAAkB;EACtB,UAHkB,WAAW,UAAU,OAAO,WAAW,SAAS,OAAO,KAGnD;EACtB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CAEA,IAAI,KAAK,WACP,MAAM,KAAK,IAAI,QAAQ,KAAK,WAAW;CAGzC,OAAO,EAAE,aAAa,MAAM,KAAK,IAAI,EAAE;AACzC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//#region src/commands/status.ts
|
|
2
|
+
/**
|
|
3
|
+
* /status — 系统健康仪表盘。
|
|
4
|
+
*
|
|
5
|
+
* 返回一段中文指令,引导模型检查 Lynx 系统的各项健康指标:
|
|
6
|
+
* MCP 连接状态、Channel 适配器、Agent 引擎、磁盘用量等。
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* 处理 /status 命令。
|
|
10
|
+
*
|
|
11
|
+
* 生成一段中文指令,引导模型检查并报告系统各项健康指标。
|
|
12
|
+
* 模型通过调用工具来获取实时数据。
|
|
13
|
+
*/
|
|
14
|
+
function handleStatusCommand() {
|
|
15
|
+
return { instruction: [
|
|
16
|
+
"# 系统状态仪表盘",
|
|
17
|
+
"",
|
|
18
|
+
"请检查以下各项系统健康指标,并使用工具获取实时数据,然后以仪表盘格式输出。",
|
|
19
|
+
"",
|
|
20
|
+
"## 检查清单",
|
|
21
|
+
"",
|
|
22
|
+
"1. **MCP 服务器连接**",
|
|
23
|
+
" - 列出所有已配置的 MCP 服务器及其连接状态",
|
|
24
|
+
" - 状态分三类:已连接 / 已断开 / 错误",
|
|
25
|
+
" - 如果存在错误,检查 ~/.lynx/logs 目录下最近的错误日志",
|
|
26
|
+
"",
|
|
27
|
+
"2. **Channel 适配器**",
|
|
28
|
+
" - 列出所有已注册的 channel 适配器(如 feishu、cli 等)",
|
|
29
|
+
" - 报告每个适配器的运行状态(活跃 / 已停止 / 出错)",
|
|
30
|
+
"",
|
|
31
|
+
"3. **Agent 引擎**",
|
|
32
|
+
" - 检查 Agent 引擎是否正常运行",
|
|
33
|
+
" - 当前活跃的会话数量",
|
|
34
|
+
" - 后台任务队列长度(如有)",
|
|
35
|
+
"",
|
|
36
|
+
"4. **磁盘使用**",
|
|
37
|
+
" - 检查 ~/.lynx 目录的总大小",
|
|
38
|
+
" - 列举各子目录(sessions / logs / heapdumps / exports 等)的大小",
|
|
39
|
+
" - 如果某个目录超过 100MB,给出清理建议",
|
|
40
|
+
"",
|
|
41
|
+
"5. **资源统计**",
|
|
42
|
+
" - Sessions 总数",
|
|
43
|
+
" - 已安装插件数量",
|
|
44
|
+
" - 内存使用(RSS)",
|
|
45
|
+
"",
|
|
46
|
+
"## 输出格式",
|
|
47
|
+
"",
|
|
48
|
+
"请用以下仪表盘格式输出(用中文):",
|
|
49
|
+
"",
|
|
50
|
+
"```",
|
|
51
|
+
"╔══════════════════════════════════╗",
|
|
52
|
+
"║ Lynx 系统状态仪表盘 ║",
|
|
53
|
+
"╠══════════════════════════════════╣",
|
|
54
|
+
"║ MCP 服务器 ║",
|
|
55
|
+
"║ server_a ● 已连接 ║",
|
|
56
|
+
"║ server_b ○ 已断开 ║",
|
|
57
|
+
"║ server_c ✕ 错误 ║",
|
|
58
|
+
"╠══════════════════════════════════╣",
|
|
59
|
+
"║ Channel 适配器 ║",
|
|
60
|
+
"║ feishu ● 活跃 ║",
|
|
61
|
+
"║ cli ● 活跃 ║",
|
|
62
|
+
"╠══════════════════════════════════╣",
|
|
63
|
+
"║ Agent 引擎 ║",
|
|
64
|
+
"║ 状态:运行中 ║",
|
|
65
|
+
"║ 活跃会话:3 ║",
|
|
66
|
+
"╠══════════════════════════════════╣",
|
|
67
|
+
"║ 磁盘使用 (~/.lynx) ║",
|
|
68
|
+
"║ 总大小:45.2 MB ║",
|
|
69
|
+
"║ sessions/ 23.1 MB ║",
|
|
70
|
+
"║ logs/ 12.0 MB ║",
|
|
71
|
+
"║ heapdumps/ 8.4 MB ║",
|
|
72
|
+
"║ exports/ 1.7 MB ║",
|
|
73
|
+
"╠══════════════════════════════════╣",
|
|
74
|
+
"║ 资源统计 ║",
|
|
75
|
+
"║ 会话总数:12 ║",
|
|
76
|
+
"║ 插件数量:5 ║",
|
|
77
|
+
"║ 内存使用:156 MB RSS ║",
|
|
78
|
+
"╚══════════════════════════════════╝",
|
|
79
|
+
"```",
|
|
80
|
+
"",
|
|
81
|
+
"## 注意事项",
|
|
82
|
+
"",
|
|
83
|
+
"- 所有数据必须通过实际工具调用获取,不要编造",
|
|
84
|
+
"- 磁盘大小使用 `du -sh` 或类似命令检查",
|
|
85
|
+
"- 如果某个检查项失败,在对应位置标明「无法获取」并尝试给出原因",
|
|
86
|
+
"- 对于任何异常状态,在仪表盘下方添加「建议操作」段落"
|
|
87
|
+
].join("\n") };
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { handleStatusCommand };
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=status-B3Tw-Ef4.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-B3Tw-Ef4.mjs","names":[],"sources":["../src/commands/status.ts"],"sourcesContent":["/**\n * /status — 系统健康仪表盘。\n *\n * 返回一段中文指令,引导模型检查 Lynx 系统的各项健康指标:\n * MCP 连接状态、Channel 适配器、Agent 引擎、磁盘用量等。\n */\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /status 命令。\n *\n * 生成一段中文指令,引导模型检查并报告系统各项健康指标。\n * 模型通过调用工具来获取实时数据。\n */\nexport function handleStatusCommand(): { instruction: string } {\n const lines: string[] = [\n \"# 系统状态仪表盘\",\n \"\",\n \"请检查以下各项系统健康指标,并使用工具获取实时数据,然后以仪表盘格式输出。\",\n \"\",\n \"## 检查清单\",\n \"\",\n \"1. **MCP 服务器连接**\",\n \" - 列出所有已配置的 MCP 服务器及其连接状态\",\n \" - 状态分三类:已连接 / 已断开 / 错误\",\n \" - 如果存在错误,检查 ~/.lynx/logs 目录下最近的错误日志\",\n \"\",\n \"2. **Channel 适配器**\",\n \" - 列出所有已注册的 channel 适配器(如 feishu、cli 等)\",\n \" - 报告每个适配器的运行状态(活跃 / 已停止 / 出错)\",\n \"\",\n \"3. **Agent 引擎**\",\n \" - 检查 Agent 引擎是否正常运行\",\n \" - 当前活跃的会话数量\",\n \" - 后台任务队列长度(如有)\",\n \"\",\n \"4. **磁盘使用**\",\n \" - 检查 ~/.lynx 目录的总大小\",\n \" - 列举各子目录(sessions / logs / heapdumps / exports 等)的大小\",\n \" - 如果某个目录超过 100MB,给出清理建议\",\n \"\",\n \"5. **资源统计**\",\n \" - Sessions 总数\",\n \" - 已安装插件数量\",\n \" - 内存使用(RSS)\",\n \"\",\n \"## 输出格式\",\n \"\",\n \"请用以下仪表盘格式输出(用中文):\",\n \"\",\n \"```\",\n \"╔══════════════════════════════════╗\",\n \"║ Lynx 系统状态仪表盘 ║\",\n \"╠══════════════════════════════════╣\",\n \"║ MCP 服务器 ║\",\n \"║ server_a ● 已连接 ║\",\n \"║ server_b ○ 已断开 ║\",\n \"║ server_c ✕ 错误 ║\",\n \"╠══════════════════════════════════╣\",\n \"║ Channel 适配器 ║\",\n \"║ feishu ● 活跃 ║\",\n \"║ cli ● 活跃 ║\",\n \"╠══════════════════════════════════╣\",\n \"║ Agent 引擎 ║\",\n \"║ 状态:运行中 ║\",\n \"║ 活跃会话:3 ║\",\n \"╠══════════════════════════════════╣\",\n \"║ 磁盘使用 (~/.lynx) ║\",\n \"║ 总大小:45.2 MB ║\",\n \"║ sessions/ 23.1 MB ║\",\n \"║ logs/ 12.0 MB ║\",\n \"║ heapdumps/ 8.4 MB ║\",\n \"║ exports/ 1.7 MB ║\",\n \"╠══════════════════════════════════╣\",\n \"║ 资源统计 ║\",\n \"║ 会话总数:12 ║\",\n \"║ 插件数量:5 ║\",\n \"║ 内存使用:156 MB RSS ║\",\n \"╚══════════════════════════════════╝\",\n \"```\",\n \"\",\n \"## 注意事项\",\n \"\",\n \"- 所有数据必须通过实际工具调用获取,不要编造\",\n \"- 磁盘大小使用 `du -sh` 或类似命令检查\",\n \"- 如果某个检查项失败,在对应位置标明「无法获取」并尝试给出原因\",\n \"- 对于任何异常状态,在仪表盘下方添加「建议操作」段落\",\n ];\n\n return { instruction: lines.join(\"\\n\") };\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,SAAgB,sBAA+C;CA2E7D,OAAO,EAAE,aAAa;EAzEpB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAGwB,CAAC,CAAC,KAAK,IAAI,EAAE;AACzC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
//#region src/commands/upgrade.ts
|
|
3
|
+
/**
|
|
4
|
+
* /upgrade — 自助升级 Lynx 到最新版本。
|
|
5
|
+
*
|
|
6
|
+
* 支持两种模式:
|
|
7
|
+
* - --check:检查依赖更新(运行 pnpm outdated)
|
|
8
|
+
* - 默认:显示升级步骤,引导用户手动执行
|
|
9
|
+
*
|
|
10
|
+
* 由于升级涉及 git pull + pnpm install + pnpm build,
|
|
11
|
+
* 直接在本地执行比通过模型指令更可靠。
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* 处理 /upgrade 命令。
|
|
15
|
+
*
|
|
16
|
+
* --check 模式运行 pnpm outdated 检查可用更新。
|
|
17
|
+
* 默认模式显示手动升级步骤。
|
|
18
|
+
*/
|
|
19
|
+
function handleUpgradeCommand(args) {
|
|
20
|
+
if (args.check) return handleCheck();
|
|
21
|
+
return { output: [
|
|
22
|
+
"【Lynx 升级指南】",
|
|
23
|
+
"",
|
|
24
|
+
"请按以下步骤手动升级:",
|
|
25
|
+
"",
|
|
26
|
+
"1. 拉取最新代码",
|
|
27
|
+
" git pull",
|
|
28
|
+
"",
|
|
29
|
+
"2. 安装依赖(如有更新)",
|
|
30
|
+
" pnpm install --no-frozen-lockfile",
|
|
31
|
+
"",
|
|
32
|
+
"3. 重新构建",
|
|
33
|
+
" pnpm build",
|
|
34
|
+
"",
|
|
35
|
+
"4. 验证升级",
|
|
36
|
+
" node lynx.mjs --version",
|
|
37
|
+
"",
|
|
38
|
+
"提示:",
|
|
39
|
+
"- 升级前建议先 git stash 暂存当前修改",
|
|
40
|
+
"- 使用 lynx upgrade --check 可检查依赖是否有可用更新",
|
|
41
|
+
"- 如果升级后出现问题,可 git reset --hard HEAD~1 回退"
|
|
42
|
+
].join("\n") };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 运行 pnpm outdated 检查过期的依赖。
|
|
46
|
+
* 超时 30 秒,避免在网络慢时长时间阻塞。
|
|
47
|
+
*/
|
|
48
|
+
function handleCheck() {
|
|
49
|
+
try {
|
|
50
|
+
const output = execSync("pnpm outdated", {
|
|
51
|
+
cwd: process.cwd(),
|
|
52
|
+
encoding: "utf-8",
|
|
53
|
+
timeout: 3e4,
|
|
54
|
+
stdio: [
|
|
55
|
+
"ignore",
|
|
56
|
+
"pipe",
|
|
57
|
+
"pipe"
|
|
58
|
+
]
|
|
59
|
+
}).trim();
|
|
60
|
+
if (!output) return { output: "所有依赖均为最新版本。" };
|
|
61
|
+
return { output: "以下依赖有可用更新:\n\n" + output };
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const exitCode = err.status;
|
|
64
|
+
const stderr = err.stderr ?? "";
|
|
65
|
+
if (exitCode === 1 && !stderr) return { output: "所有依赖均为最新版本。" };
|
|
66
|
+
return { output: "检查更新失败:" + (err instanceof Error ? err.message : String(err)) + "\n\n请确认当前目录是 Lynx 项目根目录,且 pnpm 已正确安装。" };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { handleUpgradeCommand };
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=upgrade-CREWRNeC.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade-CREWRNeC.mjs","names":[],"sources":["../src/commands/upgrade.ts"],"sourcesContent":["/**\n * /upgrade — 自助升级 Lynx 到最新版本。\n *\n * 支持两种模式:\n * - --check:检查依赖更新(运行 pnpm outdated)\n * - 默认:显示升级步骤,引导用户手动执行\n *\n * 由于升级涉及 git pull + pnpm install + pnpm build,\n * 直接在本地执行比通过模型指令更可靠。\n */\n\nimport { execSync } from \"node:child_process\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface UpgradeCommandArgs {\n /** 是否只检查更新而不执行升级。 */\n check?: boolean;\n /** 目标版本(暂未实现,预留)。 */\n version?: string;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 处理 /upgrade 命令。\n *\n * --check 模式运行 pnpm outdated 检查可用更新。\n * 默认模式显示手动升级步骤。\n */\nexport function handleUpgradeCommand(args: UpgradeCommandArgs): { output: string } {\n if (args.check) {\n return handleCheck();\n }\n\n const lines: string[] = [\n \"【Lynx 升级指南】\",\n \"\",\n \"请按以下步骤手动升级:\",\n \"\",\n \"1. 拉取最新代码\",\n \" git pull\",\n \"\",\n \"2. 安装依赖(如有更新)\",\n \" pnpm install --no-frozen-lockfile\",\n \"\",\n \"3. 重新构建\",\n \" pnpm build\",\n \"\",\n \"4. 验证升级\",\n \" node lynx.mjs --version\",\n \"\",\n \"提示:\",\n \"- 升级前建议先 git stash 暂存当前修改\",\n \"- 使用 lynx upgrade --check 可检查依赖是否有可用更新\",\n \"- 如果升级后出现问题,可 git reset --hard HEAD~1 回退\",\n ];\n\n return { output: lines.join(\"\\n\") };\n}\n\n// ── Helpers ──────────────────────────────────────────\n\n/**\n * 运行 pnpm outdated 检查过期的依赖。\n * 超时 30 秒,避免在网络慢时长时间阻塞。\n */\nfunction handleCheck(): { output: string } {\n try {\n const result = execSync(\"pnpm outdated\", {\n cwd: process.cwd(),\n encoding: \"utf-8\",\n timeout: 30_000,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n const output = result.trim();\n if (!output) {\n return { output: \"所有依赖均为最新版本。\" };\n }\n return { output: \"以下依赖有可用更新:\\n\\n\" + output };\n } catch (err) {\n // pnpm outdated 在没有过期包时返回非零退出码\n const exitCode = (err as { status?: number }).status;\n const stderr = (err as { stderr?: string }).stderr ?? \"\";\n\n if (exitCode === 1 && !stderr) {\n return { output: \"所有依赖均为最新版本。\" };\n }\n\n const message = err instanceof Error ? err.message : String(err);\n return {\n output:\n \"检查更新失败:\" + message + \"\\n\\n请确认当前目录是 Lynx 项目根目录,且 pnpm 已正确安装。\",\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA8BA,SAAgB,qBAAqB,MAA8C;CACjF,IAAI,KAAK,OACP,OAAO,YAAY;CA0BrB,OAAO,EAAE,QAAQ;EAtBf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAGmB,CAAC,CAAC,KAAK,IAAI,EAAE;AACpC;;;;;AAQA,SAAS,cAAkC;CACzC,IAAI;EAOF,MAAM,SANS,SAAS,iBAAiB;GACvC,KAAK,QAAQ,IAAI;GACjB,UAAU;GACV,SAAS;GACT,OAAO;IAAC;IAAU;IAAQ;GAAM;EAClC,CACoB,CAAC,CAAC,KAAK;EAC3B,IAAI,CAAC,QACH,OAAO,EAAE,QAAQ,cAAc;EAEjC,OAAO,EAAE,QAAQ,mBAAmB,OAAO;CAC7C,SAAS,KAAK;EAEZ,MAAM,WAAY,IAA4B;EAC9C,MAAM,SAAU,IAA4B,UAAU;EAEtD,IAAI,aAAa,KAAK,CAAC,QACrB,OAAO,EAAE,QAAQ,cAAc;EAIjC,OAAO,EACL,QACE,aAHY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,KAGrC,wCAC1B;CACF;AACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@24klynx/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI entry point — bootstrap, command catalog, crestodian, doctor",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.mts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"types": "./dist/index.d.mts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"commander": "^13.1.0",
|
|
16
|
+
"ink": "^6.8.0",
|
|
17
|
+
"react": "^19.2.0",
|
|
18
|
+
"@24klynx/agent": "0.1.0",
|
|
19
|
+
"@24klynx/channels": "0.1.0",
|
|
20
|
+
"@24klynx/core": "0.1.0",
|
|
21
|
+
"@24klynx/permissions": "0.1.0",
|
|
22
|
+
"@24klynx/llm": "0.1.0",
|
|
23
|
+
"@24klynx/plugins": "0.1.0",
|
|
24
|
+
"@24klynx/tui": "0.1.0",
|
|
25
|
+
"@24klynx/tools": "0.1.0",
|
|
26
|
+
"@24klynx/session": "0.1.0"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsdown --config-loader tsx",
|
|
36
|
+
"test": "vitest run --passWithNoTests",
|
|
37
|
+
"typecheck": "tsgo --noEmit"
|
|
38
|
+
}
|
|
39
|
+
}
|