@a9i5k4/dsh-auto-memory 0.1.9 → 0.1.10
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/README.md +13 -0
- package/README.zh-CN.md +13 -0
- package/lib/client.js +41 -0
- package/lib/index.js +113 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -204,6 +204,19 @@ Restart **dsh web** to activate (the 「Memory」entry appears in the sidebar).
|
|
|
204
204
|
|
|
205
205
|
> No pnpm? Use npm: `npm install @a9i5k4/dsh-auto-memory`
|
|
206
206
|
|
|
207
|
+
## Updating (check & upgrade)
|
|
208
|
+
|
|
209
|
+
The plugin is a normal npm package, so updating is one command in the same profile directory:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
cd ~/.dsh/profiles/web
|
|
213
|
+
pnpm up @a9i5k4/dsh-auto-memory # or: npm install @a9i5k4/dsh-auto-memory@latest
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Then restart **dsh web**.
|
|
217
|
+
|
|
218
|
+
The Settings → Auto Memory page has a **Check for updates** button that compares your installed version with the latest one on the npm registry (it shows the update command when a newer version exists).
|
|
219
|
+
|
|
207
220
|
---
|
|
208
221
|
|
|
209
222
|
## AI-Era Installation (copy-paste for your AI)
|
package/README.zh-CN.md
CHANGED
|
@@ -202,6 +202,19 @@ pnpm add @a9i5k4/dsh-auto-memory
|
|
|
202
202
|
|
|
203
203
|
> 没有 pnpm?用 npm 也行:`npm install @a9i5k4/dsh-auto-memory`
|
|
204
204
|
|
|
205
|
+
## 更新(检查与升级)
|
|
206
|
+
|
|
207
|
+
插件就是普通的 npm 包,更新同样是在 profile 目录里一条命令:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
cd ~/.dsh/profiles/web
|
|
211
|
+
pnpm up @a9i5k4/dsh-auto-memory # 或: npm install @a9i5k4/dsh-auto-memory@latest
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
然后**重启 dsh web** 生效。
|
|
215
|
+
|
|
216
|
+
设置 → 自动记忆 页面有「检查更新」按钮,会拿你当前安装的版本和 npm registry 上的最新版对比(有新版时直接显示更新命令)。
|
|
217
|
+
|
|
205
218
|
---
|
|
206
219
|
|
|
207
220
|
## AI 时代安装(把这句话直接丢给 AI)
|
package/lib/client.js
CHANGED
|
@@ -105,6 +105,8 @@ window.__ModuleLoader__.load({
|
|
|
105
105
|
fBrowse: '浏览…', browseTitle: '选择记忆根目录', fUp: '上级', fSelectDir: '选择此目录',
|
|
106
106
|
pickedDir: '已选择目录:', rememberSave: '(点击「保存设置」后生效)', pickerUnavailable: '系统文件夹选择器不可用,改用内嵌浏览。',
|
|
107
107
|
fDayBoundary: '日界(分钟)', fDayBoundaryHint: '从 0 点起算:凌晨在此之前的活儿归前一天。默认 450=早上 7:30 进入新一天;改 480=8:00;0=按午夜切日。',
|
|
108
|
+
fVersion: '插件版本', checkUpdate: '检查更新', checking: '检查中…', upToDate: '(已是最新)', hasUpdate: '(有新版本)', versionError: '版本检查失败: ', versionCmdHint: '更新命令: cd ~/.dsh/profiles/web && pnpm up @a9i5k4/dsh-auto-memory,然后重启 dsh web 生效。',
|
|
109
|
+
updateNow: '一键更新', updating: '更新中…', updateDone: '更新完成,请重启 dsh web 生效。', updateFailed: '更新失败: ', devLinkHint: '(当前为本地开发链接,请直接同步开发源码)', noProfileHint: '(未找到 dsh profile,无法自动更新)',
|
|
108
110
|
refresh: '刷新', refreshing: '刷新中…', close: '关闭', dragMove: '拖动移动', dragResize: '拖动调整大小', resetPos: '恢复默认位置',
|
|
109
111
|
generated: '已生成', failed: '失败: ',
|
|
110
112
|
generatedAt: '生成于 ',
|
|
@@ -178,6 +180,8 @@ window.__ModuleLoader__.load({
|
|
|
178
180
|
fBrowse: 'Browse…', browseTitle: 'Choose memory root', fUp: 'Up', fSelectDir: 'Select this folder',
|
|
179
181
|
pickedDir: 'Selected folder:', rememberSave: '(click Save to apply)', pickerUnavailable: 'Native folder picker unavailable, falling back to in-app browser.',
|
|
180
182
|
fDayBoundary: 'Day boundary (minutes)', fDayBoundaryHint: 'Minutes from midnight: work before it counts as the previous day. Default 450 = 7:30 AM starts the new day; 480 = 8:00; 0 = midnight cut.',
|
|
183
|
+
fVersion: 'Plugin version', checkUpdate: 'Check for updates', checking: 'Checking…', upToDate: '(up to date)', hasUpdate: '(update available)', versionError: 'Version check failed: ', versionCmdHint: 'Update: cd ~/.dsh/profiles/web && pnpm up @a9i5k4/dsh-auto-memory, then restart dsh web.',
|
|
184
|
+
updateNow: 'Update now', updating: 'Updating…', updateDone: 'Update finished, restart dsh web to apply.', updateFailed: 'Update failed: ', devLinkHint: '(local dev link, sync source instead)', noProfileHint: '(no dsh profile found, cannot auto-update)',
|
|
181
185
|
refresh: 'Refresh', refreshing: 'Refreshing…', close: 'Close', dragMove: 'Drag to move', dragResize: 'Drag to resize', resetPos: 'Reset position',
|
|
182
186
|
generated: 'Generated', failed: 'Failed: ',
|
|
183
187
|
generatedAt: 'Generated ',
|
|
@@ -272,6 +276,8 @@ window.__ModuleLoader__.load({
|
|
|
272
276
|
debug: '/api/dsh-auto-memory/debug',
|
|
273
277
|
browseDir: '/api/dsh-auto-memory/browse-dir',
|
|
274
278
|
pickDir: '/api/dsh-auto-memory/pick-dir',
|
|
279
|
+
updateCheck: '/api/dsh-auto-memory/update-check',
|
|
280
|
+
update: '/api/dsh-auto-memory/update',
|
|
275
281
|
config: '/api/dsh-auto-memory/config',
|
|
276
282
|
reflect: '/api/dsh-auto-memory/reflect',
|
|
277
283
|
reflectAuto: '/api/dsh-auto-memory/reflect-auto',
|
|
@@ -1244,10 +1250,39 @@ window.__ModuleLoader__.load({
|
|
|
1244
1250
|
useEffect(function () {
|
|
1245
1251
|
var alive = true
|
|
1246
1252
|
apiGet(API.config).then(function (d) { if (alive) setCfg(d.config) }).catch(function (e) { setErr(e.message) })
|
|
1253
|
+
// 打开设置页自动检查更新(host 有 12h 缓存,不重复查网)
|
|
1254
|
+
apiGet(API.updateCheck).then(function (d) { if (alive) setVerInfo(d) }).catch(function () {})
|
|
1247
1255
|
return function () { alive = false }
|
|
1248
1256
|
}, [])
|
|
1249
1257
|
if (!cfg) return err ? h('div', { 'data-dam-error': '' }, err) : h(Loading)
|
|
1250
1258
|
function set(key, value) { var next = Object.assign({}, cfg); next[key] = value; setCfg(next) }
|
|
1259
|
+
var verPair = useState(null)
|
|
1260
|
+
var verInfo = verPair[0]
|
|
1261
|
+
var setVerInfo = verPair[1]
|
|
1262
|
+
var checkingPair = useState(false)
|
|
1263
|
+
var checkingUpdate = checkingPair[0]
|
|
1264
|
+
var setCheckingUpdate = checkingPair[1]
|
|
1265
|
+
function checkUpdate() {
|
|
1266
|
+
if (checkingUpdate) return
|
|
1267
|
+
setCheckingUpdate(true)
|
|
1268
|
+
apiGet(API.updateCheck + '?force=1').then(function (d) { setVerInfo(d); setCheckingUpdate(false) })
|
|
1269
|
+
.catch(function (e) { setVerInfo({ error: e.message }); setCheckingUpdate(false) })
|
|
1270
|
+
}
|
|
1271
|
+
var upBusyPair = useState(false)
|
|
1272
|
+
var upBusy = upBusyPair[0]
|
|
1273
|
+
var setUpBusy = upBusyPair[1]
|
|
1274
|
+
var upMsgPair = useState('')
|
|
1275
|
+
var upMsg = upMsgPair[0]
|
|
1276
|
+
var setUpMsg = upMsgPair[1]
|
|
1277
|
+
function doUpdate() {
|
|
1278
|
+
if (upBusy) return
|
|
1279
|
+
setUpBusy(true); setUpMsg('')
|
|
1280
|
+
apiPost(API.update, {}).then(function (d) {
|
|
1281
|
+
setUpBusy(false)
|
|
1282
|
+
if (d && d.ok) { setUpMsg(t('updateDone')); setVerInfo(null); checkUpdate() }
|
|
1283
|
+
else setUpMsg(t('updateFailed') + (d && d.message ? d.message : '(未知)'))
|
|
1284
|
+
}).catch(function (e) { setUpBusy(false); setUpMsg(t('updateFailed') + e.message) })
|
|
1285
|
+
}
|
|
1251
1286
|
function save() {
|
|
1252
1287
|
if (busy) return
|
|
1253
1288
|
setBusy(true); setMsg(''); setErr('')
|
|
@@ -1289,6 +1324,12 @@ window.__ModuleLoader__.load({
|
|
|
1289
1324
|
field(t('fReflect'), h('input', { type: 'checkbox', checked: !!cfg.reflectEnabled, onChange: function (e) { set('reflectEnabled', e.target.checked) } }), t('fReflectHint')),
|
|
1290
1325
|
field(t('fStyle'), h('select', { 'data-dam-select': '', value: cfg.reflectStyle, onChange: function (e) { set('reflectStyle', e.target.value) } },
|
|
1291
1326
|
STYLE_OPTIONS.map(function (o) { return h('option', { key: o[0], value: o[0] }, o[1]) })), t('fStyleHint')),
|
|
1327
|
+
field(t('fVersion'), h('div', { 'data-dam-row': '' },
|
|
1328
|
+
h('span', { style: { flex: 1 } }, verInfo ? (verInfo.current || '?') + (verInfo.latest ? ' → ' + verInfo.latest + (verInfo.upToDate ? ' ' + t('upToDate') : ' ' + t('hasUpdate')) : '') : (checkingUpdate ? t('checking') : '—')),
|
|
1329
|
+
h('button', { 'data-dam-btn': '', onClick: checkUpdate, disabled: checkingUpdate }, checkingUpdate ? t('checking') : t('checkUpdate')),
|
|
1330
|
+
(verInfo && verInfo.latest && !verInfo.upToDate && verInfo.installKind === 'registry') ? h('button', { 'data-dam-btn': '', onClick: doUpdate, disabled: upBusy, style: { marginLeft: '4px' } }, upBusy ? t('updating') : t('updateNow')) : null,
|
|
1331
|
+
upMsg ? h('span', { 'data-dam-hint': '' }, upMsg) : null),
|
|
1332
|
+
t('versionCmdHint') + (verInfo && verInfo.error ? ' ' + t('versionError') + verInfo.error : '') + (verInfo && verInfo.installKind === 'dev-link' ? ' ' + t('devLinkHint') : '') + (verInfo && !verInfo.installKind ? ' ' + t('noProfileHint') : '')),
|
|
1292
1333
|
h('div', { 'data-dam-row': '' },
|
|
1293
1334
|
h('button', { 'data-dam-btn': '', onClick: save, disabled: busy }, busy ? t('saving') : t('saveSettings')),
|
|
1294
1335
|
msg ? h('span', null, msg) : null),
|
package/lib/index.js
CHANGED
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
|
|
20
20
|
import { readFile, writeFile, mkdir, readdir, stat, rm, copyFile } from 'node:fs/promises'
|
|
21
21
|
import { createReadStream, existsSync } from 'node:fs'
|
|
22
|
+
import { exec as cpExec } from 'node:child_process'
|
|
23
|
+
import { promisify } from 'node:util'
|
|
24
|
+
const execP = promisify(cpExec)
|
|
22
25
|
import { homedir } from 'node:os'
|
|
23
26
|
import path from 'node:path'
|
|
24
27
|
import { fileURLToPath } from 'node:url'
|
|
@@ -46,6 +49,8 @@ export const API = {
|
|
|
46
49
|
debug: '/api/dsh-auto-memory/debug',
|
|
47
50
|
browseDir: '/api/dsh-auto-memory/browse-dir',
|
|
48
51
|
pickDir: '/api/dsh-auto-memory/pick-dir',
|
|
52
|
+
updateCheck: '/api/dsh-auto-memory/update-check',
|
|
53
|
+
update: '/api/dsh-auto-memory/update',
|
|
49
54
|
config: '/api/dsh-auto-memory/config',
|
|
50
55
|
reflect: '/api/dsh-auto-memory/reflect',
|
|
51
56
|
'reflect-auto': '/api/dsh-auto-memory/reflect-auto',
|
|
@@ -275,6 +280,73 @@ class MemoryEngine {
|
|
|
275
280
|
}
|
|
276
281
|
}
|
|
277
282
|
|
|
283
|
+
// ---------- 更新检查(registry 自动检查 + profile 探测) ----------
|
|
284
|
+
/** 找 dsh web profile 目录(含 dsh-auto-memory 依赖/bundle 的那个)。 */
|
|
285
|
+
async findProfileDir() {
|
|
286
|
+
try {
|
|
287
|
+
const base = path.join(dshHome(), 'profiles')
|
|
288
|
+
const dirs = await readdir(base, { withFileTypes: true })
|
|
289
|
+
for (const d of dirs) {
|
|
290
|
+
if (!d.isDirectory()) continue
|
|
291
|
+
const pkgFile = path.join(base, d.name, 'package.json')
|
|
292
|
+
const raw = await this.readTextSafe(pkgFile)
|
|
293
|
+
if (!raw) continue
|
|
294
|
+
let pkg
|
|
295
|
+
try { pkg = JSON.parse(raw) } catch (e) { continue }
|
|
296
|
+
const deps = (pkg && pkg.dependencies) || {}
|
|
297
|
+
const bundles = (pkg && pkg.dsh && pkg.dsh.profile && pkg.dsh.profile.bundles) || []
|
|
298
|
+
const key = Object.keys(deps).find((k) => k.includes('dsh-auto-memory'))
|
|
299
|
+
const isLink = !!(key && typeof deps[key] === 'string' && deps[key].startsWith('link:'))
|
|
300
|
+
const isReg = !!deps['@a9i5k4/dsh-auto-memory'] || bundles.indexOf('@a9i5k4/dsh-auto-memory') >= 0
|
|
301
|
+
if (key || bundles.some((b) => b.includes('dsh-auto-memory'))) {
|
|
302
|
+
return {
|
|
303
|
+
dir: path.join(base, d.name),
|
|
304
|
+
installKind: isLink ? 'dev-link' : (isReg ? 'registry' : 'unknown'),
|
|
305
|
+
usesPnpm: existsSync(path.join(base, d.name, 'pnpm-lock.yaml')),
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
} catch (e) {}
|
|
310
|
+
return null
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** 对比本地版本与 npm registry 最新版;结果缓存 12 小时(启动/设置页打开都走缓存,不重复查网)。 */
|
|
314
|
+
async checkUpdate(force) {
|
|
315
|
+
const cacheFile = path.join(dshHome(), 'memory', 'update-check.json')
|
|
316
|
+
const base = { current: '', installKind: 'unknown' }
|
|
317
|
+
try {
|
|
318
|
+
const indexPath = fileURLToPath(import.meta.url)
|
|
319
|
+
const pkgPath = path.join(path.dirname(indexPath), '..', 'package.json')
|
|
320
|
+
const raw = await this.readTextSafe(pkgPath)
|
|
321
|
+
if (raw) { try { base.current = JSON.parse(raw).version || '' } catch (e) {} }
|
|
322
|
+
} catch (e) {}
|
|
323
|
+
const prof = await this.findProfileDir()
|
|
324
|
+
if (prof) base.installKind = prof.installKind
|
|
325
|
+
if (!force) {
|
|
326
|
+
try {
|
|
327
|
+
const cached = await this.readTextSafe(cacheFile)
|
|
328
|
+
if (cached) {
|
|
329
|
+
const j = JSON.parse(cached)
|
|
330
|
+
if (j && j.checkedAt && Date.now() - j.checkedAt < 12 * 3600 * 1000) return Object.assign({}, j, { fromCache: true })
|
|
331
|
+
}
|
|
332
|
+
} catch (e) {}
|
|
333
|
+
}
|
|
334
|
+
const result = Object.assign({}, base, { checkedAt: Date.now(), fromCache: false, latest: '', upToDate: false, error: '' })
|
|
335
|
+
try {
|
|
336
|
+
const rr = await fetch('https://registry.npmjs.org/@a9i5k4%2Fdsh-auto-memory', { signal: AbortSignal.timeout(8000) })
|
|
337
|
+
if (rr.ok) {
|
|
338
|
+
const j = await rr.json()
|
|
339
|
+
result.latest = (j && j['dist-tags'] && j['dist-tags'].latest) || ''
|
|
340
|
+
result.upToDate = !!(result.current && result.latest && result.current === result.latest)
|
|
341
|
+
} else result.error = 'registry HTTP ' + rr.status
|
|
342
|
+
} catch (e) { result.error = String(e && e.message ? e.message : e) }
|
|
343
|
+
try {
|
|
344
|
+
await mkdir(path.dirname(cacheFile), { recursive: true })
|
|
345
|
+
await writeFile(cacheFile, JSON.stringify(result, null, 2), 'utf8')
|
|
346
|
+
} catch (e) {}
|
|
347
|
+
return result
|
|
348
|
+
}
|
|
349
|
+
|
|
278
350
|
// ---------- 读取 ----------
|
|
279
351
|
async readTextSafe(p) {
|
|
280
352
|
if (!p) return ''
|
|
@@ -2052,6 +2124,8 @@ export function apply(ctx, config) {
|
|
|
2052
2124
|
// 生命周期刷新
|
|
2053
2125
|
const refreshAll = (agent) => { void engine.refresh(agent) }
|
|
2054
2126
|
refreshAll()
|
|
2127
|
+
// 自动检查更新:host 启动时查一次 npm registry(结果缓存 12 小时,设置页打开直接读缓存显示)
|
|
2128
|
+
void engine.checkUpdate(false)
|
|
2055
2129
|
ctx.on('agent/session-start', (payload) => {
|
|
2056
2130
|
refreshAll(payload && payload.agent)
|
|
2057
2131
|
// 缓存最近 agent 引用(subagent parent 需要完整 agent 对象)
|
|
@@ -2390,6 +2464,45 @@ export function apply(ctx, config) {
|
|
|
2390
2464
|
} catch (e) { writeJson(res, 200, { native: true, dir: null, error: String(e && e.message ? e.message : e) }) }
|
|
2391
2465
|
},
|
|
2392
2466
|
},
|
|
2467
|
+
{
|
|
2468
|
+
kind: 'exact',
|
|
2469
|
+
path: API.updateCheck,
|
|
2470
|
+
handler: async (req, res) => {
|
|
2471
|
+
if (!isLoopbackRequest(req)) return writeJson(res, 403, { error: 'forbidden: loopback-only' })
|
|
2472
|
+
if ((req.method || 'GET') !== 'GET') return writeJson(res, 405, { error: 'method not allowed' })
|
|
2473
|
+
try {
|
|
2474
|
+
const url = new URL(req.url || '/', 'http://localhost')
|
|
2475
|
+
const force = url.searchParams.get('force') === '1'
|
|
2476
|
+
writeJson(res, 200, await engine.checkUpdate(force))
|
|
2477
|
+
} catch (e) { writeJson(res, 500, { error: String(e && e.message ? e.message : e) }) }
|
|
2478
|
+
},
|
|
2479
|
+
},
|
|
2480
|
+
{
|
|
2481
|
+
kind: 'exact',
|
|
2482
|
+
path: API.update,
|
|
2483
|
+
handler: async (req, res) => {
|
|
2484
|
+
if (!isLoopbackRequest(req)) return writeJson(res, 403, { error: 'forbidden: loopback-only' })
|
|
2485
|
+
if ((req.method || 'POST') !== 'POST') return writeJson(res, 405, { error: 'method not allowed' })
|
|
2486
|
+
// 一键更新:在 profile 目录执行 pnpm up / npm install,完成后提示重启
|
|
2487
|
+
try {
|
|
2488
|
+
const prof = await engine.findProfileDir()
|
|
2489
|
+
if (!prof) return writeJson(res, 200, { ok: false, message: '未找到 dsh profile 目录(未安装插件或已删除)。' })
|
|
2490
|
+
if (prof.installKind === 'dev-link') return writeJson(res, 200, { ok: false, message: '当前为本地开发链接(link:)安装,不适用 npm 更新,请直接同步开发源码。' })
|
|
2491
|
+
if (prof.installKind !== 'registry') return writeJson(res, 200, { ok: false, message: '未检测到 registry 安装方式,无法自动更新。' })
|
|
2492
|
+
const cmd = prof.usesPnpm ? 'pnpm up @a9i5k4/dsh-auto-memory' : 'npm install @a9i5k4/dsh-auto-memory@latest'
|
|
2493
|
+
const out = await execP(cmd, { cwd: prof.dir, timeout: 120000, windowsHide: true })
|
|
2494
|
+
const tail = ((out.stdout || '') + (out.stderr || '')).trim().slice(-1200)
|
|
2495
|
+
writeJson(res, 200, {
|
|
2496
|
+
ok: true,
|
|
2497
|
+
output: tail || '(无输出)',
|
|
2498
|
+
restartHint: '更新完成,请重启 dsh web 生效。',
|
|
2499
|
+
installKind: prof.installKind,
|
|
2500
|
+
})
|
|
2501
|
+
} catch (e) {
|
|
2502
|
+
writeJson(res, 200, { ok: false, message: String(e && e.message ? e.message : e) })
|
|
2503
|
+
}
|
|
2504
|
+
},
|
|
2505
|
+
},
|
|
2393
2506
|
{
|
|
2394
2507
|
kind: 'exact',
|
|
2395
2508
|
path: API.config,
|
package/package.json
CHANGED