feihong-code 0.6.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,404 @@
1
+ /**
2
+ * 飞虹 Code Electron 桌面版主进程(简化稳定版)
3
+ * 晋江市飞虹智科技企业管理有限公司 · 飞扬企源研发中心 · 负责人:吴赐虹
4
+ */
5
+
6
+ const { app, BrowserWindow, shell, Tray, Menu, ipcMain, dialog, clipboard, Notification, session, desktopCapturer, screen } = require('electron');
7
+ const { spawn } = require('child_process');
8
+ const { existsSync } = require('fs');
9
+ const { join } = require('path');
10
+ const http = require('http');
11
+
12
+ // 禁用安全警告
13
+ process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
14
+
15
+ // 配置
16
+ const PORT = parseInt(process.env.FH_WEB_PORT || '8081');
17
+ const isDev = !app.isPackaged;
18
+
19
+ // 全局变量
20
+ let mainWindow = null;
21
+ let serverProcess = null;
22
+ let tray = null;
23
+
24
+ // 获取应用根目录
25
+ function getAppPath() {
26
+ if (isDev) return join(__dirname, '..');
27
+ return process.resourcesPath ? join(process.resourcesPath, 'app') : app.getAppPath();
28
+ }
29
+ const APP_PATH = getAppPath();
30
+
31
+ /**
32
+ * 等待服务器就绪
33
+ */
34
+ function waitForServer(port, timeoutMs) {
35
+ return new Promise((resolve, reject) => {
36
+ const startTime = Date.now();
37
+ const check = () => {
38
+ const req = http.get(`http://127.0.0.1:${port}/api/health`, (res) => {
39
+ if (res.statusCode === 200) {
40
+ resolve();
41
+ } else {
42
+ res.resume();
43
+ retry();
44
+ }
45
+ });
46
+ req.on('error', () => retry());
47
+ req.setTimeout(2000, () => { req.destroy(); retry(); });
48
+ };
49
+ const retry = () => {
50
+ if (Date.now() - startTime > timeoutMs) {
51
+ reject(new Error('服务器启动超时'));
52
+ } else {
53
+ setTimeout(check, 500);
54
+ }
55
+ };
56
+ check();
57
+ });
58
+ }
59
+
60
+ /**
61
+ * 启动内置 Web 服务器
62
+ */
63
+ function startServer() {
64
+ return new Promise((resolve, reject) => {
65
+ const serverEntry = join(APP_PATH, 'dist', 'cli', 'index.js');
66
+ if (!existsSync(serverEntry)) {
67
+ reject(new Error('未找到服务器入口文件,请先运行 npm run build'));
68
+ return;
69
+ }
70
+
71
+ console.log('[Electron] 启动服务器,端口: ' + PORT);
72
+
73
+ let serverLog = '';
74
+ let started = false;
75
+
76
+ serverProcess = spawn('node', [serverEntry, 'serve'], {
77
+ cwd: APP_PATH,
78
+ env: { ...process.env, FH_WEB_PORT: String(PORT) },
79
+ stdio: ['ignore', 'pipe', 'pipe']
80
+ });
81
+
82
+ serverProcess.stdout.on('data', (data) => {
83
+ const text = data.toString();
84
+ serverLog += text;
85
+ console.log('[Server] ' + text.trim());
86
+ });
87
+
88
+ serverProcess.stderr.on('data', (data) => {
89
+ const text = data.toString();
90
+ serverLog += text;
91
+ console.error('[Server Error] ' + text.trim());
92
+ });
93
+
94
+ serverProcess.on('error', (err) => {
95
+ if (!started) reject(new Error('启动服务器失败: ' + err.message));
96
+ });
97
+
98
+ serverProcess.on('exit', (code) => {
99
+ console.log('[Electron] 服务器退出,代码: ' + code);
100
+ serverProcess = null;
101
+ if (!started) {
102
+ reject(new Error('服务器启动失败,退出代码: ' + code + '\n\n日志:\n' + serverLog.slice(-2000)));
103
+ }
104
+ });
105
+
106
+ // 等待服务器就绪
107
+ waitForServer(PORT, 60000).then(() => {
108
+ started = true;
109
+ console.log('[Electron] 服务器已就绪');
110
+ resolve();
111
+ }).catch((err) => {
112
+ if (!started) reject(new Error(err.message + '\n\n服务器日志:\n' + serverLog.slice(-2000)));
113
+ });
114
+ });
115
+ }
116
+
117
+ /**
118
+ * 创建主窗口
119
+ */
120
+ function createWindow() {
121
+ mainWindow = new BrowserWindow({
122
+ width: 1400,
123
+ height: 900,
124
+ minWidth: 1024,
125
+ minHeight: 680,
126
+ title: '飞虹 Code',
127
+ webPreferences: {
128
+ preload: join(__dirname, 'preload.js'),
129
+ contextIsolation: true,
130
+ nodeIntegration: false
131
+ },
132
+ show: false,
133
+ backgroundColor: '#1a1a2e'
134
+ });
135
+
136
+ const url = `http://127.0.0.1:${PORT}/`;
137
+ console.log('[Electron] 加载页面: ' + url);
138
+ mainWindow.loadURL(url);
139
+
140
+ mainWindow.once('ready-to-show', () => {
141
+ mainWindow.show();
142
+ });
143
+
144
+ // 外部链接用系统浏览器打开
145
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
146
+ shell.openExternal(url);
147
+ return { action: 'deny' };
148
+ });
149
+
150
+ // 关闭时最小化到托盘
151
+ mainWindow.on('close', (e) => {
152
+ if (!app.isQuitting) {
153
+ e.preventDefault();
154
+ mainWindow.hide();
155
+ }
156
+ });
157
+
158
+ mainWindow.on('closed', () => {
159
+ mainWindow = null;
160
+ });
161
+ }
162
+
163
+ /**
164
+ * 创建系统托盘
165
+ */
166
+ function createTray() {
167
+ try {
168
+ const iconPath = join(__dirname, 'icon.png');
169
+ let trayIcon;
170
+ if (existsSync(iconPath)) {
171
+ trayIcon = require('electron').nativeImage.createFromPath(iconPath);
172
+ } else {
173
+ trayIcon = require('electron').nativeImage.createEmpty();
174
+ }
175
+
176
+ tray = new Tray(trayIcon);
177
+ tray.setToolTip('飞虹 Code');
178
+
179
+ const contextMenu = Menu.buildFromTemplate([
180
+ { label: '显示主窗口', click: () => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); } } },
181
+ { type: 'separator' },
182
+ { label: '退出', click: () => { app.isQuitting = true; app.quit(); } }
183
+ ]);
184
+ tray.setContextMenu(contextMenu);
185
+
186
+ tray.on('click', () => {
187
+ if (mainWindow) {
188
+ if (mainWindow.isVisible()) mainWindow.hide();
189
+ else { mainWindow.show(); mainWindow.focus(); }
190
+ }
191
+ });
192
+ } catch (e) {
193
+ console.warn('[Electron] 创建托盘失败: ' + e.message);
194
+ }
195
+ }
196
+
197
+ /**
198
+ * 创建应用菜单(关键:没有菜单会导致复制粘贴等快捷键失效)
199
+ */
200
+ function createMenu() {
201
+ const template = [
202
+ {
203
+ label: '文件',
204
+ submenu: [
205
+ { label: '刷新', role: 'reload' },
206
+ { label: '强制刷新', role: 'forceReload' },
207
+ { type: 'separator' },
208
+ { label: '退出', role: 'quit' }
209
+ ]
210
+ },
211
+ {
212
+ label: '编辑',
213
+ submenu: [
214
+ { label: '撤销', role: 'undo' },
215
+ { label: '重做', role: 'redo' },
216
+ { type: 'separator' },
217
+ { label: '剪切', role: 'cut' },
218
+ { label: '复制', role: 'copy' },
219
+ { label: '粘贴', role: 'paste' },
220
+ { label: '全选', role: 'selectAll' }
221
+ ]
222
+ },
223
+ {
224
+ label: '视图',
225
+ submenu: [
226
+ { label: '放大', role: 'zoomIn' },
227
+ { label: '缩小', role: 'zoomOut' },
228
+ { label: '重置缩放', role: 'resetZoom' },
229
+ { type: 'separator' },
230
+ { label: '全屏', role: 'togglefullscreen' },
231
+ { label: '开发者工具', role: 'toggleDevTools' }
232
+ ]
233
+ },
234
+ {
235
+ label: '窗口',
236
+ submenu: [
237
+ { label: '最小化', role: 'minimize' },
238
+ { label: '关闭', role: 'close' }
239
+ ]
240
+ },
241
+ {
242
+ label: '帮助',
243
+ submenu: [
244
+ { label: '关于飞虹 Code', click: () => {
245
+ dialog.showMessageBox(mainWindow, {
246
+ type: 'info',
247
+ title: '关于飞虹 Code',
248
+ message: '飞虹 Code v0.6.1',
249
+ detail: '终端 AI 编程智能体\n晋江市飞虹智科技企业管理有限公司'
250
+ });
251
+ }}
252
+ ]
253
+ }
254
+ ];
255
+ const menu = Menu.buildFromTemplate(template);
256
+ Menu.setApplicationMenu(menu);
257
+ }
258
+
259
+ /**
260
+ * 设置权限(麦克风、摄像头、屏幕捕获)
261
+ */
262
+ function setupPermissions() {
263
+ // 允许所有权限请求(语音输入、视频通话、截图等)
264
+ session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => {
265
+ console.log('[Electron] 权限请求: ' + permission);
266
+ // 允许所有权限
267
+ callback(true);
268
+ });
269
+
270
+ // 屏幕捕获:自动选择主屏幕,不需要每次都请求权限
271
+ session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
272
+ console.log('[Electron] 屏幕捕获请求,自动选择主屏幕');
273
+ desktopCapturer.getSources({ types: ['screen'] }).then(sources => {
274
+ if (sources.length > 0) {
275
+ // 自动选择第一个屏幕(主屏幕)
276
+ callback({ video: sources[0] });
277
+ } else {
278
+ callback({});
279
+ }
280
+ }).catch(err => {
281
+ console.error('[Electron] 获取屏幕源失败:', err.message);
282
+ callback({});
283
+ });
284
+ });
285
+ }
286
+
287
+ /**
288
+ * 设置 IPC
289
+ */
290
+ function setupIpc() {
291
+ ipcMain.on('window:minimize', () => { if (mainWindow) mainWindow.minimize(); });
292
+ ipcMain.on('window:maximize', () => { if (mainWindow) mainWindow.maximize(); });
293
+ ipcMain.on('window:unmaximize', () => { if (mainWindow) mainWindow.unmaximize(); });
294
+ ipcMain.on('window:close', () => { if (mainWindow) mainWindow.close(); });
295
+ ipcMain.handle('window:isMaximized', () => mainWindow ? mainWindow.isMaximized() : false);
296
+
297
+ ipcMain.on('app:quit', () => { app.isQuitting = true; app.quit(); });
298
+ ipcMain.on('app:restart', () => { app.relaunch(); app.exit(0); });
299
+ ipcMain.handle('app:getPath', (_, name) => { try { return app.getPath(name); } catch { return null; } });
300
+
301
+ ipcMain.handle('shell:openExternal', (_, url) => shell.openExternal(url));
302
+ ipcMain.handle('shell:openPath', (_, path) => shell.openPath(path));
303
+
304
+ ipcMain.handle('dialog:showOpen', (_, options) => dialog.showOpenDialog(mainWindow, options));
305
+ ipcMain.handle('dialog:showSave', (_, options) => dialog.showSaveDialog(mainWindow, options));
306
+ ipcMain.handle('dialog:showMessage', (_, options) => dialog.showMessageBox(mainWindow, options));
307
+
308
+ ipcMain.handle('clipboard:writeText', (_, text) => { clipboard.writeText(text); return true; });
309
+ ipcMain.handle('clipboard:readText', () => clipboard.readText());
310
+
311
+ ipcMain.handle('notification:show', (_, { title, body }) => {
312
+ if (Notification.isSupported()) { new Notification({ title, body }).show(); return true; }
313
+ return false;
314
+ });
315
+
316
+ // 截图功能:截取主屏幕,返回 dataURL
317
+ ipcMain.handle('screenshot:capture', async () => {
318
+ try {
319
+ const primaryDisplay = screen.getPrimaryDisplay();
320
+ const { width, height } = primaryDisplay.size;
321
+ const sources = await desktopCapturer.getSources({
322
+ types: ['screen'],
323
+ thumbnailSize: { width, height }
324
+ });
325
+ const primarySource = sources.find(s => s.display_id === String(primaryDisplay.id)) || sources[0];
326
+ if (!primarySource) throw new Error('未找到屏幕源');
327
+ return {
328
+ success: true,
329
+ dataUrl: primarySource.thumbnail.toDataURL(),
330
+ width: width,
331
+ height: height
332
+ };
333
+ } catch (err) {
334
+ return { success: false, error: err.message };
335
+ }
336
+ });
337
+ }
338
+
339
+ // App 就绪
340
+ app.whenReady().then(async () => {
341
+ console.log('[Electron] App 已就绪');
342
+ console.log('[Electron] 应用路径: ' + APP_PATH);
343
+
344
+ try {
345
+ setupIpc();
346
+ setupPermissions();
347
+ createMenu();
348
+ await startServer();
349
+ createWindow();
350
+ createTray();
351
+
352
+ app.on('activate', () => {
353
+ if (BrowserWindow.getAllWindows().length === 0) createWindow();
354
+ });
355
+ } catch (err) {
356
+ console.error('[Electron] 启动失败: ' + err.message);
357
+
358
+ // 显示错误窗口
359
+ const errorWin = new BrowserWindow({
360
+ width: 700,
361
+ height: 500,
362
+ title: '启动失败',
363
+ webPreferences: { nodeIntegration: true, contextIsolation: false }
364
+ });
365
+
366
+ const errorHtml = `
367
+ <html><body style="font-family:Microsoft YaHei,sans-serif;padding:30px;background:#1a1a2e;color:#fff;margin:0;">
368
+ <h2 style="color:#ff6b6b;">飞虹 Code 启动失败</h2>
369
+ <p style="color:#aaa;">错误详情:</p>
370
+ <pre style="background:#0d1117;padding:15px;border-radius:8px;overflow:auto;white-space:pre-wrap;font-size:12px;max-height:300px;">${String(err.message).replace(/</g, '&lt;')}</pre>
371
+ <p style="color:#888;margin-top:20px;">请检查:1) 是否已运行 npm run build 2) 端口 ${PORT} 是否被占用 3) Node.js 版本是否 >= 18</p>
372
+ <p style="color:#666;margin-top:20px;font-size:12px;">关闭此窗口后退出</p>
373
+ </body></html>
374
+ `;
375
+ errorWin.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(errorHtml));
376
+ errorWin.on('closed', () => app.quit());
377
+ }
378
+ });
379
+
380
+ // 所有窗口关闭时不退出(保持托盘运行)
381
+ app.on('window-all-closed', () => {});
382
+
383
+ // 退出前停止服务器
384
+ app.on('before-quit', () => {
385
+ app.isQuitting = true;
386
+ if (serverProcess) {
387
+ serverProcess.kill();
388
+ serverProcess = null;
389
+ }
390
+ });
391
+
392
+ // 防止多开
393
+ const gotLock = app.requestSingleInstanceLock();
394
+ if (!gotLock) {
395
+ app.quit();
396
+ } else {
397
+ app.on('second-instance', () => {
398
+ if (mainWindow) {
399
+ if (mainWindow.isMinimized()) mainWindow.restore();
400
+ mainWindow.show();
401
+ mainWindow.focus();
402
+ }
403
+ });
404
+ }
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "feihong-code",
3
- "version": "0.6.0",
3
+ "version": "7.0.0",
4
4
  "description": "fhcode(飞虹 Code)— 终端 AI 编程智能体,对标 Muse Code / Cursor CLI:多模型路由(DeepSeek/通义/Ollama/OpenAI)、企业级 RBAC 审计、全自动 SWE Agent、支持离线私有化",
5
5
  "bin": {
6
6
  "fhcode": "dist/cli/index.js"
7
7
  },
8
- "main": "dist/cli/index.js",
8
+ "main": "electron/main.js",
9
9
  "files": [
10
10
  "dist",
11
11
  "README.md",
@@ -31,7 +31,12 @@
31
31
  "eval": "node scripts/eval.mjs",
32
32
  "test": "tsx --test tests/unit/*.test.ts",
33
33
  "verify": "npm run typecheck && npm run build && npm run verify:m4 && npm run verify:m6 && npm run verify:m7 && npm run verify:m8 && npm run verify:m9",
34
- "prepublishOnly": "npm run build"
34
+ "prepublishOnly": "npm run build",
35
+ "electron": "electron .",
36
+ "electron:dev": "electron .",
37
+ "electron:build": "npm run build && electron-builder --win --x64",
38
+ "electron:dist": "npm run build && electron-builder --win --x64 --publish never",
39
+ "electron:pack": "npm run build && electron-builder --dir"
35
40
  },
36
41
  "keywords": [
37
42
  "ai",
@@ -95,6 +100,8 @@
95
100
  "devDependencies": {
96
101
  "@types/express": "^5.0.6",
97
102
  "@types/node": "^26.2.0",
103
+ "electron": "^41.7.1",
104
+ "electron-builder": "^26.15.3",
98
105
  "tsx": "^4.19.2",
99
106
  "typescript": "^5.6.3"
100
107
  },
@@ -104,5 +111,57 @@
104
111
  },
105
112
  "publishConfig": {
106
113
  "registry": "https://registry.npmjs.org/"
114
+ },
115
+ "build": {
116
+ "appId": "com.feihong.code",
117
+ "productName": "飞虹 Code",
118
+ "copyright": "Copyright © 2026 晋江市飞虹智科技企业管理有限公司",
119
+ "directories": {
120
+ "output": "release"
121
+ },
122
+ "files": [
123
+ "electron/**/*",
124
+ "dist/**/*",
125
+ "package.json",
126
+ "!**/*.ts",
127
+ "!**/*.map",
128
+ "!tests/**",
129
+ "!docs/**",
130
+ "!scripts/**",
131
+ "!src/**"
132
+ ],
133
+ "extraResources": [
134
+ {
135
+ "from": "dist",
136
+ "to": "app/dist"
137
+ }
138
+ ],
139
+ "win": {
140
+ "target": [
141
+ {
142
+ "target": "nsis",
143
+ "arch": [
144
+ "x64"
145
+ ]
146
+ },
147
+ {
148
+ "target": "portable",
149
+ "arch": [
150
+ "x64"
151
+ ]
152
+ }
153
+ ],
154
+ "icon": "electron/icon.png"
155
+ },
156
+ "nsis": {
157
+ "oneClick": false,
158
+ "allowToChangeInstallationDirectory": true,
159
+ "createDesktopShortcut": true,
160
+ "createStartMenuShortcut": true,
161
+ "shortcutName": "飞虹 Code"
162
+ },
163
+ "portable": {
164
+ "artifactName": "飞虹Code-Portable-${version}.exe"
165
+ }
107
166
  }
108
167
  }
package/tool-schema.json CHANGED
@@ -1,6 +1,6 @@
1
- {
1
+ {
2
2
  "name": "feihong-code",
3
- "version": "0.6.0",
3
+ "version": "7.0.0",
4
4
  "description": "Terminal AI Coding Agent 鈥?缁堢 AI 缂栫▼鏅鸿兘浣?,
5
5
  "tools": [
6
6
  {