@bolloon/bolloon-agent 0.2.0 → 0.2.1
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/agents/pi-sdk.js +249 -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 +38 -41
- package/dist/electron/menu.js +13 -18
- package/dist/electron/paths.js +12 -54
- package/dist/electron/server.js +17 -56
- package/dist/electron/tray.js +15 -53
- package/dist/electron/window.js +22 -61
- package/dist/security/tool-gate.js +4 -0
- package/dist/web/client.js +3767 -2873
- package/dist/web/components/p2p/P2PModal.js +188 -0
- package/dist/web/components/p2p/index.js +264 -226
- package/dist/web/components/p2p/p2p-modal.js +657 -0
- package/dist/web/components/p2p/p2p-tools.js +248 -0
- package/dist/web/ui/message-renderer.js +442 -326
- package/dist/web/ui/step-timeline.js +351 -255
- package/package.json +8 -2
package/dist/agents/pi-sdk.js
CHANGED
|
@@ -1014,6 +1014,10 @@ class PiAgentSession {
|
|
|
1014
1014
|
return await runSelfImproveLoop(goal);
|
|
1015
1015
|
}
|
|
1016
1016
|
});
|
|
1017
|
+
// 2026-06-24: Wallet + Polymarket + Safe 工具集 (基于 constraint-runtime/src/tools/)
|
|
1018
|
+
// 真实可用实现: ethers v6 + JsonRpcProvider, 默认走 eth.llamarpc.com
|
|
1019
|
+
// agent 可以创建/导入/查询 EVM 钱包, 查余额, 签名, 转账, 部署 Safe 多签钱包
|
|
1020
|
+
this._registerWalletTools();
|
|
1017
1021
|
// list_skills 工具: 列出当前 session 已加载的 skills
|
|
1018
1022
|
// 加载源: ~/.bolloon/skills/ → <cwd>/.bolloon/skills/ → ~/.boll/skills/
|
|
1019
1023
|
this.tools.set('list_skills', {
|
|
@@ -3107,6 +3111,251 @@ Workspace root folder: ${this.cwd}
|
|
|
3107
3111
|
console.log('[DBG parseToolCall] result:', JSON.stringify(r), 'content head:', JSON.stringify(content.substring(0, 200)));
|
|
3108
3112
|
return r;
|
|
3109
3113
|
}
|
|
3114
|
+
/**
|
|
3115
|
+
* 2026-06-24: 注册 Wallet + Polymarket + Safe 工具
|
|
3116
|
+
* 真实实现 (ethers v6 + JsonRpcProvider) — agent 可以创建 EVM 钱包, 查余额, 转账, 签名, Polymarket 下单
|
|
3117
|
+
* 工具来源: src/constraint-runtime/src/tools/{WalletTools,PolymarketSDK,SafeSDK}/
|
|
3118
|
+
*/
|
|
3119
|
+
_registerWalletTools() {
|
|
3120
|
+
// === WalletTools (EVM 钱包) ===
|
|
3121
|
+
this.tools.set('wallet_create', {
|
|
3122
|
+
name: 'wallet_create',
|
|
3123
|
+
description: '创建新 EVM 钱包 (BIP-39 12 词助记词 + 私钥 + 地址). 私钥会在响应中返回, agent 应当保存到 ~/.bolloon/wallets/<address>.json',
|
|
3124
|
+
parameters: {},
|
|
3125
|
+
execute: async () => {
|
|
3126
|
+
try {
|
|
3127
|
+
const { createWallet } = await import('../constraint-runtime/dist/tools/WalletTools/createWallet.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/createWallet.js'));
|
|
3128
|
+
const r = await createWallet();
|
|
3129
|
+
return { success: true, output: `✅ 钱包创建成功:\n address: ${r.address}\n privateKey: ${r.privateKey}\n mnemonic: ${r.mnemonic}\n createdAt: ${r.createdAt}\n\n⚠️ 请 agent 立即把私钥保存到 ~/.bolloon/wallets/${r.address}.json` };
|
|
3130
|
+
}
|
|
3131
|
+
catch (e) {
|
|
3132
|
+
return { success: false, error: `创建失败: ${String(e.message || e)}` };
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
});
|
|
3136
|
+
this.tools.set('wallet_import', {
|
|
3137
|
+
name: 'wallet_import',
|
|
3138
|
+
description: '导入已有 EVM 钱包 (用助记词或私钥)',
|
|
3139
|
+
parameters: { mnemonic: '可选, 12/15/18/21/24 词助记词', privateKey: '可选, 0x 开头的私钥' },
|
|
3140
|
+
execute: async (args) => {
|
|
3141
|
+
try {
|
|
3142
|
+
const { importWallet } = await import('../constraint-runtime/dist/tools/WalletTools/importWallet.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/importWallet.js'));
|
|
3143
|
+
const r = await importWallet({ mnemonic: args.mnemonic, privateKey: args.privateKey });
|
|
3144
|
+
return { success: true, output: `✅ 钱包导入成功:\n address: ${r.address}\n privateKey: ${r.privateKey}\n source: ${r.source}` };
|
|
3145
|
+
}
|
|
3146
|
+
catch (e) {
|
|
3147
|
+
return { success: false, error: `导入失败: ${String(e.message || e)}` };
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
});
|
|
3151
|
+
this.tools.set('wallet_get_balance', {
|
|
3152
|
+
name: 'wallet_get_balance',
|
|
3153
|
+
description: '查 EVM 钱包 ETH 余额. 默认 RPC = https://eth.llamarpc.com, 可指定其他 RPC URL',
|
|
3154
|
+
parameters: { address: '0x 开头的 EVM 地址 (必填)', rpcUrl: '可选 RPC URL (默认 eth.llamarpc.com)' },
|
|
3155
|
+
execute: async (args) => {
|
|
3156
|
+
try {
|
|
3157
|
+
const { getBalance } = await import('../constraint-runtime/dist/tools/WalletTools/getBalance.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/getBalance.js'));
|
|
3158
|
+
const r = await getBalance({ address: String(args.address), rpcUrl: args.rpcUrl });
|
|
3159
|
+
return { success: true, output: `💰 ${r.address}\n ${r.balanceEth} ${r.symbol} (${r.balance} wei)` };
|
|
3160
|
+
}
|
|
3161
|
+
catch (e) {
|
|
3162
|
+
return { success: false, error: `查余额失败: ${String(e.message || e)}` };
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
});
|
|
3166
|
+
this.tools.set('wallet_sign_message', {
|
|
3167
|
+
name: 'wallet_sign_message',
|
|
3168
|
+
description: '用私钥对消息做 EIP-191 personal_sign 签名',
|
|
3169
|
+
parameters: { message: '要签名的消息 (必填)', privateKey: '0x 开头的私钥 (必填)' },
|
|
3170
|
+
execute: async (args) => {
|
|
3171
|
+
try {
|
|
3172
|
+
const { signMessage } = await import('../constraint-runtime/dist/tools/WalletTools/signMessage.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/signMessage.js'));
|
|
3173
|
+
const r = await signMessage({ message: String(args.message), privateKey: String(args.privateKey) });
|
|
3174
|
+
return { success: true, output: `✅ 签名完成:\n address: ${r.address}\n message: ${r.message}\n signature: ${r.signature}` };
|
|
3175
|
+
}
|
|
3176
|
+
catch (e) {
|
|
3177
|
+
return { success: false, error: `签名失败: ${String(e.message || e)}` };
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
});
|
|
3181
|
+
this.tools.set('wallet_send_tx', {
|
|
3182
|
+
name: 'wallet_send_tx',
|
|
3183
|
+
description: '用 EVM 钱包发送交易 (转账 ETH 或调用合约). 走 EIP-1559, 默认 RPC = eth.llamarpc.com',
|
|
3184
|
+
parameters: { privateKey: '私钥 (必填)', to: '接收地址 (必填)', value: '发送 wei 数量 (必填)', data: '可选 0x 开头的 calldata', rpcUrl: '可选 RPC URL' },
|
|
3185
|
+
execute: async (args) => {
|
|
3186
|
+
try {
|
|
3187
|
+
const { sendTransaction } = await import('../constraint-runtime/dist/tools/WalletTools/sendTransaction.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/sendTransaction.js'));
|
|
3188
|
+
const r = await sendTransaction({
|
|
3189
|
+
privateKey: String(args.privateKey),
|
|
3190
|
+
to: String(args.to),
|
|
3191
|
+
value: String(args.value),
|
|
3192
|
+
data: args.data ? String(args.data) : undefined,
|
|
3193
|
+
rpcUrl: args.rpcUrl,
|
|
3194
|
+
});
|
|
3195
|
+
return { success: true, output: `✅ 交易已发送:\n hash: ${r.hash}\n from: ${r.from} → to: ${r.to}\n value: ${r.value} wei\n status: ${r.status}\n blockNumber: ${r.blockNumber || '(pending)'}\n gasUsed: ${r.gasUsed || '?'}` };
|
|
3196
|
+
}
|
|
3197
|
+
catch (e) {
|
|
3198
|
+
return { success: false, error: `交易失败: ${String(e.message || e)}` };
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
});
|
|
3202
|
+
this.tools.set('wallet_transfer_token', {
|
|
3203
|
+
name: 'wallet_transfer_token',
|
|
3204
|
+
description: '用 EVM 钱包转 ERC20 token (需要 token 合约地址)',
|
|
3205
|
+
parameters: { privateKey: '私钥 (必填)', tokenAddress: 'ERC20 合约地址 (必填)', to: '接收地址 (必填)', amount: 'token 数量 (人类可读, 自动按 decimals 转换)', decimals: '可选 token decimals (默认查合约)', rpcUrl: '可选 RPC URL' },
|
|
3206
|
+
execute: async (args) => {
|
|
3207
|
+
try {
|
|
3208
|
+
const { transferToken } = await import('../constraint-runtime/dist/tools/WalletTools/transferToken.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/transferToken.js'));
|
|
3209
|
+
const r = await transferToken({
|
|
3210
|
+
privateKey: String(args.privateKey),
|
|
3211
|
+
tokenAddress: String(args.tokenAddress),
|
|
3212
|
+
to: String(args.to),
|
|
3213
|
+
amount: String(args.amount),
|
|
3214
|
+
decimals: args.decimals ? Number(args.decimals) : undefined,
|
|
3215
|
+
rpcUrl: args.rpcUrl,
|
|
3216
|
+
});
|
|
3217
|
+
return { success: true, output: `✅ Token 转账完成:\n hash: ${r.hash}\n ${r.amount} ${r.tokenAddress.substring(0, 10)}...\n from: ${r.from} → to: ${r.to}\n status: ${r.status}` };
|
|
3218
|
+
}
|
|
3219
|
+
catch (e) {
|
|
3220
|
+
return { success: false, error: `转账失败: ${String(e.message || e)}` };
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
});
|
|
3224
|
+
this.tools.set('wallet_autopay', {
|
|
3225
|
+
name: 'wallet_autopay',
|
|
3226
|
+
description: '设置自动付款 (订阅/定期扣款, 用 SafeSDK 自动执行). 实际能力取决于 SafeSDK autopay 实现',
|
|
3227
|
+
parameters: { from: '付款方地址 (必填)', to: '收款方地址 (必填)', amount: '金额 (必填)', interval: '周期 (e.g. daily/weekly/monthly)', token: '可选, 默认 ETH' },
|
|
3228
|
+
execute: async (args) => {
|
|
3229
|
+
try {
|
|
3230
|
+
const mod = await import('../constraint-runtime/dist/tools/WalletTools/autoPay.js').catch(() => import('../constraint-runtime/src/tools/WalletTools/autoPay.js'));
|
|
3231
|
+
const fn = mod.autoPay || mod.setAutoPay || mod.default;
|
|
3232
|
+
if (!fn)
|
|
3233
|
+
return { success: false, error: 'autoPay 接口未找到' };
|
|
3234
|
+
const r = await fn({
|
|
3235
|
+
from: String(args.from), to: String(args.to), amount: String(args.amount),
|
|
3236
|
+
interval: String(args.interval || 'monthly'), token: args.token,
|
|
3237
|
+
});
|
|
3238
|
+
return { success: true, output: `✅ 自动付款已设置: ${JSON.stringify(r)}` };
|
|
3239
|
+
}
|
|
3240
|
+
catch (e) {
|
|
3241
|
+
return { success: false, error: `设置失败: ${String(e.message || e)}` };
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
});
|
|
3245
|
+
// === PolymarketSDK (预测市场) ===
|
|
3246
|
+
this.tools.set('polymarket_list_markets', {
|
|
3247
|
+
name: 'polymarket_list_markets',
|
|
3248
|
+
description: '列出 Polymarket 预测市场 (用 Polymarket CLOB API). limit 默认 50',
|
|
3249
|
+
parameters: { limit: '可选 数量 (默认 50)', offset: '可选 偏移', closed: '可选 是否只显示已关闭 (默认 false = 只显示活跃)' },
|
|
3250
|
+
execute: async (args) => {
|
|
3251
|
+
try {
|
|
3252
|
+
const { listMarkets } = await import('../constraint-runtime/dist/tools/PolymarketSDK/listMarkets.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/listMarkets.js'));
|
|
3253
|
+
const markets = await listMarkets({
|
|
3254
|
+
limit: args.limit ? Number(args.limit) : 50,
|
|
3255
|
+
offset: args.offset ? Number(args.offset) : 0,
|
|
3256
|
+
closed: args.closed ? String(args.closed) === 'true' : false,
|
|
3257
|
+
});
|
|
3258
|
+
if (!markets || markets.length === 0) {
|
|
3259
|
+
return { success: true, output: '📊 Polymarket 当前没有活跃市场 (或 API 不可达, 因为 constraint-runtime 是占位实现, 需要装 polymarket-sdk 包)' };
|
|
3260
|
+
}
|
|
3261
|
+
const lines = markets.slice(0, 10).map((m, i) => ` ${i + 1}. [${m.id}] ${m.question}\n vol=${m.volume} active=${m.active}`);
|
|
3262
|
+
return { success: true, output: `📊 Polymarket 找到 ${markets.length} 个市场 (前 10):\n${lines.join('\n')}` };
|
|
3263
|
+
}
|
|
3264
|
+
catch (e) {
|
|
3265
|
+
return { success: false, error: `查询失败: ${String(e.message || e)}` };
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
});
|
|
3269
|
+
this.tools.set('polymarket_get_market', {
|
|
3270
|
+
name: 'polymarket_get_market',
|
|
3271
|
+
description: '获取单个 Polymarket 市场的详情 (id/conditionId 都可)',
|
|
3272
|
+
parameters: { marketId: '市场 ID (必填)' },
|
|
3273
|
+
execute: async (args) => {
|
|
3274
|
+
try {
|
|
3275
|
+
const mod = await import('../constraint-runtime/dist/tools/PolymarketSDK/getMarket.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/getMarket.js'));
|
|
3276
|
+
const fn = mod.getMarket || mod.default;
|
|
3277
|
+
const m = await fn(String(args.marketId));
|
|
3278
|
+
if (!m)
|
|
3279
|
+
return { success: false, error: '市场不存在' };
|
|
3280
|
+
return { success: true, output: `📊 市场详情:\n ${JSON.stringify(m, null, 2).substring(0, 1500)}` };
|
|
3281
|
+
}
|
|
3282
|
+
catch (e) {
|
|
3283
|
+
return { success: false, error: `查询失败: ${String(e.message || e)}` };
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
});
|
|
3287
|
+
this.tools.set('polymarket_get_orders', {
|
|
3288
|
+
name: 'polymarket_get_orders',
|
|
3289
|
+
description: '查询 Polymarket 订单 (需要 CLOB client + auth, 当前 constraint-runtime 是占位实现)',
|
|
3290
|
+
parameters: { marketId: '可选 按市场 ID 过滤' },
|
|
3291
|
+
execute: async (args) => {
|
|
3292
|
+
try {
|
|
3293
|
+
const { getOrders } = await import('../constraint-runtime/dist/tools/PolymarketSDK/getOrders.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/getOrders.js'));
|
|
3294
|
+
const orders = await getOrders(args.marketId ? { marketId: String(args.marketId) } : {});
|
|
3295
|
+
return { success: true, output: `📋 订单列表: ${JSON.stringify(orders, null, 2).substring(0, 1500)}` };
|
|
3296
|
+
}
|
|
3297
|
+
catch (e) {
|
|
3298
|
+
return { success: false, error: `查询失败: ${String(e.message || e)}` };
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
});
|
|
3302
|
+
this.tools.set('polymarket_create_order', {
|
|
3303
|
+
name: 'polymarket_create_order',
|
|
3304
|
+
description: '在 Polymarket 下单 (BUY/SELL, price 0-1, size USDC). 当前 constraint-runtime 是占位实现, 实际下单需要 CLOB client + 私钥签名',
|
|
3305
|
+
parameters: { marketId: '市场 ID (必填)', side: 'BUY 或 SELL (必填)', price: '价格 0-1 (必填)', size: '数量 USDC (必填)' },
|
|
3306
|
+
execute: async (args) => {
|
|
3307
|
+
try {
|
|
3308
|
+
const { createOrder } = await import('../constraint-runtime/dist/tools/PolymarketSDK/createOrder.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/createOrder.js'));
|
|
3309
|
+
const r = await createOrder({
|
|
3310
|
+
marketId: String(args.marketId),
|
|
3311
|
+
side: String(args.side).toUpperCase() === 'SELL' ? 'SELL' : 'BUY',
|
|
3312
|
+
price: Number(args.price),
|
|
3313
|
+
size: Number(args.size),
|
|
3314
|
+
});
|
|
3315
|
+
if (r.success)
|
|
3316
|
+
return { success: true, output: `✅ 订单: ${r.message}` };
|
|
3317
|
+
return { success: false, error: r.message, output: r.message };
|
|
3318
|
+
}
|
|
3319
|
+
catch (e) {
|
|
3320
|
+
return { success: false, error: `下单失败: ${String(e.message || e)}` };
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
});
|
|
3324
|
+
this.tools.set('polymarket_cancel_order', {
|
|
3325
|
+
name: 'polymarket_cancel_order',
|
|
3326
|
+
description: '取消 Polymarket 订单 (order ID)',
|
|
3327
|
+
parameters: { orderId: '订单 ID (必填)' },
|
|
3328
|
+
execute: async (args) => {
|
|
3329
|
+
try {
|
|
3330
|
+
const { cancelOrder } = await import('../constraint-runtime/dist/tools/PolymarketSDK/cancelOrder.js').catch(() => import('../constraint-runtime/src/tools/PolymarketSDK/cancelOrder.js'));
|
|
3331
|
+
const r = await cancelOrder({ orderId: String(args.orderId) });
|
|
3332
|
+
if (r.success)
|
|
3333
|
+
return { success: true, output: `✅ 取消订单: ${r.message}` };
|
|
3334
|
+
return { success: false, error: r.message, output: r.message };
|
|
3335
|
+
}
|
|
3336
|
+
catch (e) {
|
|
3337
|
+
return { success: false, error: `取消失败: ${String(e.message || e)}` };
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
});
|
|
3341
|
+
// === SafeSDK (多签钱包) ===
|
|
3342
|
+
this.tools.set('safe_deploy', {
|
|
3343
|
+
name: 'safe_deploy',
|
|
3344
|
+
description: '部署 Safe 多签钱包 (需要 owners/threshold 参数)',
|
|
3345
|
+
parameters: { owners: 'JSON 数组 owner 地址 (必填)', threshold: '需要几个签名 (必填, e.g. 2)' },
|
|
3346
|
+
execute: async (args) => {
|
|
3347
|
+
try {
|
|
3348
|
+
const { deploySafe } = await import('../constraint-runtime/dist/tools/SafeSDK/deploySafe.js').catch(() => import('../constraint-runtime/src/tools/SafeSDK/deploySafe.js'));
|
|
3349
|
+
const owners = Array.isArray(args.owners) ? args.owners : JSON.parse(String(args.owners));
|
|
3350
|
+
const r = await deploySafe({ owners, threshold: Number(args.threshold) });
|
|
3351
|
+
return { success: true, output: `✅ Safe 部署: ${JSON.stringify(r)}` };
|
|
3352
|
+
}
|
|
3353
|
+
catch (e) {
|
|
3354
|
+
return { success: false, error: `部署失败: ${String(e.message || e)}` };
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
});
|
|
3358
|
+
}
|
|
3110
3359
|
/**
|
|
3111
3360
|
* 2026-06-19: 工具名大小写不敏感 + Claude Code 风格别名映射
|
|
3112
3361
|
|
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
|