@bolloon/bolloon-agent 0.3.10 → 0.3.12
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/cli-entry.js +94 -0
- package/dist/electron/config.js +9 -14
- package/dist/electron/dialogs.js +16 -53
- package/dist/electron/first-run.js +24 -65
- package/dist/electron/ipc.js +10 -14
- package/dist/electron/logger.js +7 -44
- package/dist/electron/main.js +42 -45
- package/dist/electron/menu.js +13 -18
- package/dist/electron/paths.js +12 -54
- package/dist/electron/server.js +18 -57
- package/dist/electron/tray.js +15 -53
- package/dist/electron/window.js +22 -61
- package/dist/electron-preload.js +16 -19
- package/dist/electron.js +1 -4
- package/dist/index.js +42 -8
- package/dist/utils/auto-update.js +12 -51
- package/package.json +4 -4
package/dist/cli-entry.js
CHANGED
|
@@ -9,12 +9,15 @@
|
|
|
9
9
|
* bolloon --summarize <file> # 总结文档
|
|
10
10
|
* bolloon --web # 启动 Web UI
|
|
11
11
|
* bolloon --version # 显示版本
|
|
12
|
+
* bolloon engine list # 列出外部编码智能体
|
|
13
|
+
* bolloon engine run <prompt> --engine opencode --model opencode/deepseek-v4-flash-free # 委派任务
|
|
12
14
|
*/
|
|
13
15
|
import { spawn } from 'child_process';
|
|
14
16
|
import * as path from 'path';
|
|
15
17
|
import * as fs from 'fs';
|
|
16
18
|
import { fileURLToPath } from 'url';
|
|
17
19
|
import { printBanner } from './cli/loading-tui.js';
|
|
20
|
+
import { discoverEngines, delegateToEngine } from './external-engines/index.js';
|
|
18
21
|
const isWindows = process.platform === 'win32';
|
|
19
22
|
// ANSI 颜色
|
|
20
23
|
const RESET = '\x1b[0m';
|
|
@@ -121,11 +124,99 @@ function parseArgs() {
|
|
|
121
124
|
case '-c':
|
|
122
125
|
case '--cli':
|
|
123
126
|
return { mode: 'cli', args: args.slice(1) };
|
|
127
|
+
case 'engine':
|
|
128
|
+
return { mode: 'engine', args: args.slice(1) };
|
|
124
129
|
default:
|
|
125
130
|
// 传递所有参数给主程序
|
|
126
131
|
return { mode: 'passthrough', args };
|
|
127
132
|
}
|
|
128
133
|
}
|
|
134
|
+
/** 引擎子命令: list / run */
|
|
135
|
+
async function handleEngineCommand(engineArgs) {
|
|
136
|
+
if (engineArgs.length === 0) {
|
|
137
|
+
console.log(`${BOLD}用法:${RESET}`);
|
|
138
|
+
console.log(' bolloon engine list # 列出外部编码智能体');
|
|
139
|
+
console.log(' bolloon engine run <prompt> [options] # 委派任务给智能体');
|
|
140
|
+
console.log('');
|
|
141
|
+
console.log(`${BOLD}选项 (run):${RESET}`);
|
|
142
|
+
console.log(' --engine <id> 引擎 id (默认 opencode)');
|
|
143
|
+
console.log(' --model <name> 指定模型 (如 opencode/deepseek-v4-flash-free)');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const sub = engineArgs[0];
|
|
147
|
+
if (sub === 'list') {
|
|
148
|
+
const engines = await discoverEngines();
|
|
149
|
+
console.log(`\n${BOLD}外部编码智能体:${RESET}`);
|
|
150
|
+
console.log('─'.repeat(60));
|
|
151
|
+
for (const e of engines) {
|
|
152
|
+
const status = e.available ? `${GREEN}✅ 可用${RESET}` : e.configured ? `${YELLOW}⚠ 已配置${RESET}` : e.installed ? `${YELLOW}⚠ 未配置${RESET}` : `${MAGENTA}✗ 未安装${RESET}`;
|
|
153
|
+
console.log(` ${status} ${e.displayName}`);
|
|
154
|
+
console.log(` ID: ${e.id}`);
|
|
155
|
+
console.log(` CLI: ${e.cliPath || '(未安装)'}`);
|
|
156
|
+
console.log(` Provider: ${e.provider || '(未知)'}`);
|
|
157
|
+
if (e.model)
|
|
158
|
+
console.log(` 模型: ${e.model}`);
|
|
159
|
+
if (e.apiKey)
|
|
160
|
+
console.log(` API Key: ***${e.apiKey.slice(-4)}`);
|
|
161
|
+
if (e.notes)
|
|
162
|
+
console.log(` ${e.notes}`);
|
|
163
|
+
console.log('');
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (sub === 'run') {
|
|
168
|
+
// 解析子参数: bolloon engine run <prompt> --engine <id> --model <name>
|
|
169
|
+
const rest = engineArgs.slice(1);
|
|
170
|
+
const promptParts = [];
|
|
171
|
+
let engineId = 'opencode';
|
|
172
|
+
let modelName;
|
|
173
|
+
let cwd;
|
|
174
|
+
for (let i = 0; i < rest.length; i++) {
|
|
175
|
+
const arg = rest[i];
|
|
176
|
+
if (arg === '--engine' && i + 1 < rest.length) {
|
|
177
|
+
engineId = rest[++i];
|
|
178
|
+
}
|
|
179
|
+
else if (arg === '--model' && i + 1 < rest.length) {
|
|
180
|
+
modelName = rest[++i];
|
|
181
|
+
}
|
|
182
|
+
else if (arg === '--cwd' && i + 1 < rest.length) {
|
|
183
|
+
cwd = rest[++i];
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
promptParts.push(arg);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const prompt = promptParts.join(' ');
|
|
190
|
+
if (!prompt) {
|
|
191
|
+
console.error('错误: 请提供 prompt');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
console.log(`${CYAN}🚀 委派给 ${engineId}${RESET}${modelName ? ` (模型: ${modelName})` : ''}`);
|
|
195
|
+
console.log(` Prompt: ${prompt.slice(0, 120)}${prompt.length > 120 ? '...' : ''}`);
|
|
196
|
+
console.log('');
|
|
197
|
+
const result = await delegateToEngine(engineId, prompt, {
|
|
198
|
+
model: modelName,
|
|
199
|
+
cwd: cwd || process.cwd(),
|
|
200
|
+
});
|
|
201
|
+
if (result.success) {
|
|
202
|
+
console.log(`${GREEN}✅ 执行成功 (exit: ${result.exitCode})${RESET}`);
|
|
203
|
+
console.log('─'.repeat(40));
|
|
204
|
+
console.log(result.output || '(无输出)');
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
console.log(`${MAGENTA}❌ 执行失败${RESET}`);
|
|
208
|
+
console.log('─'.repeat(40));
|
|
209
|
+
if (result.error)
|
|
210
|
+
console.error(` Error: ${result.error}`);
|
|
211
|
+
if (result.output)
|
|
212
|
+
console.log(result.output.slice(0, 2000));
|
|
213
|
+
process.exit(result.exitCode ?? 1);
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
console.error(`未知引擎子命令: ${sub}`);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
129
220
|
// 执行 Node.js 脚本
|
|
130
221
|
async function runNodeScript(scriptPath, additionalArgs) {
|
|
131
222
|
return new Promise((resolve, reject) => {
|
|
@@ -237,6 +328,9 @@ async function main() {
|
|
|
237
328
|
case 'cli':
|
|
238
329
|
await startCLI(args);
|
|
239
330
|
break;
|
|
331
|
+
case 'engine':
|
|
332
|
+
await handleEngineCommand(args);
|
|
333
|
+
break;
|
|
240
334
|
case 'passthrough':
|
|
241
335
|
// 传递所有参数给主程序
|
|
242
336
|
try {
|
package/dist/electron/config.js
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isDev = exports.MAIN_WINDOW_MIN = exports.MAIN_WINDOW_DEFAULT = exports.WEB_SERVER_STARTUP_TIMEOUT_MS = exports.DEFAULT_HOST = exports.DEFAULT_PORT = void 0;
|
|
4
|
-
exports.preferredPort = preferredPort;
|
|
5
1
|
/**
|
|
6
2
|
* 常量配置 (env 解析在这里集中, 不散在 main 流程)
|
|
7
3
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { app } from 'electron';
|
|
5
|
+
export const DEFAULT_PORT = 54188;
|
|
10
6
|
/** Hard-pin to loopback; LAN exposure must be explicit. */
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
function preferredPort() {
|
|
7
|
+
export const DEFAULT_HOST = '127.0.0.1';
|
|
8
|
+
export const WEB_SERVER_STARTUP_TIMEOUT_MS = 15_000;
|
|
9
|
+
export const MAIN_WINDOW_DEFAULT = { width: 1200, height: 800 };
|
|
10
|
+
export const MAIN_WINDOW_MIN = { width: 800, height: 600 };
|
|
11
|
+
export function preferredPort() {
|
|
16
12
|
const raw = process.env.ELECTRON_PORT || process.env.PORT;
|
|
17
13
|
const n = parseInt(raw || '', 10);
|
|
18
|
-
return Number.isFinite(n) && n > 0 && n < 65536 ? n :
|
|
14
|
+
return Number.isFinite(n) && n > 0 && n < 65536 ? n : DEFAULT_PORT;
|
|
19
15
|
}
|
|
20
|
-
|
|
21
|
-
//# sourceMappingURL=config.js.map
|
|
16
|
+
export const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
|
package/dist/electron/dialogs.js
CHANGED
|
@@ -1,61 +1,25 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.registerDialogIpc = registerDialogIpc;
|
|
37
1
|
/**
|
|
38
2
|
* 文件 dialog 桥 (open / save / dir) + 安全的 fs 桥 (read / write / exists)
|
|
39
3
|
*
|
|
40
4
|
* 5MB read 上限保护 — 渲染进程直接 fs.readFile 没法做限制, 走主进程就有界
|
|
41
5
|
* 所有 handler 解析 event.sender 拿到 window, 让 dialog 模态在该窗口上
|
|
42
6
|
*/
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
7
|
+
import { BrowserWindow, dialog, ipcMain } from 'electron';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { log } from './logger';
|
|
47
11
|
const MAX_READ_BYTES = 5 * 1024 * 1024; // 5MB
|
|
48
12
|
function windowFor(event) {
|
|
49
|
-
return
|
|
13
|
+
return BrowserWindow.fromWebContents(event.sender);
|
|
50
14
|
}
|
|
51
15
|
function resolveSafe(target) {
|
|
52
16
|
// 不去硬限制路径 — user 给 renderer 暴露 fs 已经信任了, 这里只 normalize
|
|
53
17
|
return path.resolve(target);
|
|
54
18
|
}
|
|
55
|
-
function registerDialogIpc() {
|
|
56
|
-
|
|
19
|
+
export function registerDialogIpc() {
|
|
20
|
+
ipcMain.handle('dialog:open-file', async (event, opts = {}) => {
|
|
57
21
|
const win = windowFor(event);
|
|
58
|
-
const result = await
|
|
22
|
+
const result = await dialog.showOpenDialog(win, {
|
|
59
23
|
title: opts.title,
|
|
60
24
|
defaultPath: opts.defaultPath,
|
|
61
25
|
filters: opts.filters,
|
|
@@ -63,25 +27,25 @@ function registerDialogIpc() {
|
|
|
63
27
|
});
|
|
64
28
|
return { canceled: result.canceled, filePaths: result.filePaths };
|
|
65
29
|
});
|
|
66
|
-
|
|
30
|
+
ipcMain.handle('dialog:save-file', async (event, opts = {}) => {
|
|
67
31
|
const win = windowFor(event);
|
|
68
|
-
const result = await
|
|
32
|
+
const result = await dialog.showSaveDialog(win, {
|
|
69
33
|
title: opts.title,
|
|
70
34
|
defaultPath: opts.defaultPath,
|
|
71
35
|
filters: opts.filters,
|
|
72
36
|
});
|
|
73
37
|
return { canceled: result.canceled, filePath: result.filePath };
|
|
74
38
|
});
|
|
75
|
-
|
|
39
|
+
ipcMain.handle('dialog:open-directory', async (event, opts = {}) => {
|
|
76
40
|
const win = windowFor(event);
|
|
77
|
-
const result = await
|
|
41
|
+
const result = await dialog.showOpenDialog(win, {
|
|
78
42
|
title: opts.title,
|
|
79
43
|
defaultPath: opts.defaultPath,
|
|
80
44
|
properties: ['openDirectory', 'createDirectory'],
|
|
81
45
|
});
|
|
82
46
|
return { canceled: result.canceled, filePaths: result.filePaths };
|
|
83
47
|
});
|
|
84
|
-
|
|
48
|
+
ipcMain.handle('fs:read-text-file', async (_event, opts) => {
|
|
85
49
|
const target = resolveSafe(opts.path);
|
|
86
50
|
const stat = fs.statSync(target);
|
|
87
51
|
if (stat.size > MAX_READ_BYTES) {
|
|
@@ -89,12 +53,12 @@ function registerDialogIpc() {
|
|
|
89
53
|
}
|
|
90
54
|
return fs.readFileSync(target, { encoding: opts.encoding ?? 'utf8' });
|
|
91
55
|
});
|
|
92
|
-
|
|
56
|
+
ipcMain.handle('fs:write-text-file', async (_event, opts) => {
|
|
93
57
|
const target = resolveSafe(opts.path);
|
|
94
58
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
95
59
|
fs.writeFileSync(target, opts.content, { encoding: opts.encoding ?? 'utf8' });
|
|
96
60
|
});
|
|
97
|
-
|
|
61
|
+
ipcMain.handle('fs:path-exists', async (_event, opts) => {
|
|
98
62
|
try {
|
|
99
63
|
fs.accessSync(resolveSafe(opts.path));
|
|
100
64
|
return true;
|
|
@@ -103,6 +67,5 @@ function registerDialogIpc() {
|
|
|
103
67
|
return false;
|
|
104
68
|
}
|
|
105
69
|
});
|
|
106
|
-
|
|
70
|
+
log('dialog + fs IPC handlers registered');
|
|
107
71
|
}
|
|
108
|
-
//# sourceMappingURL=dialogs.js.map
|
|
@@ -1,54 +1,14 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.hasSeenFirstRun = hasSeenFirstRun;
|
|
37
|
-
exports.markFirstRunSeen = markFirstRunSeen;
|
|
38
|
-
exports.showFirstRunOverlay = showFirstRunOverlay;
|
|
39
|
-
exports.registerFirstRunIpc = registerFirstRunIpc;
|
|
40
|
-
exports.maybeShowFirstRun = maybeShowFirstRun;
|
|
41
1
|
/**
|
|
42
2
|
* 首启检测 + 引导浮层
|
|
43
3
|
*
|
|
44
4
|
* 标记文件写在 userData (不是 ~/.bolloon/), 卸载 app 自然清掉
|
|
45
5
|
* 引导窗是父主窗的 modal, frame=false, 透明背景; 关闭时标记写入
|
|
46
6
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
7
|
+
import { BrowserWindow, app, ipcMain } from 'electron';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { firstRunFlagPath, dataDir, logsDir } from './paths';
|
|
11
|
+
import { log } from './logger';
|
|
52
12
|
const OVERLAY_HTML = `
|
|
53
13
|
<!DOCTYPE html>
|
|
54
14
|
<html>
|
|
@@ -104,26 +64,26 @@ const OVERLAY_HTML = `
|
|
|
104
64
|
</body>
|
|
105
65
|
</html>
|
|
106
66
|
`;
|
|
107
|
-
function hasSeenFirstRun() {
|
|
67
|
+
export function hasSeenFirstRun() {
|
|
108
68
|
try {
|
|
109
|
-
return fs.existsSync(
|
|
69
|
+
return fs.existsSync(firstRunFlagPath());
|
|
110
70
|
}
|
|
111
71
|
catch {
|
|
112
72
|
return false;
|
|
113
73
|
}
|
|
114
74
|
}
|
|
115
|
-
function markFirstRunSeen() {
|
|
75
|
+
export function markFirstRunSeen() {
|
|
116
76
|
try {
|
|
117
|
-
fs.mkdirSync(path.dirname(
|
|
118
|
-
fs.writeFileSync(
|
|
77
|
+
fs.mkdirSync(path.dirname(firstRunFlagPath()), { recursive: true });
|
|
78
|
+
fs.writeFileSync(firstRunFlagPath(), new Date().toISOString());
|
|
119
79
|
}
|
|
120
80
|
catch (err) {
|
|
121
|
-
|
|
81
|
+
log(`写入首启标记失败: ${err.message}`, 'warn');
|
|
122
82
|
}
|
|
123
83
|
}
|
|
124
|
-
function showFirstRunOverlay(parent) {
|
|
84
|
+
export function showFirstRunOverlay(parent) {
|
|
125
85
|
return new Promise((resolve) => {
|
|
126
|
-
const overlay = new
|
|
86
|
+
const overlay = new BrowserWindow({
|
|
127
87
|
parent,
|
|
128
88
|
modal: true,
|
|
129
89
|
frame: false,
|
|
@@ -143,28 +103,27 @@ function showFirstRunOverlay(parent) {
|
|
|
143
103
|
markFirstRunSeen();
|
|
144
104
|
overlay.close();
|
|
145
105
|
};
|
|
146
|
-
|
|
106
|
+
ipcMain.once('first-run:ack', handler);
|
|
147
107
|
overlay.on('closed', () => {
|
|
148
|
-
|
|
108
|
+
ipcMain.removeListener('first-run:ack', handler);
|
|
149
109
|
resolve();
|
|
150
110
|
});
|
|
151
111
|
});
|
|
152
112
|
}
|
|
153
113
|
/** 注册 IPC handlers (给 preload 桥用) */
|
|
154
|
-
function registerFirstRunIpc() {
|
|
155
|
-
|
|
156
|
-
|
|
114
|
+
export function registerFirstRunIpc() {
|
|
115
|
+
ipcMain.handle('first-run:seen', () => hasSeenFirstRun());
|
|
116
|
+
ipcMain.handle('first-run:mark-seen', () => { markFirstRunSeen(); });
|
|
157
117
|
// 同步值 (不用 ipcRenderer.invoke 的 await) — 用 exposeInMainWorld 的 sync getter 更顺
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
118
|
+
ipcMain.handle('first-run:data-dir', () => dataDir());
|
|
119
|
+
ipcMain.handle('first-run:logs-dir', () => logsDir());
|
|
120
|
+
log('first-run IPC handlers registered');
|
|
161
121
|
}
|
|
162
122
|
/** 包装 — 决定要不要弹 overlay */
|
|
163
|
-
async function maybeShowFirstRun(parent) {
|
|
123
|
+
export async function maybeShowFirstRun(parent) {
|
|
164
124
|
if (hasSeenFirstRun())
|
|
165
125
|
return;
|
|
166
|
-
|
|
126
|
+
log('首启 — 弹出引导');
|
|
167
127
|
await showFirstRunOverlay(parent);
|
|
168
|
-
|
|
128
|
+
app.addRecentDocument(firstRunFlagPath()); // 跟踪最近文档, 让 user 知道有这文件
|
|
169
129
|
}
|
|
170
|
-
//# sourceMappingURL=first-run.js.map
|
package/dist/electron/ipc.js
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerCoreIpc = registerCoreIpc;
|
|
4
1
|
/**
|
|
5
2
|
* IPC handler 集中注册 — version / userData path / open-external (legacy 3 个)
|
|
6
3
|
* dialog/fs 的注册在 dialogs.ts; updater 的注册在 updater.ts
|
|
7
4
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
function registerCoreIpc() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
await
|
|
5
|
+
import { app, ipcMain, shell } from 'electron';
|
|
6
|
+
import { userDataDir, dataDir } from './paths';
|
|
7
|
+
import { log } from './logger';
|
|
8
|
+
export function registerCoreIpc() {
|
|
9
|
+
ipcMain.handle('get-version', () => app.getVersion());
|
|
10
|
+
ipcMain.handle('get-user-data-path', () => userDataDir());
|
|
11
|
+
ipcMain.handle('get-data-path', () => dataDir()); // 跟 userData 分开, 渲染层要用
|
|
12
|
+
ipcMain.handle('open-external', async (_event, url) => {
|
|
13
|
+
await shell.openExternal(url);
|
|
17
14
|
});
|
|
18
|
-
|
|
15
|
+
log('core IPC handlers registered');
|
|
19
16
|
}
|
|
20
|
-
//# sourceMappingURL=ipc.js.map
|
package/dist/electron/logger.js
CHANGED
|
@@ -1,39 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.log = log;
|
|
37
1
|
/**
|
|
38
2
|
* 简单 logger: 写日志到 <userData>/logs/bolloon.log, size-based 轮转
|
|
39
3
|
* (5MB 上限, 保留 .1 .2 .3 共 3 个备份 → ~20MB 上限)
|
|
@@ -41,8 +5,8 @@ exports.log = log;
|
|
|
41
5
|
* 同步写是因为 Electron 主进程日志量小, 同步 append 简单可靠, 不丢日志.
|
|
42
6
|
* 量大的会话级 jsonl 走自己路径 (lifecycle-hooks 等), 跟本 logger 无关.
|
|
43
7
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import { mainLogPath, logsDir } from './paths';
|
|
46
10
|
const MAX_BYTES = 5 * 1024 * 1024;
|
|
47
11
|
const MAX_BACKUPS = 3; // 保留 bolloon.log.1 .2 .3
|
|
48
12
|
let currentSize = 0;
|
|
@@ -52,9 +16,9 @@ function ensureLogFile() {
|
|
|
52
16
|
return;
|
|
53
17
|
initialized = true;
|
|
54
18
|
try {
|
|
55
|
-
fs.mkdirSync(
|
|
19
|
+
fs.mkdirSync(logsDir(), { recursive: true });
|
|
56
20
|
try {
|
|
57
|
-
currentSize = fs.statSync(
|
|
21
|
+
currentSize = fs.statSync(mainLogPath()).size;
|
|
58
22
|
}
|
|
59
23
|
catch {
|
|
60
24
|
currentSize = 0;
|
|
@@ -67,7 +31,7 @@ function ensureLogFile() {
|
|
|
67
31
|
}
|
|
68
32
|
}
|
|
69
33
|
function rotate() {
|
|
70
|
-
const logPath =
|
|
34
|
+
const logPath = mainLogPath();
|
|
71
35
|
for (let i = MAX_BACKUPS; i >= 1; i--) {
|
|
72
36
|
const src = `${logPath}.${i}`;
|
|
73
37
|
const dst = `${logPath}.${i + 1}`;
|
|
@@ -90,14 +54,14 @@ function rotate() {
|
|
|
90
54
|
catch { /* missing ok */ }
|
|
91
55
|
currentSize = 0;
|
|
92
56
|
}
|
|
93
|
-
function log(message, level = 'info') {
|
|
57
|
+
export function log(message, level = 'info') {
|
|
94
58
|
ensureLogFile();
|
|
95
59
|
const ts = new Date().toISOString();
|
|
96
60
|
const line = `[${ts}] [${level}] ${message}\n`;
|
|
97
61
|
if (currentSize + line.length > MAX_BYTES)
|
|
98
62
|
rotate();
|
|
99
63
|
try {
|
|
100
|
-
fs.appendFileSync(
|
|
64
|
+
fs.appendFileSync(mainLogPath(), line);
|
|
101
65
|
currentSize += line.length;
|
|
102
66
|
}
|
|
103
67
|
catch (err) {
|
|
@@ -111,4 +75,3 @@ function log(message, level = 'info') {
|
|
|
111
75
|
else
|
|
112
76
|
console.log(line.trimEnd());
|
|
113
77
|
}
|
|
114
|
-
//# sourceMappingURL=logger.js.map
|
package/dist/electron/main.js
CHANGED
|
@@ -1,45 +1,43 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
1
|
/**
|
|
4
2
|
* Electron 主进程 lifecycle 入口
|
|
5
3
|
* 单实例锁 → whenReady → createMainWindow → cleanup on quit
|
|
6
4
|
* 启动时: 装 menu / tray / IPC handlers / 首启引导
|
|
7
5
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
import { app, BrowserWindow } from 'electron';
|
|
7
|
+
import { log } from './logger';
|
|
8
|
+
import { createMainWindow, focusMainWindow, getMainWindow } from './window';
|
|
9
|
+
import { killWebServer } from './server';
|
|
10
|
+
import { installAppMenu } from './menu';
|
|
11
|
+
import { createTray, destroyTray } from './tray';
|
|
12
|
+
import { registerCoreIpc } from './ipc';
|
|
13
|
+
import { registerDialogIpc } from './dialogs';
|
|
14
|
+
import { registerFirstRunIpc, maybeShowFirstRun } from './first-run';
|
|
15
|
+
import { checkAndUpdate } from '../utils/auto-update.js';
|
|
16
|
+
log('Bolloon Electron 启动');
|
|
19
17
|
// 单实例锁
|
|
20
|
-
const gotTheLock =
|
|
18
|
+
const gotTheLock = app.requestSingleInstanceLock();
|
|
21
19
|
if (!gotTheLock) {
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
log('已有实例在运行,退出');
|
|
21
|
+
app.quit();
|
|
24
22
|
}
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
app.on('second-instance', () => {
|
|
24
|
+
focusMainWindow();
|
|
27
25
|
});
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
app.whenReady().then(async () => {
|
|
27
|
+
log('Electron 准备好');
|
|
28
|
+
registerCoreIpc();
|
|
29
|
+
registerDialogIpc();
|
|
30
|
+
registerFirstRunIpc();
|
|
31
|
+
installAppMenu(getMainWindow);
|
|
32
|
+
createTray(getMainWindow);
|
|
35
33
|
// 启动自动更新检查(后台,不阻塞 UI)。
|
|
36
34
|
// 安装成功后用 app.relaunch() 自动重启以应用新版本(避免单实例锁冲突)。
|
|
37
35
|
void (async () => {
|
|
38
36
|
try {
|
|
39
|
-
await
|
|
37
|
+
await checkAndUpdate({
|
|
40
38
|
onUpdated: () => {
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
app.relaunch();
|
|
40
|
+
app.exit(0);
|
|
43
41
|
},
|
|
44
42
|
});
|
|
45
43
|
}
|
|
@@ -47,33 +45,32 @@ electron_1.app.whenReady().then(async () => {
|
|
|
47
45
|
// 自动更新失败不影响主程序
|
|
48
46
|
}
|
|
49
47
|
})();
|
|
50
|
-
await
|
|
48
|
+
await createMainWindow();
|
|
51
49
|
// 首启引导 (在主窗口就绪后弹, 不阻塞 UI)
|
|
52
|
-
const win =
|
|
50
|
+
const win = getMainWindow();
|
|
53
51
|
if (win) {
|
|
54
52
|
win.webContents.once('did-finish-load', () => {
|
|
55
|
-
void
|
|
53
|
+
void maybeShowFirstRun(win);
|
|
56
54
|
});
|
|
57
55
|
}
|
|
58
|
-
|
|
56
|
+
app.on('activate', async () => {
|
|
59
57
|
// macOS: dock 图标被点击且没有窗口时, 重开
|
|
60
|
-
if (
|
|
61
|
-
await
|
|
58
|
+
if (BrowserWindow.getAllWindows().length === 0) {
|
|
59
|
+
await createMainWindow();
|
|
62
60
|
}
|
|
63
61
|
});
|
|
64
62
|
});
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
app.on('window-all-closed', () => {
|
|
64
|
+
log('所有窗口已关闭');
|
|
65
|
+
destroyTray();
|
|
66
|
+
killWebServer();
|
|
69
67
|
if (process.platform !== 'darwin') {
|
|
70
|
-
|
|
68
|
+
app.quit();
|
|
71
69
|
}
|
|
72
70
|
});
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
app.on('before-quit', () => {
|
|
72
|
+
log('应用即将退出');
|
|
73
|
+
destroyTray();
|
|
74
|
+
killWebServer();
|
|
77
75
|
});
|
|
78
|
-
|
|
79
|
-
//# sourceMappingURL=main.js.map
|
|
76
|
+
log('主进程初始化完成');
|