Search
Project
选择一个本地目录,Nano 才能读取文件、执行命令和维护上下文。
Plugins
截图后显示当前桌面
动作完成后会自动截图验证,高风险操作需要确认。
commit aafc3f5c6be849e344ec064b19545954de5b6b0e Author: WpyQwq <3911625973@qq.com> Date: Sat Sep 19 11:58:56 2026 +0800 Initial commit: Nano Agent:Windows 优先、本地优先的 Codex 风格桌面智能体 MVP diff --git a/.qoder/repowiki/wiki_plan.yaml b/.qoder/repowiki/wiki_plan.yaml new file mode 100644 index 0000000..a50d9c0 --- /dev/null +++ b/.qoder/repowiki/wiki_plan.yaml @@ -0,0 +1,47 @@ +version: 1 + +scope: + include: [] + exclude: + - "node_modules/**" + - "assets/**" + +repowiki: + template: architecture + notes: + - text: "本项目是 Windows 优先、本地优先的 Codex 风格桌面智能体 MVP,基于 Electron 41,无前端框架,全部使用原生 JS。" + - text: "支持 OpenAI Chat Completions 与 Anthropic Messages 双供应商兼容接口,通过统一 callProvider 抽象切换。" + - text: "主进程 main.js 承载 Agent 工具循环(最多 8 步)和 Windows 原生电脑操控(截图、鼠标、键盘、窗口列表),通过 PowerShell + Win32 P/Invoke 实现。" + documents: + - title: "主进程与 Agent 循环" + goal: "分析 main.js 中的应用入口、IPC 注册、Agent 工具循环流程、双供应商调用抽象和权限控制机制。" + parent: "" + hints: + - "重点描述 runAgentLoop 的步进逻辑与 approval 中断机制" + - "说明 callProvider 对 OpenAI / Anthropic 的请求构造差异" + - title: "Windows 电脑操控层" + goal: "梳理 main.js 中截图、鼠标、键盘、滚轮、窗口列表等系统级能力的实现方式与安全边界。" + parent: "" + hints: + - "PowerShell 脚本 + Win32 P/Invoke 的调用链路" + - "protectedTool 权限门控逻辑" + - title: "Preload 桥接层" + goal: "说明 preload.js 如何通过 contextBridge 向渲染进程安全暴露 IPC 接口。" + parent: "" + - title: "渲染进程与 UI 逻辑" + goal: "分析 src/renderer.js 中的任务管理、对话流、Composer 交互、设置面板和电脑操控面板的状态管理与事件驱动。" + parent: "" + hints: + - "无框架,纯 DOM 操作 + 事件监听" + - "agent:event 流式消息的接收与渲染" + - title: "视图结构与样式体系" + goal: "描述 src/index.html 的页面骨架和 src/styles.css 的深色主题设计系统。" + parent: "" + hints: + - "自定义标题栏、侧边栏、对话区、Composer 布局" + - "CSS 变量与深色主题约定" + +knowledgecard: + notes: + - text: "模块划分建议:主进程(Agent 循环 + 系统交互)、Preload 桥接、渲染进程(UI + 状态)、样式层。" + - text: "本项目无任何第三方运行时依赖(仅 devDependency electron),所有功能均为手写实现,知识卡应突出各模块的手写约定与接口契约。" diff --git a/README.md b/README.md new file mode 100644 index 0000000..604d22b --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Nano Agent + +一个 Windows 优先、本地优先的 Codex 风格智能体工作台 MVP。 + +## 当前已实现 + +- Codex 风格深色桌面 UI。 +- 任务列表、欢迎页、任务对话和底部 Composer。 +- 项目目录选择。 +- OpenAI Chat Completions 兼容请求。 +- Anthropic Messages 兼容请求。 +- 演示模式,无 API Key 时也可以打开和体验界面。 +- Windows 原生截图。 +- Windows 原生鼠标移动、点击、滚轮和文本输入基础接口。 +- 当前窗口列表。 +- 电脑操控面板和危险点击确认。 +- 本地设置保存。 + +## 启动 + +```powershell +cd D:\Nano +npm install +npm start +``` + +如果 Electron 二进制下载因网络原因超时,可先确认本机已存在: + +```powershell +Test-Path .\node_modules\electron\dist\electron.exe +``` + +## 使用真实模型 + +打开“设置”,配置: + +- 供应商:OpenAI 兼容或 Anthropic 兼容。 +- 模型名称。 +- API Base URL。 +- API Key。 + +当前 MVP 在本地设置中保存配置。正式版本需要替换为 Windows Credential Manager 加密存储。 + +## 下一步开发重点 + +1. 独立感知层:Windows UI Automation、浏览器 DOM、OCR、可选视觉模型。 +2. 统一 Computer Controller:observe、locate、checkPolicy、execute、verify。 +3. 文件读取、搜索、Diff 和命令执行工具接入真实智能体循环。 +4. Git 分支、提交、回滚和测试修复闭环。 +5. MCP / 插件系统和后台任务。 diff --git a/assets/dark-sidebar-check.png b/assets/dark-sidebar-check.png new file mode 100644 index 0000000..4ea1c0c Binary files /dev/null and b/assets/dark-sidebar-check.png differ diff --git a/assets/design-md-pass.png b/assets/design-md-pass.png new file mode 100644 index 0000000..add1f71 Binary files /dev/null and b/assets/design-md-pass.png differ diff --git a/assets/final-dark-sidebar.png b/assets/final-dark-sidebar.png new file mode 100644 index 0000000..3a868e2 Binary files /dev/null and b/assets/final-dark-sidebar.png differ diff --git a/assets/first-run.png b/assets/first-run.png new file mode 100644 index 0000000..69094eb Binary files /dev/null and b/assets/first-run.png differ diff --git a/assets/polished-run.png b/assets/polished-run.png new file mode 100644 index 0000000..47783a7 Binary files /dev/null and b/assets/polished-run.png differ diff --git a/assets/reference-match.png b/assets/reference-match.png new file mode 100644 index 0000000..5e3f7ef Binary files /dev/null and b/assets/reference-match.png differ diff --git a/assets/rewrite-final.png b/assets/rewrite-final.png new file mode 100644 index 0000000..8ad492a Binary files /dev/null and b/assets/rewrite-final.png differ diff --git a/assets/rewrite-pass-2.png b/assets/rewrite-pass-2.png new file mode 100644 index 0000000..d18d257 Binary files /dev/null and b/assets/rewrite-pass-2.png differ diff --git a/assets/rewrite-pass.png b/assets/rewrite-pass.png new file mode 100644 index 0000000..81f9abb Binary files /dev/null and b/assets/rewrite-pass.png differ diff --git a/assets/rounded-shell.png b/assets/rounded-shell.png new file mode 100644 index 0000000..a59e5dc Binary files /dev/null and b/assets/rounded-shell.png differ diff --git a/assets/task-view-max.png b/assets/task-view-max.png new file mode 100644 index 0000000..d74a214 Binary files /dev/null and b/assets/task-view-max.png differ diff --git a/assets/task-view-single.png b/assets/task-view-single.png new file mode 100644 index 0000000..a6cdee5 Binary files /dev/null and b/assets/task-view-single.png differ diff --git a/assets/task-view.png b/assets/task-view.png new file mode 100644 index 0000000..1db3f75 Binary files /dev/null and b/assets/task-view.png differ diff --git a/main.js b/main.js new file mode 100644 index 0000000..4456fcb --- /dev/null +++ b/main.js @@ -0,0 +1,327 @@ +const { app, BrowserWindow, ipcMain, dialog, shell, screen } = require('electron'); +const path = require('node:path'); +const fs = require('node:fs/promises'); +const os = require('node:os'); +const { execFile } = require('node:child_process'); + +let mainWindow; + +function projectPathOrThrow(projectPath) { + if (!projectPath || typeof projectPath !== 'string') throw new Error('未选择项目目录'); + return path.resolve(projectPath); +} + +function isIgnored(name) { + return new Set(['.git', 'node_modules', 'dist', 'build', '.next', 'target']).has(name); +} + +async function walkFiles(root, current = root, result = []) { + if (result.length >= 500) return result; + const entries = await fs.readdir(current, { withFileTypes: true }); + for (const entry of entries) { + if (isIgnored(entry.name)) continue; + const absolute = path.join(current, entry.name); + if (entry.isDirectory()) await walkFiles(root, absolute, result); + else result.push(path.relative(root, absolute)); + if (result.length >= 500) break; + } + return result; +} + +async function projectFiles(projectPath) { + const root = projectPathOrThrow(projectPath); + const files = await walkFiles(root); + return { root, files }; +} + +function safeProjectFile(projectPath, relativePath) { + const root = projectPathOrThrow(projectPath); + const target = path.resolve(root, relativePath || ''); + if (target !== root && !target.startsWith(`${root}${path.sep}`)) throw new Error('路径超出项目目录范围'); + return { root, target }; +} + +async function searchProject(projectPath, query) { + const root = projectPathOrThrow(projectPath); + const files = await walkFiles(root); + const results = []; + for (const relative of files) { + if (results.length >= 100) break; + const absolute = path.join(root, relative); + try { + const content = await fs.readFile(absolute, 'utf8'); + const lines = content.split(/\r?\n/); + lines.forEach((line, index) => { + if (results.length < 100 && line.toLowerCase().includes(String(query || '').toLowerCase())) results.push({ file: relative, line: index + 1, text: line.trim().slice(0, 240) }); + }); + } catch { /* binary or unreadable file */ } + } + return results; +} + +function runCommand(command, cwd) { + return new Promise((resolve, reject) => { + execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', command], { cwd: projectPathOrThrow(cwd), windowsHide: true, maxBuffer: 8 * 1024 * 1024, timeout: 120000 }, (error, stdout, stderr) => { + if (error) return reject(new Error(stderr.trim() || stdout.trim() || error.message)); + resolve({ stdout, stderr, code: 0 }); + }); + }); +} + +function runPowerShell(script) { + return new Promise((resolve, reject) => { + execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], + { windowsHide: true, maxBuffer: 20 * 1024 * 1024 }, + (error, stdout, stderr) => { + if (error) return reject(new Error(stderr.trim() || error.message)); + resolve(stdout.trim()); + }); + }); +} + +async function takeScreenshot() { + const output = path.join(os.tmpdir(), 'nano-agent-screen.png'); + const escaped = output.replace(/'/g, "''"); + const script = ` +Add-Type -AssemblyName System.Drawing +Add-Type -AssemblyName System.Windows.Forms +$bounds = [System.Windows.Forms.SystemInformation]::VirtualScreen +$bitmap = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height +$graphics = [System.Drawing.Graphics]::FromImage($bitmap) +$graphics.CopyFromScreen($bounds.Left, $bounds.Top, 0, 0, $bitmap.Size) +$bitmap.Save('${escaped}', [System.Drawing.Imaging.ImageFormat]::Png) +$graphics.Dispose(); $bitmap.Dispose() +Write-Output '${escaped}' +`; + await runPowerShell(script); + const data = await fs.readFile(output); + return { path: output, dataUrl: `data:image/png;base64,${data.toString('base64')}` }; +} + +async function computerAction(action) { + const type = action?.type; + if (type === 'screenshot') return takeScreenshot(); + + const x = Number.isFinite(action?.x) ? Math.round(action.x) : 0; + const y = Number.isFinite(action?.y) ? Math.round(action.y) : 0; + const buttonMap = { left: 0x0002 | 0x0004, right: 0x0008 | 0x0010, middle: 0x0020 | 0x0040 }; + const button = buttonMap[action?.button || 'left'] || buttonMap.left; + const keyMap = { ENTER: 0x0D, ESC: 0x1B, TAB: 0x09, CTRL: 0x11, SHIFT: 0x10, ALT: 0x12, BACKSPACE: 0x08, DELETE: 0x2E, SPACE: 0x20 }; + const keys = (action?.keys || []).map((key) => keyMap[String(key).toUpperCase()] || String(key).charCodeAt(0)).filter(Boolean); + const text = String(action?.text || '').replace(/'/g, "''"); + + let body = ''; + if (type === 'mouse_move') { + body = `[Win32]::SetCursorPos(${x}, ${y}) | Out-Null`; + } else if (type === 'mouse_click') { + body = `[Win32]::SetCursorPos(${x}, ${y}) | Out-Null; [Win32]::mouse_event(${button}, 0, 0, 0, 0)`; + } else if (type === 'scroll') { + const amount = Math.round(Number(action.amount || 1) * 120); + body = `[Win32]::mouse_event(0x0800, 0, 0, ${amount}, 0)`; + } else if (type === 'key_press') { + body = keys.map((code) => `[Win32]::keybd_event(${code}, 0, 0, 0); [Win32]::keybd_event(${code}, 0, 2, 0)`).join(';'); + } else if (type === 'type') { + body = `Set-Clipboard -Value '${text}'; [Win32]::keybd_event(0x11,0,0,0); [Win32]::keybd_event(0x56,0,0,0); [Win32]::keybd_event(0x56,0,2,0); [Win32]::keybd_event(0x11,0,2,0)`; + } else { + throw new Error(`Unsupported computer action: ${type}`); + } + + const script = ` +Add-Type @' +using System; +using System.Runtime.InteropServices; +public static class Win32 { + [DllImport("user32.dll")] public static extern bool SetCursorPos(int X, int Y); + [DllImport("user32.dll")] public static extern void mouse_event(uint flags, uint dx, uint dy, uint data, UIntPtr extraInfo); + [DllImport("user32.dll")] public static extern void keybd_event(byte key, byte scan, uint flags, UIntPtr extraInfo); +} +'@ +${body} +Write-Output 'ok' +`; + await runPowerShell(script); + return { ok: true, type }; +} + +async function listWindows() { + const script = `Get-Process | Where-Object { $_.MainWindowTitle -and $_.MainWindowTitle.Trim() } | Select-Object Id, ProcessName, MainWindowTitle | ConvertTo-Json -Compress`; + const output = await runPowerShell(script); + if (!output) return []; + const parsed = JSON.parse(output); + return Array.isArray(parsed) ? parsed : [parsed]; +} + +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1300, + height: 840, + minWidth: 1120, + minHeight: 720, + show: false, + backgroundColor: '#00000000', + transparent: true, + hasShadow: true, + titleBarStyle: 'hidden', + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + contextIsolation: true, + nodeIntegration: false + } + }); + mainWindow.loadFile(path.join(__dirname, 'src', 'index.html')); + mainWindow.once('ready-to-show', () => mainWindow.show()); +} + +const AGENT_TOOLS = [ + { type: 'function', function: { name: 'list_files', description: '列出项目目录中的文件。', parameters: { type: 'object', properties: { depth: { type: 'number', description: '最大递归深度,默认 3。' } } } } }, + { type: 'function', function: { name: 'read_file', description: '读取项目中的一个文本文件。', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } }, + { type: 'function', function: { name: 'search_text', description: '在项目文件中搜索文本。', parameters: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] } } }, + { type: 'function', function: { name: 'write_file', description: '创建或修改项目文件。执行前必须确认用户授权。', parameters: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' } }, required: ['path', 'content'] } } }, + { type: 'function', function: { name: 'run_command', description: '在项目目录执行 PowerShell 命令。执行前必须确认用户授权。', parameters: { type: 'object', properties: { command: { type: 'string' } }, required: ['command'] } } }, + { type: 'function', function: { name: 'computer_screenshot', description: '获取当前 Windows 屏幕截图,用于观察界面。', parameters: { type: 'object', properties: {} } } }, + { type: 'function', function: { name: 'computer_windows', description: '获取当前桌面窗口列表。', parameters: { type: 'object', properties: {} } } }, + { type: 'function', function: { name: 'computer_action', description: '执行鼠标、键盘或滚轮动作。执行前必须确认用户授权。', parameters: { type: 'object', properties: { type: { type: 'string' }, x: { type: 'number' }, y: { type: 'number' }, button: { type: 'string' }, text: { type: 'string' }, keys: { type: 'array', items: { type: 'string' } }, amount: { type: 'number' } }, required: ['type'] } } } +]; + +async function callProvider(config, messages, tools = AGENT_TOOLS) { + if (!config?.apiKey || !config?.model) { + return { text: '演示模式已启用。请在设置中配置 API Base URL、API Key 和模型,即可连接真实模型。当前界面、任务流和电脑操控基础能力已经可用。', toolCalls: [] }; + } + const provider = config.provider || 'openai'; + const baseUrl = (config.baseUrl || (provider === 'anthropic' ? 'https://api.anthropic.com' : 'https://api.openai.com/v1')).replace(/\/$/, ''); + let url; + let headers; + let body; + if (provider === 'anthropic') { + url = `${baseUrl}/v1/messages`; + headers = { 'content-type': 'application/json', 'x-api-key': config.apiKey, 'anthropic-version': '2023-06-01' }; + const system = messages.find((message) => message.role === 'system')?.content; + body = { model: config.model, max_tokens: config.maxOutputTokens || 4096, system, messages: messages.filter((message) => message.role !== 'system'), tools: tools.map((tool) => ({ name: tool.function.name, description: tool.function.description, input_schema: tool.function.parameters })) }; + } else { + url = `${baseUrl}/chat/completions`; + headers = { 'content-type': 'application/json', authorization: `Bearer ${config.apiKey}` }; + body = { model: config.model, messages, tools, tool_choice: 'auto', stream: false, temperature: config.temperature ?? 0.2 }; + } + const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body) }); + const raw = await response.text(); + if (!response.ok) throw new Error(`${response.status}: ${raw.slice(0, 500)}`); + const data = JSON.parse(raw); + if (provider === 'anthropic') { + return { text: data.content?.filter((item) => item.type === 'text').map((item) => item.text || '').join('') || '', toolCalls: data.content?.filter((item) => item.type === 'tool_use').map((item) => ({ id: item.id, name: item.name, input: item.input, raw: item })) || [], assistantContent: data.content || [] }; + } + const message = data.choices?.[0]?.message || {}; + return { text: message.content || '', toolCalls: (message.tool_calls || []).map((call) => ({ id: call.id, name: call.function.name, input: JSON.parse(call.function.arguments || '{}'), raw: call })), assistantMessage: message }; +} + +async function executeAgentTool(name, input, payload) { + const projectPath = payload.projectPath; + if (name === 'list_files') return projectFiles(projectPath); + if (name === 'read_file') return projectReadFile(projectPath, input.path); + if (name === 'search_text') return searchProject(projectPath, input.query); + if (name === 'write_file') return projectWriteFile(projectPath, input.path, input.content); + if (name === 'run_command') return runCommand(input.command, projectPath); + if (name === 'computer_screenshot') { const result = await takeScreenshot(); return { path: result.path, note: '截图已生成;支持视觉输入的模型可以继续分析截图。' }; } + if (name === 'computer_windows') return listWindows(); + if (name === 'computer_action') return computerAction(input); + throw new Error(`未知工具:${name}`); +} + +async function projectReadFile(projectPath, relativePath) { + const { target } = safeProjectFile(projectPath, relativePath); + return { path: relativePath, content: await fs.readFile(target, 'utf8') }; +} + +async function projectWriteFile(projectPath, relativePath, content) { + const { target } = safeProjectFile(projectPath, relativePath); + await fs.mkdir(path.dirname(target), { recursive: true }); + await fs.writeFile(target, String(content || ''), 'utf8'); + return { ok: true, path: relativePath }; +} + +async function runAgentLoop(event, payload) { + const messages = [...payload.messages]; + for (let step = 0; step < 8; step += 1) { + const response = await callProvider(payload.config, messages); + if (response.text) { + for (const chunk of response.text.match(/.{1,18}/gs) || [response.text]) { + event.sender.send('agent:event', { type: 'message.delta', content: chunk }); + await new Promise((resolve) => setTimeout(resolve, 14)); + } + } + if (!response.toolCalls.length) return; + + if (payload.config?.provider === 'anthropic') { + messages.push({ role: 'assistant', content: response.assistantContent }); + } else { + messages.push({ role: 'assistant', content: response.assistantMessage.content || null, tool_calls: response.assistantMessage.tool_calls }); + } + + for (const toolCall of response.toolCalls) { + event.sender.send('agent:event', { type: 'tool.started', callId: toolCall.id, name: toolCall.name, input: toolCall.input }); + const protectedTool = new Set(['write_file', 'run_command', 'computer_action']).has(toolCall.name); + if (protectedTool && payload.permissionMode !== 'full') { + event.sender.send('agent:event', { type: 'approval.required', approvalId: toolCall.id, reason: `工具 ${toolCall.name} 需要完全访问权限` }); + return; + } + let result; + try { + result = await executeAgentTool(toolCall.name, toolCall.input || {}, payload); + } catch (error) { + result = { error: error.message }; + } + event.sender.send('agent:event', { type: 'tool.completed', callId: toolCall.id, name: toolCall.name, output: result }); + if (toolCall.name === 'write_file' && result.path) event.sender.send('agent:event', { type: 'file.changed', path: result.path }); + if (payload.config?.provider === 'anthropic') { + messages.push({ role: 'user', content: [{ type: 'tool_result', tool_use_id: toolCall.id, content: JSON.stringify(result) }] }); + } else { + messages.push({ role: 'tool', tool_call_id: toolCall.id, content: JSON.stringify(result) }); + } + } + } + throw new Error('智能体工具循环超过最大步骤数'); +} + +ipcMain.handle('app:info', () => ({ version: app.getVersion(), platform: process.platform, displays: screen.getAllDisplays().length })); +ipcMain.handle('window:control', (_event, action) => { + if (!mainWindow) return; + if (action === 'minimize') mainWindow.minimize(); + if (action === 'maximize') mainWindow.isMaximized() ? mainWindow.unmaximize() : mainWindow.maximize(); + if (action === 'close') mainWindow.close(); +}); +ipcMain.handle('project:choose', async () => { + const result = await dialog.showOpenDialog(mainWindow, { properties: ['openDirectory'] }); + return result.canceled ? null : result.filePaths[0]; +}); +ipcMain.handle('project:files', (_event, projectPath) => projectFiles(projectPath)); +ipcMain.handle('project:read', async (_event, payload) => { + const { target } = safeProjectFile(payload.projectPath, payload.relativePath); + return { path: payload.relativePath, content: await fs.readFile(target, 'utf8') }; +}); +ipcMain.handle('project:search', (_event, payload) => searchProject(payload.projectPath, payload.query)); +ipcMain.handle('project:write', async (_event, payload) => { + const { target } = safeProjectFile(payload.projectPath, payload.relativePath); + await fs.mkdir(path.dirname(target), { recursive: true }); + await fs.writeFile(target, String(payload.content || ''), 'utf8'); + return { ok: true, path: payload.relativePath }; +}); +ipcMain.handle('shell:run', (_event, payload) => runCommand(payload.command, payload.projectPath)); +ipcMain.handle('shell:open', (_event, target) => shell.openPath(target)); +ipcMain.handle('computer:action', (_event, action) => computerAction(action)); +ipcMain.handle('computer:windows', () => listWindows()); +ipcMain.handle('agent:run', async (event, payload) => { + try { + event.sender.send('agent:event', { type: 'run.started' }); + await runAgentLoop(event, payload); + event.sender.send('agent:event', { type: 'run.completed', status: 'success' }); + return { ok: true }; + } catch (error) { + event.sender.send('agent:event', { type: 'run.error', message: error.message }); + return { ok: false, error: error.message }; + } +}); + +app.whenReady().then(() => { + createWindow(); + app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); +}); +app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..dd64890 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,871 @@ +{ + "name": "nano-agent", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nano-agent", + "version": "0.1.0", + "devDependencies": { + "electron": "^36.0.0" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/electron": { + "version": "36.9.5", + "resolved": "https://registry.npmjs.org/electron/-/electron-36.9.5.tgz", + "integrity": "sha512-1UCss2IqxqujSzg/2jkRjuiT3G+EEXgd6UKB5kUekwQW1LJ6d4QCr8YItfC3Rr9VIGRDJ29eOERmnRNO1Eh+NA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^22.7.7", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4224d3c --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "nano-agent", + "version": "0.1.0", + "description": "Codex-style local-first desktop agent for Windows", + "main": "main.js", + "private": true, + "scripts": { + "start": "electron .", + "check": "node --check main.js && node --check preload.js && node --check src/renderer.js" + }, + "devDependencies": { + "electron": "^41.0.2" + } +} diff --git a/preload.js b/preload.js new file mode 100644 index 0000000..f72cc4f --- /dev/null +++ b/preload.js @@ -0,0 +1,21 @@ +const { contextBridge, ipcRenderer } = require('electron'); + +contextBridge.exposeInMainWorld('nano', { + appInfo: () => ipcRenderer.invoke('app:info'), + windowControl: (action) => ipcRenderer.invoke('window:control', action), + chooseProject: () => ipcRenderer.invoke('project:choose'), + listProjectFiles: (projectPath) => ipcRenderer.invoke('project:files', projectPath), + readProjectFile: (payload) => ipcRenderer.invoke('project:read', payload), + searchProject: (payload) => ipcRenderer.invoke('project:search', payload), + writeProjectFile: (payload) => ipcRenderer.invoke('project:write', payload), + runCommand: (payload) => ipcRenderer.invoke('shell:run', payload), + openPath: (target) => ipcRenderer.invoke('shell:open', target), + computerAction: (action) => ipcRenderer.invoke('computer:action', action), + listWindows: () => ipcRenderer.invoke('computer:windows'), + runAgent: (payload) => ipcRenderer.invoke('agent:run', payload), + onAgentEvent: (callback) => { + const listener = (_event, data) => callback(data); + ipcRenderer.on('agent:event', listener); + return () => ipcRenderer.removeListener('agent:event', listener); + } +}); diff --git a/scripts/screenshot.ps1 b/scripts/screenshot.ps1 new file mode 100644 index 0000000..22a6e9b --- /dev/null +++ b/scripts/screenshot.ps1 @@ -0,0 +1,10 @@ +Add-Type -AssemblyName System.Drawing +Add-Type -AssemblyName System.Windows.Forms +$bounds = [System.Windows.Forms.SystemInformation]::VirtualScreen +$bitmap = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height +$graphics = [System.Drawing.Graphics]::FromImage($bitmap) +$graphics.CopyFromScreen($bounds.Left, $bounds.Top, 0, 0, $bitmap.Size) +$bitmap.Save('d:\Nano\assets\ui-redesign-v2.png', [System.Drawing.Imaging.ImageFormat]::Png) +$graphics.Dispose() +$bitmap.Dispose() +Write-Output 'done' diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..e7e0b56 --- /dev/null +++ b/src/index.html @@ -0,0 +1,107 @@ + + +
+ + +选择一个本地目录,Nano 才能读取文件、执行命令和维护上下文。
截图后显示当前桌面
动作完成后会自动截图验证,高风险操作需要确认。
告诉 Nano 目标,它可以理解项目、修改文件、执行命令,也能直接操作你的 Windows 桌面。