@yottameta/yotta-memory 0.6.3 → 0.6.4

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 CHANGED
@@ -43,7 +43,7 @@
43
43
  | **越用越懂(v0.6.0)** | `profile` 画像聚合(零推断)+ `context` 开工上下文包(身份 / 画像 / 近期记忆 / 边界 / 承诺)+ SKILL「记忆守则」规则层,记忆随使用成长 |
44
44
  | **生态分发** | GitHub + npm 双源同步发布;npx skills / npm / install.sh 三种方式,覆盖 17+ 类智能体目录 |
45
45
  | **便携记忆盘(随盘走)** | 记忆库即引擎:装在硬盘 / 主机上,插上即恢复全部记忆;引擎主机只需装 CLI 当存放点,无需装任何 AI 智能体 |
46
- | **局域网共享与自启** | 每智能体独立 token(Bearer + X-Agent-Id)鉴权、可吊销;`lan enable` 注册开机自启(Windows:优先计划任务;非管理员自动降级用户级 Startup 静默自启);MCP 工具集与 CLI 一致(8 个工具),管理动作不远程暴露 |
46
+ | **局域网共享与自启** | 每智能体独立 token(Bearer + X-Agent-Id)鉴权、可吊销;`lan enable` 注册开机自启(Windows:优先计划任务,非管理员自动降级用户级 Startup 静默自启;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot);MCP 工具集与 CLI 一致(8 个工具),管理动作不远程暴露 |
47
47
  | **本地 / 局域网双模式** | 本地 `serve --stdio` 零进程、按需拉起(无常驻);局域网 streamable HTTP 常驻——两种模式可并存、按需选用 |
48
48
 
49
49
  ## 功能详解
@@ -115,7 +115,7 @@
115
115
 
116
116
  - **记忆库即引擎**:`serve` 把记忆目录挂成 MCP 服务,目录随盘走;引擎主机只需装 CLI 当存放点,无需装任何 AI 智能体。
117
117
  - **双模式可并存**:本地 `serve --stdio` 零进程(客户端按需拉起);局域网 streamable HTTP(默认 `0.0.0.0:8787`)+ 每智能体 token 鉴权。
118
- - **开机自启**:`lan enable` 注册 Windows 开机自启——优先计划任务(默认登录自启,`--onstart` 开机即启需管理员);非管理员(计划任务被拒)自动降级为**用户级 Startup 静默自启**(免管理员),启动脚本(VBS)内联启动命令,即使启动文件被外部清理也会在开机时自动重建(v0.6.3 自愈,不再出现 80070002 弹窗);`lan disable` 移除,`lan status` 查询。
118
+ - **开机自启**:`lan enable` 注册开机自启——Windows 优先计划任务(默认登录自启,`--onstart` 开机即启需管理员),非管理员自动降级为**用户级 Startup 静默自启**(免管理员,启动脚本内联启动命令、被清理也会在开机时自动重建,v0.6.3 起不再弹 80070002);Linux 优先 **systemd 用户单元**(`systemctl --user`,登录自启;`--onstart` 附加 `loginctl enable-linger` 开机即启),systemd 不可用时自动降级**用户 crontab @reboot**(v0.6.4);`lan disable` 移除,`lan status` 查询。
119
119
  - **安全边界**:管理动作(init / config / token / lan / serve)不进 MCP,token 不远程暴露;远程智能体只能读写记忆,不能改配置、不能管 token。
120
120
  - 完整操作步骤见上文「局域网多机共享」章节与 [USER_GUIDE.md](USER_GUIDE.md)。
121
121
 
@@ -196,7 +196,7 @@ npm i -g @yottameta/yotta-memory
196
196
  | `yotta-memory iam <id> [--name <显示名>] [--user <用户名>] [--relationship <关系>] [--force]` | 登记本智能体唯一身份并自动落自我档案(`agents.json`,ID 必须唯一;可选扩展显示名 / 用户 / 关系)|
197
197
  | `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | 为智能体生成 / 列出 / 吊销访问 token(登记于记忆库 `.server/tokens.json`;同 ID 已被其它来源占用需 `--force` 覆盖,防不同智能体合流)|
198
198
  | `yotta-memory serve [--host 0.0.0.0] [--port 8787] [--no-auth] [--stdio]` | 启动 MCP 记忆引擎(streamable HTTP 局域网 / --stdio 本地零进程模式;Bearer token + X-Agent-Id 鉴权)|
199
- | `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务,默认 ONLOGON 登录自启、--onstart 开机即启需管理员;非管理员自动降级用户级 Startup 静默自启)|
199
+ | `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务,默认 ONLOGON、--onstart 开机即启需管理员,非管理员自动降级用户级 Startup 静默自启;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot)|
200
200
 
201
201
  类型:`FACT`(事实,公共共享)/ `PREF`(偏好)/ `BOUND`(边界)/ `COMMIT`(承诺)。
202
202
 
@@ -239,7 +239,7 @@ yotta-memory recall --type FACT --limit 10
239
239
  3. 启动服务(默认监听 0.0.0.0:8787,Bearer token + X-Agent-Id 鉴权)——临时运行或注册开机自启二选一:
240
240
  ```bash
241
241
  yotta-memory serve # 临时前台运行
242
- yotta-memory lan enable # 注册开机自启(Windows:计划任务;非管理员自动降级用户级 Startup 静默自启)
242
+ yotta-memory lan enable # 注册开机自启(Windows:计划任务/用户级 Startup;Linux:systemd 用户单元/用户 crontab)
243
243
  yotta-memory lan status # 查看自启状态
244
244
  ```
245
245
  > `lan enable --onstart` 改为开机即启(需管理员);非管理员时 `lan enable` 自动改用用户级 Startup 静默自启(无需管理员);`lan disable` 移除自启。
package/SKILL.md CHANGED
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  name: yotta-memory
3
3
  description: "元忆 —— 有权限边界的文件式智能体记忆。文件式、零依赖、可 diff/回滚:让任何 AI 智能体活过会话,开工 recall 恢复上下文、重要信息 remember 落盘、收工归档。类型体系 FACT(公共共享)/ PREF / BOUND / COMMIT(私密隔离)。触发:记住、别忘了、记一笔、记忆、remember、recall、跨会话、上次说到、续测、交接、归档、记忆盘、共享记忆、局域网记忆、画像、开工上下文、记忆守则、profile、context、越用越懂"
4
- version: 0.6.3
4
+ version: 0.6.4
5
5
  license: MIT
6
6
  ---
7
7
 
8
8
  # yotta-memory(元忆)— 有权限边界的文件式智能体记忆
9
9
 
10
10
  > 一句话:元忆 —— 有权限边界的文件式智能体记忆(不注入、可 diff、能回滚;FACT 共享、PREF / BOUND / COMMIT 私密隔离)。
11
- > 版本:0.6.3 | 最后更新:2026-08-26
11
+ > 版本:0.6.4 | 最后更新:2026-08-26
12
12
 
13
13
  ## 这是什么
14
14
 
@@ -158,7 +158,7 @@ license: MIT
158
158
  | `yotta-memory iam <id> [--name <显示名>] [--user <用户名>] [--relationship <关系>] [--force]` | 登记本智能体唯一身份并自动落自我档案(`agents.json`,ID 必须唯一;可选扩展显示名 / 用户 / 关系)|
159
159
  | `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | 每智能体访问 token:生成 / 列出 / 吊销(登记 `<记忆库>/.server/tokens.json`;同 ID 已被其它来源占用需 `--force` 覆盖,防不同智能体合流)|
160
160
  | `yotta-memory serve [--host 0.0.0.0] [--port 8787] [--no-auth] [--stdio]` | 启动 MCP 记忆引擎(streamable HTTP 局域网 / --stdio 本地零进程模式;Bearer token + X-Agent-Id 鉴权)|
161
- | `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务,默认 ONLOGON 登录自启、--onstart 开机即启需管理员;非管理员自动降级用户级 Startup 静默自启;v0.6.3 起 VBS 自愈:内联启动命令,.cmd 缺失/被清理时开机自动重建,不再弹 80070002)|
161
+ | `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务,默认 ONLOGON、--onstart 开机即启需管理员,非管理员自动降级用户级 Startup 静默自启,v0.6.3 起 VBS 自愈不弹 80070002;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot)|
162
162
 
163
163
  ## 存储格式(摘要)
164
164
 
package/USER_GUIDE.md CHANGED
@@ -131,25 +131,16 @@ yotta-memory lan enable --onstart # 开机即启(需管理员)
131
131
  yotta-memory lan status # 查看状态
132
132
  yotta-memory lan disable # 取消
133
133
 
134
- # Linux:用 systemd 开机自启(lan 命令当前仅支持 Windows)
135
- # 先查 yotta-memory 实际路径:which yotta-memory(npm 全局默认 /usr/local/bin/yotta-memory)
136
- sudo tee /etc/systemd/system/yotta-memory.service > /dev/null <<'EOF'
137
- [Unit]
138
- Description=yotta-memory memory engine
139
- After=network.target
140
-
141
- [Service]
142
- ExecStart=/usr/local/bin/yotta-memory serve --host 0.0.0.0 --port 8787
143
- Restart=on-failure
144
-
145
- [Install]
146
- WantedBy=multi-user.target
147
- EOF
148
- sudo systemctl daemon-reload
149
- sudo systemctl enable --now yotta-memory
150
- systemctl status yotta-memory # 查看状态
134
+ # Linux:内置命令(v0.6.4 起)——优先 systemd 用户单元(登录自启);
135
+ # systemd 不可用时自动降级用户 crontab @reboot(开机自启,免管理员)
136
+ yotta-memory lan enable # 登录后自动启动(默认)
137
+ yotta-memory lan enable --onstart # 开机即启(无需登录,需系统支持 loginctl)
138
+ yotta-memory lan status # 查看状态
139
+ yotta-memory lan disable # 取消
151
140
  ```
152
141
 
142
+ > 说明:`lan enable` 默认「登录后自启」;如需系统级(整机、开机即启)服务,可手动创建 `/etc/systemd/system/yotta-memory.service`(ExecStart 指向 `which yotta-memory` 实际路径)并 `sudo systemctl enable --now yotta-memory`;桌面环境也可在 `~/.config/autostart/` 放置 `yotta-memory.desktop` 实现图形登录自启。
143
+
153
144
  **第 4 步:为局域网其它主机的智能体生成访问 token**
154
145
 
155
146
  ```bash
@@ -258,7 +249,7 @@ yotta-memory remember FACT 主题 内容 # 智能体落盘
258
249
  | `yotta-memory iam <id> [--name <显示名>] [--user <用户名>] [--relationship <关系>] [--force]` | 登记本智能体唯一身份并自动落自我档案(`agents.json`,ID 必须唯一;可选扩展显示名 / 用户 / 关系)|
259
250
  | `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | 访问 token(同 ID 已被其它来源占用需 `--force` 覆盖)|
260
251
  | `yotta-memory serve [--port 8787] [--stdio] [--no-auth]` | 启动记忆引擎(--no-auth 关闭鉴权,仅限可信内网)|
261
- | `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务;非管理员自动降级用户级 Startup 静默自启)|
252
+ | `yotta-memory lan enable [--onstart] / disable / status` | 开机自启管理(Windows:计划任务/用户级 Startup 静默自启;Linux:systemd 用户单元/用户 crontab @reboot)|
262
253
 
263
254
  类型:`FACT`(事实,共享)/ `PREF`(偏好)/ `BOUND`(边界)/ `COMMIT`(承诺),后三类按智能体物理分目录隔离(`private/<owner>/<type>/`)。
264
255
 
@@ -266,7 +257,7 @@ yotta-memory remember FACT 主题 内容 # 智能体落盘
266
257
 
267
258
  **远程连不上时,按顺序检查:**
268
259
 
269
- 1. 引擎在运行吗?Windows:`yotta-memory lan status`;Linux(systemd 自启时):`systemctl status yotta-memory`,或 `ps aux | grep yotta-memory`;也可直接 `yotta-memory --version`。
260
+ 1. 引擎在运行吗?`yotta-memory lan status`(Windows / Linux 通用);Linux 也可 `systemctl --user status yotta-memory-serve.service`(systemd 自启时)或 `ps aux | grep yotta-memory`;也可直接 `yotta-memory --version`。
270
261
  2. IP 对吗?引擎主机执行 `hostname -I`(Linux)或 `ipconfig`(Windows)确认。
271
262
  3. 端口对吗?默认 8787,两端要一致。
272
263
  4. 防火墙放行了吗?Linux:`sudo ufw status`,未放行则 `sudo ufw allow 8787/tcp`;Windows:允许首次监听的入站请求。
@@ -281,7 +272,7 @@ yotta-memory remember FACT 主题 内容 # 智能体落盘
281
272
 
282
273
  **recall 没有结果:** 关键词太细或确实没有这条记忆;先 `recall`(不带关键词)看库里的全部记忆核对。
283
274
 
284
- **systemd 自启的引擎看日志:** `journalctl -u yotta-memory -n 50`。
275
+ **systemd 自启的引擎看日志:** `journalctl --user -u yotta-memory-serve.service -n 50`(系统级手动服务为 `journalctl -u yotta-memory -n 50`)。
285
276
 
286
277
  **记错位置了?** `yotta-memory config get` 查看当前生效位置;`config set memory_home <正确目录>` 改正。
287
278
 
@@ -8,6 +8,7 @@
8
8
  // v0.6.0 新增:灵魂盘核心——profile(用户画像聚合,零推断)/ context(开工上下文包)/ iam 扩展(--name/--user/--relationship)/ remember --verify(写后回读)与 --no-hint(关闭类型启发式提示)+ SKILL「记忆守则」
9
9
  // v0.6.1 新增:灵魂盘机制精髓补齐——context --budget(token 预算,近记忆按剩余预算放行)/ context 内嵌「多智能体接入铁律」段 / remember --source/--weight(来源 + 重要性权重,去重 weight 取 max)/ 近期记忆排序融合 importance(confidence×recency+updated+weight+immutable)
10
10
  // v0.6.3 修复:lan 开机自启 VBS 自愈——VBS 内联 autostart.cmd 内容,启动时自动重建 .cmd(根治 80070002:wscript 找不到被引用启动文件)
11
+ // v0.6.4 新增:lan 命令扩展 Linux——systemd 用户单元(systemctl --user enable/start,登录自启;--onstart 附加 loginctl enable-linger 开机即启)/ systemd 不可用时自动降级用户 crontab @reboot;lanPlatform 测试钩子(YOTTA_LAN_PLATFORM)
11
12
  // v0.6.2 修复:remember --verify 写后回读改为直查索引 + 权限判定 + 召回匹配性(不再依赖 recall top-N 排序,消除泛化 subject 下偶发误报「回读未命中」)
12
13
  'use strict';
13
14
 
@@ -18,7 +19,7 @@ const crypto = require('crypto');
18
19
  const http = require('http');
19
20
  const child_process = require('child_process');
20
21
 
21
- const VERSION = '0.6.3';
22
+ const VERSION = '0.6.4';
22
23
  const TYPES = ['FACT', 'PREF', 'BOUND', 'COMMIT'];
23
24
  const TYPE_DIRS = { FACT: 'facts', PREF: 'prefs', BOUND: 'bounds', COMMIT: 'commits' };
24
25
  const PUBLIC_DIR = 'facts';
@@ -1268,11 +1269,17 @@ function cmdServeStdio() {
1268
1269
  process.stdin.on('end', function () { process.exit(0); });
1269
1270
  }
1270
1271
 
1271
- // ---- lan 命令(Windows 开机自启管理 serve)----
1272
- // 双机制:① schtasks 计划任务(默认,需相应权限);② 非管理员(Access denied)自动降级为
1273
- // 用户级 Startup 静默自启(VBS sh.Run <autostart.cmd>, 0, False + autostart.cmd;node 路径 process.execPath 自动探测,0.5.4)
1272
+ // ---- lan command (autostart management for serve) ----
1273
+ // Windows dual mechanism: (1) schtasks scheduled task (default, needs permission); (2) non-admin
1274
+ // (Access denied) auto-fallback to user-level Startup silent autostart (VBS sh.Run <autostart.cmd>,
1275
+ // 0, False + autostart.cmd; node path process.execPath auto-detected, 0.5.4)
1276
+ // Linux (v0.6.4): (1) systemd user unit (systemctl --user enable/start, login autostart; --onstart
1277
+ // additionally runs loginctl enable-linger for boot autostart); (2) auto-fallback to user crontab
1278
+ // @reboot when no systemd user session is available.
1274
1279
  const LAN_TASK_NAME = 'YottaMemoryServe';
1275
1280
  const LAN_GEN_MARKER = 'Generated by yotta-memory lan enable';
1281
+ const LAN_UNIT_NAME = 'yotta-memory-serve.service';
1282
+ const LAN_CRONTAB_MARKER = '#YTM_LAN:yotta-memory-serve';
1276
1283
  function lanTaskRunCmd(opts) {
1277
1284
  const host = opts.host || '0.0.0.0';
1278
1285
  const port = opts.port || 8787;
@@ -1280,6 +1287,33 @@ function lanTaskRunCmd(opts) {
1280
1287
  const q = (p) => /[\s"]/.test(p) ? '\\"' + p.replace(/"/g, '\\"') + '\\"' : p;
1281
1288
  return q(process.execPath) + ' ' + q(__filename) + ' serve --host ' + host + ' --port ' + port;
1282
1289
  }
1290
+
1291
+ function lanServeArgs(opts) {
1292
+ return ['serve', '--host', String(opts.host || '0.0.0.0'), '--port', String(opts.port || 8787)];
1293
+ }
1294
+ // generic spawn wrapper: YOTTA_LAN_*_BIN may point to a .js stub (testing/advanced use;
1295
+ // on Windows run it with the current node interpreter automatically)
1296
+ function lanSpawn(bin, args, spawnOpts) {
1297
+ if (process.platform === 'win32' && /\.js$/i.test(String(bin))) {
1298
+ return child_process.spawnSync(process.execPath, [bin].concat(args), spawnOpts);
1299
+ }
1300
+ return child_process.spawnSync(bin, args, spawnOpts);
1301
+ }
1302
+ // platform override for testing/advanced use (YOTTA_LAN_PLATFORM=linux/win32/...)
1303
+ function lanPlatform() { return process.env.YOTTA_LAN_PLATFORM || process.platform; }
1304
+ // sh-style single-quote quoting (crontab line is executed via /bin/sh -c)
1305
+ function shQuote(s) {
1306
+ const p = String(s);
1307
+ if (!/[\s'"\\$\`]/.test(p)) return p;
1308
+ return "'" + p.replace(/'/g, "'\\''") + "'";
1309
+ }
1310
+ // systemd ExecStart quoting (systemd's own parse rules: double quotes group, \" escapes a quote,
1311
+ // \\ escapes a backslash)
1312
+ function systemdEscapeArg(s) {
1313
+ const p = String(s);
1314
+ if (!/[\s"\\$;]/.test(p)) return p;
1315
+ return '"' + p.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
1316
+ }
1283
1317
  function lanStartupDir() {
1284
1318
  // 测试/高级用途可用 YOTTA_LAN_STARTUP_DIR 覆盖(默认用户级 Startup 目录,免管理员)
1285
1319
  if (process.env.YOTTA_LAN_STARTUP_DIR) return process.env.YOTTA_LAN_STARTUP_DIR;
@@ -1370,7 +1404,191 @@ function isSchtasksAccessDenied(e) {
1370
1404
  const msg = String((e && e.stderr) || (e && e.message) || e);
1371
1405
  return /access\s+is\s+denied|access\s+denied|拒绝访问/i.test(msg);
1372
1406
  }
1373
- function cmdLanEnable(opts) {
1407
+
1408
+ // ---- lan Linux (systemd user unit / user crontab @reboot) ----
1409
+ function lanLinuxSystemdUserDir() {
1410
+ // override with YOTTA_LAN_SYSTEMD_USER_DIR for testing/advanced use (default ~/.config/systemd/user)
1411
+ if (process.env.YOTTA_LAN_SYSTEMD_USER_DIR) return process.env.YOTTA_LAN_SYSTEMD_USER_DIR;
1412
+ const cfg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
1413
+ return path.join(cfg, 'systemd', 'user');
1414
+ }
1415
+ function lanLinuxUnitPath() { return path.join(lanLinuxSystemdUserDir(), LAN_UNIT_NAME); }
1416
+ function lanLinuxSystemctlBin() { return process.env.YOTTA_LAN_SYSTEMCTL_BIN || 'systemctl'; }
1417
+ function lanLinuxLoginctlBin() { return process.env.YOTTA_LAN_LOGINCTL_BIN || 'loginctl'; }
1418
+ function lanLinuxExecStart(opts) {
1419
+ return [systemdEscapeArg(process.execPath), systemdEscapeArg(__filename)].concat(lanServeArgs(opts)).join(' ');
1420
+ }
1421
+ function lanLinuxUnitContent(opts) {
1422
+ // keep comments ASCII (avoid locale/encoding issues); ExecStart uses systemd's own quote rules
1423
+ return [
1424
+ '# ' + LAN_GEN_MARKER + '; remove with: yotta-memory lan disable (English only, keep ASCII)',
1425
+ '',
1426
+ '[Unit]',
1427
+ 'Description=yotta-memory memory engine (lan autostart)',
1428
+ '',
1429
+ '[Service]',
1430
+ 'Type=simple',
1431
+ 'ExecStart=' + lanLinuxExecStart(opts),
1432
+ 'Restart=on-failure',
1433
+ 'RestartSec=3',
1434
+ '',
1435
+ '[Install]',
1436
+ 'WantedBy=default.target',
1437
+ '',
1438
+ ].join('\n');
1439
+ }
1440
+ function lanCrontabBin() { return process.env.YOTTA_LAN_CRONTAB_BIN || 'crontab'; }
1441
+ function lanCrontabLine(opts) {
1442
+ const parts = ['@reboot', shQuote(process.execPath), shQuote(__filename)].concat(lanServeArgs(opts));
1443
+ return parts.join(' ') + ' >> ' + shQuote(lanLogPath(opts)) + ' 2>&1 ' + LAN_CRONTAB_MARKER;
1444
+ }
1445
+ function lanCrontabRead() {
1446
+ try {
1447
+ const r = lanSpawn(lanCrontabBin(), ['-l'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
1448
+ if (r.status === 0) return String(r.stdout || '').split(/\r?\n/).filter(function (l) { return l !== ''; });
1449
+ } catch (e) { /* crontab unavailable -> treat as empty */ }
1450
+ return [];
1451
+ }
1452
+ function lanCrontabWrite(lines) {
1453
+ const content = lines.join('\n') + (lines.length ? '\n' : '');
1454
+ const r = lanSpawn(lanCrontabBin(), ['-'], { input: content, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
1455
+ if (r.error) throw r.error;
1456
+ if (r.status !== 0) throw new Error(String(r.stderr || r.stdout || '').toString().split(/\r?\n/)[0]);
1457
+ }
1458
+ function lanCrontabHasOurLine(lines) {
1459
+ return lines.some(function (l) { return l.indexOf(LAN_CRONTAB_MARKER) !== -1; });
1460
+ }
1461
+ function lanCrontabWithoutOurLine(lines) {
1462
+ return lines.filter(function (l) { return l.indexOf(LAN_CRONTAB_MARKER) === -1; });
1463
+ }
1464
+ function lanLinuxHasSystemd() {
1465
+ try {
1466
+ const r = lanSpawn(lanLinuxSystemctlBin(), ['--user', 'show-environment'], { stdio: ['ignore', 'ignore', 'ignore'] });
1467
+ return r.status === 0;
1468
+ } catch (e) { return false; }
1469
+ }
1470
+ function lanLinuxSystemctl(args) {
1471
+ const r = lanSpawn(lanLinuxSystemctlBin(), ['--user'].concat(args), { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
1472
+ if (r.error) throw r.error;
1473
+ return { status: r.status, out: String(r.stdout || ''), err: String(r.stderr || '') };
1474
+ }
1475
+ function lanLinuxInstallSystemd(opts) {
1476
+ const dir = lanLinuxSystemdUserDir();
1477
+ fs.mkdirSync(dir, { recursive: true });
1478
+ const unit = lanLinuxUnitPath();
1479
+ fs.writeFileSync(unit, lanLinuxUnitContent(opts), 'utf8');
1480
+ lanLinuxSystemctl(['daemon-reload']);
1481
+ const en = lanLinuxSystemctl(['enable', LAN_UNIT_NAME]);
1482
+ if (en.status !== 0) throw new Error('systemctl --user enable 失败: ' + en.err.split(/\r?\n/)[0]);
1483
+ const st = lanLinuxSystemctl(['start', LAN_UNIT_NAME]);
1484
+ if (st.status !== 0) {
1485
+ console.error('启动服务失败(已注册,稍后可执行 systemctl --user start yotta-memory-serve.service): ' + st.err.split(/\r?\n/)[0]);
1486
+ }
1487
+ if (opts.onstart) {
1488
+ try {
1489
+ const lr = lanSpawn(lanLinuxLoginctlBin(), ['enable-linger'], { stdio: ['ignore', 'pipe', 'pipe'] });
1490
+ if (lr.status === 0) console.log('已启用 lingering(开机即启,无需登录)');
1491
+ else console.error('提示: 开机即启需 loginctl enable-linger,当前未生效(' + String(lr.stderr || '').split(/\r?\n/)[0] + ')');
1492
+ } catch (e) {
1493
+ console.error('提示: 开机即启需 loginctl enable-linger,当前无 loginctl(' + String((e && e.message) || e).split(/\r?\n/)[0] + ')');
1494
+ }
1495
+ }
1496
+ return unit;
1497
+ }
1498
+ function lanLinuxRemoveSystemd() {
1499
+ // only remove our own marker unit file, never a user's own systemd unit
1500
+ const unit = lanLinuxUnitPath();
1501
+ if (!fs.existsSync(unit) || !lanFileHasMarker(unit)) return false;
1502
+ try { lanLinuxSystemctl(['disable', LAN_UNIT_NAME]); } catch (e) {}
1503
+ try { lanLinuxSystemctl(['stop', LAN_UNIT_NAME]); } catch (e) {}
1504
+ try { fs.unlinkSync(unit); } catch (e) {}
1505
+ try { lanLinuxSystemctl(['daemon-reload']); } catch (e) {}
1506
+ return true;
1507
+ }
1508
+ function lanLinuxInstallCrontab(opts) {
1509
+ const lines = lanCrontabWithoutOurLine(lanCrontabRead());
1510
+ lines.push(lanCrontabLine(opts));
1511
+ lanCrontabWrite(lines);
1512
+ return lanCrontabLine(opts);
1513
+ }
1514
+ function lanLinuxRemoveCrontab() {
1515
+ const cur = lanCrontabRead();
1516
+ const lines = lanCrontabWithoutOurLine(cur);
1517
+ if (lines.length === cur.length) return false;
1518
+ lanCrontabWrite(lines);
1519
+ return true;
1520
+ }
1521
+ function cmdLanLinuxEnable(opts) {
1522
+ if (lanLinuxHasSystemd()) {
1523
+ try {
1524
+ const unit = lanLinuxInstallSystemd(opts);
1525
+ console.log('已注册开机自启: systemd 用户单元 ' + LAN_UNIT_NAME);
1526
+ console.log('单元文件: ' + unit);
1527
+ console.log('备注: 登录后自动启动(systemctl --user);如需现在运行请执行 yotta-memory serve');
1528
+ if (!opts.onstart) console.log('提示: 如需开机即启(无需登录),请用 --onstart 重新执行 yotta-memory lan enable');
1529
+ return;
1530
+ } catch (e) {
1531
+ const msg = String((e && e.message) || e).split(/\r?\n/)[0];
1532
+ console.error('systemd 注册失败: ' + msg);
1533
+ console.log('自动改用用户 crontab @reboot 自启...');
1534
+ }
1535
+ } else {
1536
+ console.log('未检测到 systemd 用户会话,改用用户 crontab @reboot 自启...');
1537
+ }
1538
+ try {
1539
+ const line = lanLinuxInstallCrontab(opts);
1540
+ console.log('已启用用户 crontab @reboot 自启');
1541
+ console.log('启动命令: ' + line);
1542
+ console.log('日志: ' + lanLogPath(opts));
1543
+ console.log('备注: 开机时自动启动;如需现在运行请执行 yotta-memory serve');
1544
+ } catch (e2) {
1545
+ console.error('crontab 注册失败: ' + String((e2 && e2.message) || e2).split(/\r?\n/)[0]);
1546
+ console.error('请手动配置开机自启(如 ~/.config/autostart/*.desktop 或系统服务),或直接运行 yotta-memory serve');
1547
+ process.exit(1);
1548
+ }
1549
+ }
1550
+ function cmdLanLinuxDisable() {
1551
+ let removedAny = false;
1552
+ let sawError = false;
1553
+ try {
1554
+ if (lanLinuxRemoveSystemd()) { console.log('已移除 systemd 用户单元 ' + LAN_UNIT_NAME); removedAny = true; }
1555
+ } catch (e) { console.error('移除 systemd 单元失败: ' + String((e && e.message) || e).split(/\r?\n/)[0]); sawError = true; }
1556
+ try {
1557
+ if (lanLinuxRemoveCrontab()) { console.log('已移除 crontab @reboot 自启'); removedAny = true; }
1558
+ } catch (e) { console.error('移除 crontab 自启失败: ' + String((e && e.message) || e).split(/\r?\n/)[0]); sawError = true; }
1559
+ if (!removedAny && !sawError) console.log('未启用: 未发现任何自启配置(systemd 单元与 crontab 均不存在)');
1560
+ if (sawError) process.exit(1);
1561
+ }
1562
+ function cmdLanLinuxStatus() {
1563
+ let any = false;
1564
+ const unit = lanLinuxUnitPath();
1565
+ const unitExists = fs.existsSync(unit) && lanFileHasMarker(unit);
1566
+ if (unitExists) {
1567
+ any = true;
1568
+ console.log('systemd 用户单元 ' + LAN_UNIT_NAME + ': 已注册');
1569
+ try {
1570
+ const en = lanLinuxSystemctl(['is-enabled', LAN_UNIT_NAME]);
1571
+ if (en.status === 0) console.log(' 启用状态: ' + String(en.out).trim());
1572
+ } catch (e) {}
1573
+ try {
1574
+ const ac = lanLinuxSystemctl(['is-active', LAN_UNIT_NAME]);
1575
+ if (ac.status === 0) console.log(' 运行状态: ' + String(ac.out).trim());
1576
+ } catch (e) {}
1577
+ console.log(' 单元文件: ' + unit);
1578
+ } else {
1579
+ console.log('systemd 用户单元 ' + LAN_UNIT_NAME + ': 未注册');
1580
+ }
1581
+ const lines = lanCrontabRead();
1582
+ if (lanCrontabHasOurLine(lines)) {
1583
+ any = true;
1584
+ console.log('crontab @reboot: 已启用');
1585
+ for (const l of lines) if (l.indexOf(LAN_CRONTAB_MARKER) !== -1) console.log(' ' + l.trim());
1586
+ } else {
1587
+ console.log('crontab @reboot: 未启用');
1588
+ }
1589
+ if (!any) console.log('未启用任何开机自启(可用 yotta-memory lan enable 注册)');
1590
+ }
1591
+ function cmdLanWinEnable(opts) {
1374
1592
  if (process.platform !== 'win32') {
1375
1593
  console.error('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + process.platform);
1376
1594
  process.exit(2);
@@ -1404,7 +1622,7 @@ function cmdLanEnable(opts) {
1404
1622
  return;
1405
1623
  }
1406
1624
  }
1407
- function cmdLanDisable() {
1625
+ function cmdLanWinDisable() {
1408
1626
  if (process.platform !== 'win32') {
1409
1627
  console.error('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + process.platform);
1410
1628
  process.exit(2);
@@ -1429,7 +1647,7 @@ function cmdLanDisable() {
1429
1647
  if (!removedAny && !sawError) console.log('未启用: 未发现任何自启配置(计划任务与 Startup 文件均不存在)');
1430
1648
  if (sawError) process.exit(1);
1431
1649
  }
1432
- function cmdLanStatus() {
1650
+ function cmdLanWinStatus() {
1433
1651
  if (process.platform !== 'win32') {
1434
1652
  console.log('lan 命令当前仅支持 Windows(计划任务 / Startup 自启);本机平台: ' + process.platform);
1435
1653
  return;
@@ -1460,6 +1678,27 @@ function cmdLanStatus() {
1460
1678
  }
1461
1679
  if (!taskFound && !vbsOk && !cmdOk) console.log('未启用任何开机自启(可用 yotta-memory lan enable 注册)');
1462
1680
  }
1681
+
1682
+ function cmdLanEnable(opts) {
1683
+ const p = lanPlatform();
1684
+ if (p === 'win32') return cmdLanWinEnable(opts);
1685
+ if (p === 'linux') return cmdLanLinuxEnable(opts);
1686
+ console.error('lan 命令当前仅支持 Windows / Linux;本机平台: ' + p);
1687
+ process.exit(2);
1688
+ }
1689
+ function cmdLanDisable() {
1690
+ const p = lanPlatform();
1691
+ if (p === 'win32') return cmdLanWinDisable();
1692
+ if (p === 'linux') return cmdLanLinuxDisable();
1693
+ console.error('lan 命令当前仅支持 Windows / Linux;本机平台: ' + p);
1694
+ process.exit(2);
1695
+ }
1696
+ function cmdLanStatus() {
1697
+ const p = lanPlatform();
1698
+ if (p === 'win32') return cmdLanWinStatus();
1699
+ if (p === 'linux') return cmdLanLinuxStatus();
1700
+ console.log('lan 命令当前仅支持 Windows / Linux;本机平台: ' + p);
1701
+ }
1463
1702
  // ---- usage / main ----
1464
1703
  function usage() {
1465
1704
  console.log([
@@ -1484,7 +1723,7 @@ function usage() {
1484
1723
  ' yotta-memory token list 列出已登记智能体',
1485
1724
  ' yotta-memory token revoke --agent <id> 吊销某智能体 token',
1486
1725
  ' yotta-memory serve [--host 0.0.0.0] [--port 8787] [--no-auth] [--stdio] 启动 MCP 记忆引擎(streamable HTTP;--stdio 本地零进程模式)',
1487
- ' yotta-memory lan enable [--onstart] | disable | status 开机自启管理(Windows 计划任务,默认 ONLOGON;非管理员自动降级用户级 Startup 静默自启)',
1726
+ ' yotta-memory lan enable [--onstart] | disable | status 开机自启管理(Windows:计划任务,默认 ONLOGON,非管理员自动降级用户级 Startup 静默自启;Linux:systemd 用户单元,不可用时自动降级用户 crontab @reboot)',
1488
1727
  ' yotta-memory --version 版本',
1489
1728
  '',
1490
1729
  '类型: FACT(公共共享) / PREF(偏好) / BOUND(边界) / COMMIT(承诺)',
@@ -1620,4 +1859,34 @@ module.exports = {
1620
1859
  lanRemoveStartupFiles: lanRemoveStartupFiles,
1621
1860
  lanFileHasMarker: lanFileHasMarker,
1622
1861
  isSchtasksAccessDenied: isSchtasksAccessDenied,
1862
+ lanServeArgs: lanServeArgs,
1863
+ lanSpawn: lanSpawn,
1864
+ lanPlatform: lanPlatform,
1865
+ shQuote: shQuote,
1866
+ systemdEscapeArg: systemdEscapeArg,
1867
+ lanLinuxSystemdUserDir: lanLinuxSystemdUserDir,
1868
+ lanLinuxUnitPath: lanLinuxUnitPath,
1869
+ lanLinuxSystemctlBin: lanLinuxSystemctlBin,
1870
+ lanLinuxLoginctlBin: lanLinuxLoginctlBin,
1871
+ lanLinuxExecStart: lanLinuxExecStart,
1872
+ lanLinuxUnitContent: lanLinuxUnitContent,
1873
+ lanCrontabBin: lanCrontabBin,
1874
+ lanCrontabLine: lanCrontabLine,
1875
+ lanCrontabRead: lanCrontabRead,
1876
+ lanCrontabWrite: lanCrontabWrite,
1877
+ lanCrontabHasOurLine: lanCrontabHasOurLine,
1878
+ lanCrontabWithoutOurLine: lanCrontabWithoutOurLine,
1879
+ lanLinuxHasSystemd: lanLinuxHasSystemd,
1880
+ lanLinuxSystemctl: lanLinuxSystemctl,
1881
+ lanLinuxInstallSystemd: lanLinuxInstallSystemd,
1882
+ lanLinuxRemoveSystemd: lanLinuxRemoveSystemd,
1883
+ lanLinuxInstallCrontab: lanLinuxInstallCrontab,
1884
+ lanLinuxRemoveCrontab: lanLinuxRemoveCrontab,
1885
+ cmdLanWinEnable: cmdLanWinEnable,
1886
+ cmdLanWinDisable: cmdLanWinDisable,
1887
+ cmdLanWinStatus: cmdLanWinStatus,
1888
+ cmdLanLinuxEnable: cmdLanLinuxEnable,
1889
+ cmdLanLinuxDisable: cmdLanLinuxDisable,
1890
+ cmdLanLinuxStatus: cmdLanLinuxStatus,
1891
+
1623
1892
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottameta/yotta-memory",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "元忆 —— 有权限边界的文件式智能体记忆。文件式、零依赖、可 diff/回滚;FACT/PREF/BOUND/COMMIT 四分类(公共共享/私密隔离),用户级+项目级双级存储。",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -1,4 +1,4 @@
1
- # yotta-memory 协议规范 v0.6.3
1
+ # yotta-memory 协议规范 v0.6.4
2
2
 
3
3
  > 本文件定义 yotta-memory 记忆标准:存储位置、目录结构、文件格式、类型体系与 CLI 命令参考。
4
4
  > 目标:任何支持 Agent Skills 开放标准的智能体,装完即可读写同一份记忆。