memory-eternal 0.10.54 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/api.js CHANGED
@@ -15,6 +15,7 @@ import { gzipSync, brotliCompressSync, constants as zlibConstants } from 'node:z
15
15
  import os from 'node:os'
16
16
  import fs, { readFileSync } from 'node:fs'
17
17
  import path from 'node:path'
18
+ import { spawn } from 'node:child_process'
18
19
  import { listCards, countCards, readCard, search, searchAll, graph, graphAll, overview, exportCards, deleteCard, writeCard, parseCard, stats, optimizeCandidates, optimizeApply, addFeedback, dailyCounts, mergeCards, ensureVault, auditQueue, recycleList, purgeExpired, setCardStatus, restoreCard, mainStoreBodies } from './vault.js'
19
20
  import { compressExcerpt, DEFAULT_CAPTURE_MAX_TOKENS } from './capture.js'
20
21
  import { buildDiagnostics } from './feedback.js'
@@ -30,6 +31,26 @@ export function broadcast(event, data) { return hub.broadcast(event, data) }
30
31
 
31
32
  export const API_PREFIX = '/memory-eternal/api'
32
33
 
34
+ // 浏览器/Electron 里 target=_blank 可能被宿主拦截;由本机 Node 进程调用系统默认浏览器。
35
+ // 只允许项目自己的 GitHub 地址,避免变成任意 URL 打开器。
36
+ function openExternalUrl(raw) {
37
+ let u
38
+ try { u = new URL(String(raw || '')) } catch { return { ok: false, error: 'bad url' } }
39
+ const allowed = u.protocol === 'https:' && u.hostname === 'github.com' && u.pathname.startsWith('/EternalNight996/memory-eternal')
40
+ if (!allowed) return { ok: false, error: 'url not allowed' }
41
+ try {
42
+ let child
43
+ if (process.platform === 'win32') child = spawn('rundll32', ['url.dll,FileProtocolHandler', u.href], { detached: true, stdio: 'ignore', windowsHide: true })
44
+ else if (process.platform === 'darwin') child = spawn('open', [u.href], { detached: true, stdio: 'ignore' })
45
+ else child = spawn('xdg-open', [u.href], { detached: true, stdio: 'ignore' })
46
+ child.on('error', () => {})
47
+ child.unref()
48
+ return { ok: true, url: u.href }
49
+ } catch (e) {
50
+ return { ok: false, error: String((e && e.message) || e) }
51
+ }
52
+ }
53
+
33
54
  // npm 最新版本缓存(避免每次点「检查更新」都打 registry)
34
55
  const versionCache = { at: 0, latest: '' }
35
56
 
@@ -477,6 +498,10 @@ export function createApi(deps = {}) {
477
498
  }, 300)
478
499
  return
479
500
  }
501
+ case '/open-external': {
502
+ if (req.method && req.method !== 'POST') return json(res, 405, { ok: false, error: 'method not allowed' })
503
+ return json(res, 200, openExternalUrl(query.get('url')))
504
+ }
480
505
  case '/diagnostics': {
481
506
  // 「反馈异常」用:返回一段可直接贴进 issue 的**已脱敏**诊断文本。
482
507
  // 宿主与独立 web 共用这个实现;home 目录、各类 key/token 全都会被替换掉。