pi-information-protecter 0.4.0 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog / 更新日志
2
2
 
3
+ ## 0.4.1 — Safe audit clearing / 安全审计清理
4
+
5
+ - Add `/protecter logs clear`, restricted to local TUI with explicit confirmation and idle waiting; cancellation and non-TUI modes never clear records.
6
+ 新增本地 TUI 确认及空闲等待的日志清理命令,取消或非 TUI 模式不清理。
7
+ - Serialize clearing with scans and the existing cross-process append lock; truncate and fsync the validated current log without deleting configuration, other logs or in-memory mappings.
8
+ 清理与扫描及现有跨进程写锁协调,对验证后的当前日志截断并同步,不删除配置、其他日志或内存映射。
9
+ - Cover missing/full/partial logs, unsafe links, occupied locks, resumed writes and source/packed command execution in regression tests.
10
+ 回归覆盖缺失、满容量、残缺日志、不安全链接、占用锁、恢复写入及源码和实际发布包命令执行。
11
+
3
12
  ## 0.4.0 — Fixed replacement aliases / 固定替换别名
4
13
 
5
14
  - Add optional literal `replacement` strings to literal and regex rules; omitted targets retain stable per-instance random placeholders.
package/README.md CHANGED
@@ -14,7 +14,7 @@ Requires Node.js 22+ and `@earendil-works/pi-coding-agent` **0.85.1–0.85.x**,
14
14
 
15
15
  ```bash
16
16
  # Install the pinned npm release.
17
- pi install npm:pi-information-protecter@0.4.0
17
+ pi install npm:pi-information-protecter@0.4.1
18
18
 
19
19
  # Alternatively, build and install from this repository without copying it.
20
20
  npm ci
@@ -94,6 +94,8 @@ One record is written per distinct original/token pair per successful scan; repe
94
94
 
95
95
  `/protecter` or `/protecter logs [1-100]` reads the most recent records (20 by default). Local TUI only: choose a time/provider entry, then confirm before revealing plaintext. The editor is a preview and discards edits. Controls are escaped; long previews are truncated. RPC/print modes do not display secrets, and the command never calls `sendMessage` or writes session entries.
96
96
 
97
+ Use `/protecter logs clear` to empty only the current machine's audit log. Local TUI confirmation is mandatory; cancelling, RPC/print mode or extra arguments do nothing. After confirmation, Pi waits for idle, queues the clear with scans and acquires the same cross-process log lock as appends. The validated file is truncated and synced, not unlinked; missing logs are a successful no-op. Configuration, other machine logs and in-memory mappings remain unchanged. New requests may immediately append new records. Full or partial-tail logs can be cleared, but unsafe links and occupied/stale locks are never bypassed. This is irreversible logical deletion, **not secure erasure** of filesystem snapshots/backups.
98
+
97
99
  **Logs contain real secrets and reversible mappings.** Unix permissions are 0600; symlinks, hard links and other-user files are rejected. Tool guards cover current/old hash log names and aliases but are not a sandbox. Logs are not encrypted, automatically rotated, migrated with configuration or used to rebuild mappings. Stop all Pi processes before locally archiving/deleting logs or inspecting a stale `.jsonl.lock`. Reading is bounded to the last 1 MiB, display to 100 records and 20,000 characters per preview. A log is capped at 32 MiB and a request batch at 8 MiB; full, unwritable, partial-tail or locked logs block matched requests rather than silently losing audits. Failed scans add no records; a hard crash can leave a partial batch that requires local repair.
98
100
 
99
101
  ## How it works
@@ -163,7 +165,7 @@ API references: [Pi Extensions](https://pi.dev/docs/latest/extensions), local 0.
163
165
 
164
166
  ```bash
165
167
  # 安装固定 npm 版本。
166
- pi install npm:pi-information-protecter@0.4.0
168
+ pi install npm:pi-information-protecter@0.4.1
167
169
 
168
170
  # 或在本仓库构建后本地安装,不复制仓库。
169
171
  npm ci
@@ -243,6 +245,8 @@ pi -e ./src/index.ts
243
245
 
244
246
  `/protecter` 或 `/protecter logs [1-100]` 查看最近记录,默认 20 条。仅在本地 TUI 可用:先选择时间及供应商条目,确认后才显示明文。编辑器只作预览,修改不保存;控制字符转义,过长预览截断。RPC、打印模式不展示秘密,命令不调用消息发送接口,也不写入会话条目。
245
247
 
248
+ 使用 `/protecter logs clear` 只清空当前机器的审计日志。必须在本地 TUI 确认;取消、RPC/打印模式或多余参数均不执行。确认后等待 Pi 空闲,与扫描排队并获取和追加日志相同的跨进程锁;安全验证后截断文件并同步,不删除文件,日志不存在则成功无操作。不改变配置、其他机器日志或内存映射;新请求可能立即新增记录。可清理已满或尾部残缺日志,但不绕过危险链接、占用锁或残留锁。这是不可撤销的逻辑删除,**不等于安全擦除**文件系统快照或备份。
249
+
246
250
  **日志包含真实秘密及可逆映射。** Unix 权限为 0600,拒绝符号链接、硬链接及他人文件。工具防护覆盖当前和旧机器哈希日志及别名,但不是沙箱。日志不加密、不自动轮转、不随配置迁移,也不用于恢复映射。请停止全部 Pi 进程后再本地归档、删除日志或检查残留 `.jsonl.lock`。读取限最后 1 MiB、最多 100 条,每条预览最多 20,000 字符;单日志上限 32 MiB、请求批次 8 MiB。日志满、不可写、尾部残缺或锁占用时,命中请求会被阻止,不静默漏记。扫描失败不新增记录,硬崩溃可能遗留部分批次,需本地修复。
247
251
 
248
252
  ## 工作方式
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  /*! SPI Protecter | MIT | see LICENSE / SPI 隐私保护插件 | MIT 许可 */
2
- import{getAgentDir as _e,VERSION as ee}from"@earendil-works/pi-coding-agent";import{Worker as Ee}from"node:worker_threads";import{fileURLToPath as Oe}from"node:url";import*as u from"node:fs";import{join as k,resolve as ae}from"node:path";import{createHash as ce,randomUUID as fe}from"node:crypto";import{Worker as le}from"node:worker_threads";import{fileURLToPath as ue}from"node:url";import{constants as O,closeSync as J,fchmodSync as te,fstatSync as re,mkdirSync as ne,openSync as j,readFileSync as oe,writeFileSync as ie}from"node:fs";import{dirname as se}from"node:path";var C={version:1,sensitiveWords:[]},w="SPI Protecter: unsafe or invalid configuration / 配置不安全或格式无效,请在本地修复。";function I(o){try{let e=JSON.parse(o);if(!e||e.version!==1||!Array.isArray(e.sensitiveWords)||e.sensitiveWords.length>1e3||Object.keys(e).some(t=>!["version","sensitiveWords"].includes(t)))throw new Error;let n=new Map;for(let t of e.sensitiveWords){if(typeof t=="string"){if(!t||t.length>8192)throw new Error;let r=JSON.stringify(["literal",t]);if(n.has(r)&&n.get(r)!==void 0)throw new Error;n.set(r,void 0);continue}if(!t||typeof t!="object")throw new Error;if(Object.hasOwn(t,"replacement")&&(typeof t.replacement!="string"||!t.replacement.trim()||t.replacement.length>8192||t.replacement.includes("__PIP_")))throw new Error;if(t.type==="literal"){if(Object.keys(t).some(r=>!["type","value","replacement"].includes(r))||typeof t.value!="string"||!t.value||t.value.length>8192)throw new Error}else if(t.type==="regex"){if(Object.keys(t).some(s=>!["type","pattern","flags","replacement"].includes(s))||typeof t.pattern!="string"||!t.pattern||t.pattern.length>8192||t.flags!==void 0&&(typeof t.flags!="string"||!/^[gimsu]*$/.test(t.flags)))throw new Error;let r=new RegExp(t.pattern,t.flags??"")}else throw new Error;let i=t.type==="literal"?JSON.stringify(["literal",t.value]):JSON.stringify(["regex",t.pattern,[...new Set((t.flags??"")+"g")].sort().join("")]);if(n.has(i)&&n.get(i)!==t.replacement)throw new Error;n.set(i,t.replacement)}return e}catch{throw new Error(w)}}function F(o,e=!1){let n;try{if(e){ne(se(o),{recursive:!0,mode:448});try{let r=j(o,O.O_WRONLY|O.O_CREAT|O.O_EXCL,384);try{ie(r,JSON.stringify(C,null,2)+`
3
- `)}finally{J(r)}}catch(r){if(r.code!=="EEXIST")throw r}}n=j(o,O.O_RDONLY|O.O_NOFOLLOW|O.O_NONBLOCK);let t=re(n);if(!t.isFile()||t.nlink!==1||t.size>1024*1024||process.getuid&&t.uid!==process.getuid())throw new Error;process.platform!=="win32"&&te(n,384);let i=oe(n,"utf8");if(Buffer.byteLength(i)>1024*1024)throw new Error;return{config:I(i),raw:i}}catch{throw new Error(w)}finally{n!==void 0&&J(n)}}var A=/^protecter(?:\.[a-f0-9]{32})?\.json$/;function de(o){return new Promise((e,n)=>{let t=new le(ue(new URL("./validate-worker.mjs",import.meta.url)),{workerData:o,execArgv:[],resourceLimits:{maxOldGenerationSizeMb:64}}),i=!1,r=l=>{i||(i=!0,clearTimeout(s),t.terminate(),l?e():n(new Error(w)))},s=setTimeout(()=>r(!1),2e3);t.once("message",l=>r(l===!0)),t.once("error",()=>r(!1)),t.once("exit",()=>r(!1))})}function ge(o){let e=new Set,n=[];for(let i of o)for(let r of i.sensitiveWords){let s=typeof r=="string"?JSON.stringify(["literal",r,null]):r.type==="literal"?JSON.stringify(["literal",r.value,r.replacement??null]):JSON.stringify(["regex",r.pattern,[...new Set((r.flags??"")+"g")].sort().join(""),r.replacement??null]);e.has(s)||(e.add(s),n.push(r))}let t=JSON.stringify({version:1,sensitiveWords:n});if(Buffer.byteLength(t)>1024*1024)throw new Error(w);return I(t)}function W(o){let e=u.lstatSync(o,{bigint:!0});if(!e.isFile()||e.nlink!==1n||process.getuid&&e.uid!==BigInt(process.getuid()))throw new Error(w);return`${e.dev}:${e.ino}:${e.size}:${e.mtimeNs}`}function T(o,e){let n=k(o,e),t=W(n),i=F(n);if(W(n)!==t)throw new Error(w);return{name:e,...i,identity:t,digest:ce("sha256").update(i.raw).digest("hex")}}function x(o,e){let n=T(o,e.name);if(n.identity!==e.identity||n.digest!==e.digest)throw new Error(w)}function P(o){return u.readdirSync(o).filter(e=>A.test(e)).sort()}function U(o){if(process.platform==="win32")return;let e=u.openSync(o,u.constants.O_RDONLY);try{u.fsyncSync(e)}finally{u.closeSync(e)}}async function z(o,e,n={}){if(!/^[a-f0-9]{32}$/.test(e))throw new Error(w);let t=ae(o),i=`protecter.${e}.json`,r=k(t,i);u.mkdirSync(t,{recursive:!0,mode:448});let s=k(t,".protecter-migration.lock"),l=Date.now()+(n.lockTimeoutMs??5e3);for(;;)try{u.mkdirSync(s,{mode:448});break}catch(d){if(d.code!=="EEXIST"||Date.now()>=l)throw new Error("SPI Protecter: migration locked; check active processes / 迁移锁被占用,请检查活动进程。");await new Promise(g=>setTimeout(g,50))}let a;try{let d=P(t);if(d.length>64)throw new Error(w);let c=[...d].sort((h,D)=>h===i?-1:D===i?1:h.localeCompare(D)).map(h=>T(t,h)),p=ge(c.length?c.map(h=>h.config):[C]);if(await de([p]),c.length===1&&c[0].name===i){if(x(t,c[0]),JSON.stringify(P(t))!==JSON.stringify(d))throw new Error(w);return{path:r,config:c[0].config,raw:c[0].raw,sourceRaws:[c[0].raw],migrated:0}}let m=JSON.stringify(p,null,2)+`
4
- `;if(Buffer.byteLength(m)>1024*1024)throw new Error(w);a=k(t,`.protecter-migration.${fe()}.tmp`);let y=u.openSync(a,u.constants.O_WRONLY|u.constants.O_CREAT|u.constants.O_EXCL,384);try{u.writeFileSync(y,m),u.fsyncSync(y)}finally{u.closeSync(y)}if(n.checkpoint?.("beforeCommit",r),JSON.stringify(P(t))!==JSON.stringify(d))throw new Error(w);for(let h of c)x(t,h);u.renameSync(a,r),a=void 0,U(t);let E=T(t,i);if(E.raw!==m)throw new Error(w);n.checkpoint?.("afterCommit",r);for(let h of c)h.name!==i&&(n.checkpoint?.("beforeDelete",k(t,h.name)),x(t,E),x(t,h),u.unlinkSync(k(t,h.name)));U(t);let R=[i];if(JSON.stringify(P(t))!==JSON.stringify(R))throw new Error(w);return x(t,E),{path:r,config:p,raw:m,sourceRaws:[...new Set([...c.map(h=>h.raw),m])],migrated:c.filter(h=>h.name!==i).length}}catch{throw new Error(w)}finally{if(a)try{u.unlinkSync(a)}catch{}u.rmdirSync(s)}}import{createHash as pe}from"node:crypto";import{readFileSync as me}from"node:fs";import{execFileSync as B}from"node:child_process";var G="SPI Protecter: machine ID unavailable / 无法获取机器标识。";function $(o){let e=o.trim().toLowerCase().replace(/-/g,"");if(!/^[a-f0-9]{32}$/.test(e)||/^0+$/.test(e))throw new Error(G);return pe("sha256").update(`pi-information-protecter:v1:${e}`).digest("hex").slice(0,32)}function q(){try{if(process.platform==="linux")return $(me("/etc/machine-id","utf8"));if(process.platform==="darwin"){let o=B("/usr/sbin/ioreg",["-rd1","-c","IOPlatformExpertDevice"],{encoding:"utf8",timeout:5e3,maxBuffer:1048576,stdio:["ignore","pipe","pipe"]});return $(o.match(/"IOPlatformUUID"\s*=\s*"([a-fA-F0-9-]+)"/)?.[1]??"")}if(process.platform==="win32"){let o=B("reg.exe",["query","HKLM\\SOFTWARE\\Microsoft\\Cryptography","/v","MachineGuid","/reg:64"],{encoding:"utf8",timeout:5e3,maxBuffer:65536,stdio:["ignore","pipe","pipe"]});return $(o.match(/MachineGuid\s+REG_SZ\s+([a-fA-F0-9-]+)/i)?.[1]??"")}}catch{}throw new Error(G)}import*as f from"node:fs";import{randomUUID as he}from"node:crypto";var v="SPI Protecter: audit unavailable; request blocked / 审计日志不可用,请求已阻止。",we=32*1024*1024,ye=8*1024*1024;function Se(o){let e=n=>String(n).padStart(2,"0");return`${o.getFullYear()}-${e(o.getMonth()+1)}-${e(o.getDate())} ${e(o.getHours())}:${e(o.getMinutes())}:${e(o.getSeconds())}`}function H(o,e){let n=f.openSync(o,(e?f.constants.O_RDWR|f.constants.O_CREAT:f.constants.O_RDONLY)|f.constants.O_NOFOLLOW|f.constants.O_NONBLOCK,384);try{let t=f.fstatSync(n);if(!t.isFile()||t.nlink!==1||process.getuid&&t.uid!==process.getuid())throw new Error;return process.platform!=="win32"&&f.fchmodSync(n,384),n}catch{throw f.closeSync(n),new Error(v)}}async function X(o,e,n){if(!n.length)return;let t=new Date,i=he(),r=Buffer.from(n.map(([d,g])=>JSON.stringify({version:1,time:Se(t),timezoneOffset:-t.getTimezoneOffset(),requestId:i,provider:e||"unknown",original:g,replacement:d})).join(`
2
+ import{getAgentDir as Ie,VERSION as te}from"@earendil-works/pi-coding-agent";import{Worker as ke}from"node:worker_threads";import{fileURLToPath as ve}from"node:url";import*as u from"node:fs";import{join as v,resolve as fe}from"node:path";import{createHash as ue,randomUUID as le}from"node:crypto";import{Worker as de}from"node:worker_threads";import{fileURLToPath as ge}from"node:url";import{constants as k,closeSync as F,fchmodSync as ne,fstatSync as oe,mkdirSync as ie,openSync as j,readFileSync as se,writeFileSync as ae}from"node:fs";import{dirname as ce}from"node:path";var C={version:1,sensitiveWords:[]},w="SPI Protecter: unsafe or invalid configuration / 配置不安全或格式无效,请在本地修复。";function I(i){try{let e=JSON.parse(i);if(!e||e.version!==1||!Array.isArray(e.sensitiveWords)||e.sensitiveWords.length>1e3||Object.keys(e).some(r=>!["version","sensitiveWords"].includes(r)))throw new Error;let n=new Map;for(let r of e.sensitiveWords){if(typeof r=="string"){if(!r||r.length>8192)throw new Error;let t=JSON.stringify(["literal",r]);if(n.has(t)&&n.get(t)!==void 0)throw new Error;n.set(t,void 0);continue}if(!r||typeof r!="object")throw new Error;if(Object.hasOwn(r,"replacement")&&(typeof r.replacement!="string"||!r.replacement.trim()||r.replacement.length>8192||r.replacement.includes("__PIP_")))throw new Error;if(r.type==="literal"){if(Object.keys(r).some(t=>!["type","value","replacement"].includes(t))||typeof r.value!="string"||!r.value||r.value.length>8192)throw new Error}else if(r.type==="regex"){if(Object.keys(r).some(s=>!["type","pattern","flags","replacement"].includes(s))||typeof r.pattern!="string"||!r.pattern||r.pattern.length>8192||r.flags!==void 0&&(typeof r.flags!="string"||!/^[gimsu]*$/.test(r.flags)))throw new Error;let t=new RegExp(r.pattern,r.flags??"")}else throw new Error;let o=r.type==="literal"?JSON.stringify(["literal",r.value]):JSON.stringify(["regex",r.pattern,[...new Set((r.flags??"")+"g")].sort().join("")]);if(n.has(o)&&n.get(o)!==r.replacement)throw new Error;n.set(o,r.replacement)}return e}catch{throw new Error(w)}}function W(i,e=!1){let n;try{if(e){ie(ce(i),{recursive:!0,mode:448});try{let t=j(i,k.O_WRONLY|k.O_CREAT|k.O_EXCL,384);try{ae(t,JSON.stringify(C,null,2)+`
3
+ `)}finally{F(t)}}catch(t){if(t.code!=="EEXIST")throw t}}n=j(i,k.O_RDONLY|k.O_NOFOLLOW|k.O_NONBLOCK);let r=oe(n);if(!r.isFile()||r.nlink!==1||r.size>1024*1024||process.getuid&&r.uid!==process.getuid())throw new Error;process.platform!=="win32"&&ne(n,384);let o=se(n,"utf8");if(Buffer.byteLength(o)>1024*1024)throw new Error;return{config:I(o),raw:o}}catch{throw new Error(w)}finally{n!==void 0&&F(n)}}var A=/^protecter(?:\.[a-f0-9]{32})?\.json$/;function me(i){return new Promise((e,n)=>{let r=new de(ge(new URL("./validate-worker.mjs",import.meta.url)),{workerData:i,execArgv:[],resourceLimits:{maxOldGenerationSizeMb:64}}),o=!1,t=a=>{o||(o=!0,clearTimeout(s),r.terminate(),a?e():n(new Error(w)))},s=setTimeout(()=>t(!1),2e3);r.once("message",a=>t(a===!0)),r.once("error",()=>t(!1)),r.once("exit",()=>t(!1))})}function pe(i){let e=new Set,n=[];for(let o of i)for(let t of o.sensitiveWords){let s=typeof t=="string"?JSON.stringify(["literal",t,null]):t.type==="literal"?JSON.stringify(["literal",t.value,t.replacement??null]):JSON.stringify(["regex",t.pattern,[...new Set((t.flags??"")+"g")].sort().join(""),t.replacement??null]);e.has(s)||(e.add(s),n.push(t))}let r=JSON.stringify({version:1,sensitiveWords:n});if(Buffer.byteLength(r)>1024*1024)throw new Error(w);return I(r)}function z(i){let e=u.lstatSync(i,{bigint:!0});if(!e.isFile()||e.nlink!==1n||process.getuid&&e.uid!==BigInt(process.getuid()))throw new Error(w);return`${e.dev}:${e.ino}:${e.size}:${e.mtimeNs}`}function T(i,e){let n=v(i,e),r=z(n),o=W(n);if(z(n)!==r)throw new Error(w);return{name:e,...o,identity:r,digest:ue("sha256").update(o.raw).digest("hex")}}function N(i,e){let n=T(i,e.name);if(n.identity!==e.identity||n.digest!==e.digest)throw new Error(w)}function R(i){return u.readdirSync(i).filter(e=>A.test(e)).sort()}function U(i){if(process.platform==="win32")return;let e=u.openSync(i,u.constants.O_RDONLY);try{u.fsyncSync(e)}finally{u.closeSync(e)}}async function q(i,e,n={}){if(!/^[a-f0-9]{32}$/.test(e))throw new Error(w);let r=fe(i),o=`protecter.${e}.json`,t=v(r,o);u.mkdirSync(r,{recursive:!0,mode:448});let s=v(r,".protecter-migration.lock"),a=Date.now()+(n.lockTimeoutMs??5e3);for(;;)try{u.mkdirSync(s,{mode:448});break}catch(d){if(d.code!=="EEXIST"||Date.now()>=a)throw new Error("SPI Protecter: migration locked; check active processes / 迁移锁被占用,请检查活动进程。");await new Promise(p=>setTimeout(p,50))}let f;try{let d=R(r);if(d.length>64)throw new Error(w);let l=[...d].sort((h,J)=>h===o?-1:J===o?1:h.localeCompare(J)).map(h=>T(r,h)),g=pe(l.length?l.map(h=>h.config):[C]);if(await me([g]),l.length===1&&l[0].name===o){if(N(r,l[0]),JSON.stringify(R(r))!==JSON.stringify(d))throw new Error(w);return{path:t,config:l[0].config,raw:l[0].raw,sourceRaws:[l[0].raw],migrated:0}}let m=JSON.stringify(g,null,2)+`
4
+ `;if(Buffer.byteLength(m)>1024*1024)throw new Error(w);f=v(r,`.protecter-migration.${le()}.tmp`);let y=u.openSync(f,u.constants.O_WRONLY|u.constants.O_CREAT|u.constants.O_EXCL,384);try{u.writeFileSync(y,m),u.fsyncSync(y)}finally{u.closeSync(y)}if(n.checkpoint?.("beforeCommit",t),JSON.stringify(R(r))!==JSON.stringify(d))throw new Error(w);for(let h of l)N(r,h);u.renameSync(f,t),f=void 0,U(r);let E=T(r,o);if(E.raw!==m)throw new Error(w);n.checkpoint?.("afterCommit",t);for(let h of l)h.name!==o&&(n.checkpoint?.("beforeDelete",v(r,h.name)),N(r,E),N(r,h),u.unlinkSync(v(r,h.name)));U(r);let b=[o];if(JSON.stringify(R(r))!==JSON.stringify(b))throw new Error(w);return N(r,E),{path:t,config:g,raw:m,sourceRaws:[...new Set([...l.map(h=>h.raw),m])],migrated:l.filter(h=>h.name!==o).length}}catch{throw new Error(w)}finally{if(f)try{u.unlinkSync(f)}catch{}u.rmdirSync(s)}}import{createHash as he}from"node:crypto";import{readFileSync as we}from"node:fs";import{execFileSync as B}from"node:child_process";var G="SPI Protecter: machine ID unavailable / 无法获取机器标识。";function $(i){let e=i.trim().toLowerCase().replace(/-/g,"");if(!/^[a-f0-9]{32}$/.test(e)||/^0+$/.test(e))throw new Error(G);return he("sha256").update(`pi-information-protecter:v1:${e}`).digest("hex").slice(0,32)}function H(){try{if(process.platform==="linux")return $(we("/etc/machine-id","utf8"));if(process.platform==="darwin"){let i=B("/usr/sbin/ioreg",["-rd1","-c","IOPlatformExpertDevice"],{encoding:"utf8",timeout:5e3,maxBuffer:1048576,stdio:["ignore","pipe","pipe"]});return $(i.match(/"IOPlatformUUID"\s*=\s*"([a-fA-F0-9-]+)"/)?.[1]??"")}if(process.platform==="win32"){let i=B("reg.exe",["query","HKLM\\SOFTWARE\\Microsoft\\Cryptography","/v","MachineGuid","/reg:64"],{encoding:"utf8",timeout:5e3,maxBuffer:65536,stdio:["ignore","pipe","pipe"]});return $(i.match(/MachineGuid\s+REG_SZ\s+([a-fA-F0-9-]+)/i)?.[1]??"")}}catch{}throw new Error(G)}import*as c from"node:fs";import{randomUUID as ye}from"node:crypto";var O="SPI Protecter: audit unavailable; request blocked / 审计日志不可用,请求已阻止。",Se=32*1024*1024,Ee=8*1024*1024;function Oe(i){let e=n=>String(n).padStart(2,"0");return`${i.getFullYear()}-${e(i.getMonth()+1)}-${e(i.getDate())} ${e(i.getHours())}:${e(i.getMinutes())}:${e(i.getSeconds())}`}function L(i,e,n=e){let r=c.openSync(i,(e?c.constants.O_RDWR|(n?c.constants.O_CREAT:0):c.constants.O_RDONLY)|c.constants.O_NOFOLLOW|c.constants.O_NONBLOCK,384);try{let o=c.fstatSync(r);if(!o.isFile()||o.nlink!==1||process.getuid&&o.uid!==process.getuid())throw new Error;return process.platform!=="win32"&&c.fchmodSync(r,384),r}catch{throw c.closeSync(r),new Error(O)}}async function X(i){let e=`${i}.lock`,n=Date.now()+2e3;for(;;)try{return c.mkdirSync(e,{mode:448}),e}catch(r){if(r.code!=="EEXIST"||Date.now()>=n)throw new Error(O);await new Promise(o=>setTimeout(o,25))}}async function Y(i){let e=await X(i),n;try{try{n=L(i,!0,!1)}catch(o){if(o.code==="ENOENT")return 0;throw o}let r=c.fstatSync(n).size;return c.ftruncateSync(n,0),c.fsyncSync(n),r}catch{throw new Error(O)}finally{try{n!==void 0&&c.closeSync(n)}finally{c.rmdirSync(e)}}}async function K(i,e,n){if(!n.length)return;let r=new Date,o=ye(),t=Buffer.from(n.map(([f,d])=>JSON.stringify({version:1,time:Oe(r),timezoneOffset:-r.getTimezoneOffset(),requestId:o,provider:e||"unknown",original:d,replacement:f})).join(`
5
5
  `)+`
6
- `);if(r.length>ye)throw new Error(v);let s=`${o}.lock`,l=Date.now()+2e3;for(;;)try{f.mkdirSync(s,{mode:448});break}catch(d){if(d.code!=="EEXIST"||Date.now()>=l)throw new Error(v);await new Promise(g=>setTimeout(g,25))}let a;try{a=H(o,!0);let d=f.fstatSync(a).size;if(d+r.length>we)throw new Error;if(d){let g=Buffer.alloc(1);if(f.readSync(a,g,0,1,d-1),g[0]!==10)throw new Error}try{let g=0;for(;g<r.length;){let c=f.writeSync(a,r,g,r.length-g,d+g);if(!c)throw new Error;g+=c}f.fsyncSync(a)}catch{throw f.ftruncateSync(a,d),f.fsyncSync(a),new Error}}catch{throw new Error(v)}finally{a!==void 0&&f.closeSync(a),f.rmdirSync(s)}}function Y(o,e=20){if(!Number.isInteger(e)||e<1||e>100)throw new Error(v);let n;try{try{n=H(o,!1)}catch(p){if(p.code==="ENOENT")return{records:[],truncated:!1};throw p}let t=f.fstatSync(n).size,i=Math.max(0,t-1024*1024),r=Buffer.alloc(t-i),s=f.readSync(n,r,0,r.length,i),l=r.subarray(0,s).toString("utf8");if(i){let p=l.indexOf(`
7
- `);l=p<0?"":l.slice(p+1)}let a=l.split(`
8
- `),d=a.pop()!=="",g=[],c=!1;for(let p of a.slice(-e))try{let m=JSON.parse(p);if(m.version!==1||typeof m.time!="string"||typeof m.provider!="string"||typeof m.original!="string"||typeof m.replacement!="string"||typeof m.requestId!="string"||typeof m.timezoneOffset!="number")throw new Error;g.push(m)}catch{c=!0}return{records:g,truncated:i>0||a.length>e||d||c}}catch{throw new Error(v)}finally{n!==void 0&&f.closeSync(n)}}var S="SPI Protecter: request cleared; check config, audit log, scan limits or attachments / 请求已清空,请检查配置、审计日志、扫描限制或附件。",b=class{constructor(e,n=2e3,t=q){this.configDir=e;this.timeoutMs=n;this.machineId=t}configDir;timeoutMs;machineId;tokens=new Map;queue=Promise.resolve();workers=new Set;closed=!1;snapshot;async initialize(){if(this.closed)throw new Error(S);this.snapshot=void 0;let e=await z(this.configDir,this.machineId());if(this.closed)throw new Error(S);this.snapshot=e}get configPath(){return this.snapshot?.path??""}get auditPath(){return this.configPath?this.configPath.replace(/\.json$/,".jsonl"):""}auditRecords(e=20){if(!this.ready)throw new Error(S);return Y(this.auditPath,e)}get ruleCount(){return this.snapshot?.config.sensitiveWords.length??0}get ready(){return!!this.snapshot&&!this.closed}get mappingCount(){return this.tokens.size}redact(e,n="unknown"){let t=this.queue.then(()=>this.scan(e,n));return this.queue=t.catch(()=>{}),t}async scan(e,n){if(!this.ready)throw new Error(S);let{config:t,sourceRaws:i}=this.snapshot,r=JSON.stringify(e);if(!r||Buffer.byteLength(r)>8*1024*1024)throw new Error(S);let s;try{s=JSON.parse(r)}catch{throw new Error(S)}let l=await new Promise((a,d)=>{let g=!1,c=new Ee(Oe(new URL("./scan-worker.mjs",import.meta.url)),{workerData:{payload:s,rules:t.sensitiveWords,entries:[...this.tokens],configRaws:i},execArgv:[],resourceLimits:{maxOldGenerationSizeMb:128}});this.workers.add(c);let p=y=>{if(!g){if(g=!0,clearTimeout(m),this.workers.delete(c),c.terminate(),!y||this.closed){d(new Error(S));return}a(y)}},m=setTimeout(()=>p(),this.timeoutMs);c.once("message",y=>p(y.failed?void 0:y)),c.once("error",()=>p()),c.once("exit",()=>p())});if(this.closed)throw new Error(S);if(await X(this.auditPath,n,l.hits),this.closed)throw new Error(S);for(let[a,d]of l.additions)this.tokens.set(a,d);return l.payload}restoreText(e){let n=r=>r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),t=[...this.tokens.keys()].filter(r=>!/^__PIP_[a-f0-9]{48}__$/.test(r)),i=new RegExp(["__PIP_[a-f0-9]{48}__",...t.sort((r,s)=>s.length-r.length).map(n)].join("|"),"g");return e.replace(i,r=>this.tokens.get(r)??r)}restore(e,n=0){if(n>80)throw new Error("protecter: 响应层级过深。");if(typeof e=="string")return this.restoreText(e);if(Array.isArray(e))return e.map(t=>this.restore(t,n+1));if(e&&typeof e=="object"){let t=Object.create(null);for(let[i,r]of Object.entries(e)){let s=this.restoreText(i);if(Object.hasOwn(t,s))throw new Error("protecter: 响应键冲突。");t[s]=this.restore(r,n+1)}return t}return e}close(){this.closed=!0,this.snapshot=void 0,this.tokens.clear();for(let e of this.workers)e.terminate();this.workers.clear()}};import{realpathSync as ke,statSync as K,readdirSync as ve}from"node:fs";import{homedir as Re}from"node:os";import{basename as L,dirname as N,isAbsolute as xe,relative as V,resolve as _}from"node:path";import{fileURLToPath as Ne}from"node:url";function Pe(o,e){let n=o.replace(/^@/,"");return n.startsWith("file://")&&(n=Ne(n)),(n==="~"||n.startsWith("~/"))&&(n=Re()+n.slice(1)),_(e,n)}function M(o){try{return ke(o)}catch{let e=N(o);return e===o?o:_(M(e),V(e,o))}}function be(o,e){let n=V(o,e);return n===""||!n.startsWith("..")&&!xe(n)}function Q(o,e){try{let n=K(o),t=K(e);return n.dev===t.dev&&n.ino===t.ino}catch{return!1}}function Z(o,e,n,t){if(!t)return!0;let i=M(_(t));if(["bash","powershell"].includes(o)){let l=typeof e.command=="string"?e.command:"";if(/protecter(?:\.[a-f0-9]{32})?\.jsonl?|\.protecter-migration/i.test(l)||l.includes(t))return!0}let r=e.path??e.file_path??e.filePath,s=["grep","find","ls"].includes(o);if(typeof r!="string"&&!s)return!1;try{let l=Pe(typeof r=="string"?r:".",n),a=M(l),d=/^protecter\.[a-f0-9]{32}\.jsonl(?:\.lock)?$/,g=ve(N(i)).filter(c=>d.test(c)).some(c=>Q(l,_(N(i),c)));return N(a)===N(i)&&(A.test(L(a))||d.test(L(a))||L(a).startsWith(".protecter-migration"))||a===i||Q(l,t)||g||s&&be(a,i)}catch{return!0}}function Ce(o){let e=new b(_e()),n=/^0\.85\./.test(ee)&&Number(ee.split(".")[2])>=1,t=!1;o.on("session_start",async(i,r)=>{try{if(!n)throw new Error;await e.initialize(),t=!0;let s=e.ruleCount;r.ui.setStatus("protecter",`SPI Protecter · ${s} rules`),s===0&&r.ui.notify("SPI Protecter: empty rules; edit local machine config and /reload / 规则为空,请编辑本地机器配置后重载。","warning")}catch{t=!1,r.ui.notify(S,"error")}}),o.on("before_provider_request",async(i,r)=>{try{if(!n)throw new Error;let s=await e.redact(i.payload,r.model?.provider??"unknown");return t=!0,s}catch{t=!1;try{await r.abort()}catch{}try{r.ui.notify(S,"error")}catch{}return{}}}),o.on("message_end",i=>{if(i.message.role!=="assistant")return;let r=i.message;return{message:{...r,content:r.content.map(s=>s.type==="text"?{...s,text:e.restoreText(s.text)}:s.type==="thinking"?{...s,thinking:e.restoreText(s.thinking)}:s.type==="toolCall"?{...s,arguments:e.restore(s.arguments)}:s),...r.errorMessage?{errorMessage:e.restoreText(r.errorMessage)}:{}}}}),o.registerMarkdownTransformer((i,r)=>r.messageType==="user"?i:e.restoreText(i)),o.on("tool_call",(i,r)=>{if(!t)return{block:!0,reason:"SPI Protecter 尚未就绪。"};let s=e.restore(i.input);for(let l of Object.keys(i.input))delete i.input[l];if(Object.assign(i.input,s),Z(i.toolName,i.input,r.cwd,e.configPath))return{block:!0,reason:"SPI Protecter:禁止工具访问保护配置,请由用户在本地编辑。"}}),o.on("session_before_compact",(i,r)=>(r.ui.notify("SPI Protecter:暂不支持远程上下文压缩,请使用 /new 开始新会话。","warning"),{cancel:!0})),o.on("session_before_tree",i=>i.preparation.userWantsSummary?{cancel:!0}:void 0),o.registerCommand("protecter",{description:"View local protection records / 查看本地保护记录",handler:async(i,r)=>{let s=i.trim().split(/\s+/).filter(Boolean),l=s[0]??"logs";if(!["status","reload","logs"].includes(l)||l!=="logs"&&s.length>1||s.length>2||s[1]!==void 0&&!/^(?:[1-9]\d?|100)$/.test(s[1])){r.ui.notify("Usage / 用法: /protecter [logs [1-100]|status|reload]. No secrets in arguments / 不要在参数中输入秘密。","info");return}if(l==="logs"){if(r.mode!=="tui"){r.ui.notify("Local TUI required / 请在本地 TUI 查看记录。","warning");return}try{let{records:a,truncated:d}=e.auditRecords(Number(s[1]??20));if(!a.length){r.ui.notify("No readable records / 暂无可读记录。","info");return}let g=E=>JSON.stringify(E).replace(/[\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g,R=>`\\u${R.charCodeAt(0).toString(16).padStart(4,"0")}`),c=a.map((E,R)=>`${R+1}. ${g(E.time)} | ${g(E.provider).slice(0,120)}`),p=await r.ui.select(`Protection records / 保护记录${d?" (limited tail / 有限尾部)":""}`,c),m=p===void 0?-1:c.indexOf(p);if(m<0||!await r.ui.confirm("Sensitive plaintext / 敏感明文","Reveal locally? Never share this view / 确认本地显示?请勿分享此界面。"))return;let y=g(a[m]);await r.ui.editor("Local preview; edits discarded / 本地预览,编辑不保存",y.length>2e4?y.slice(0,2e4)+`
9
- [Preview truncated / 预览截断]`:y)}catch{r.ui.notify("Cannot read audit records / 无法读取审计记录。","error")}return}if(l==="reload"){await r.waitForIdle(),await r.reload();return}r.ui.notify(`SPI Protecter: ${e.ready?"ready / 就绪":"not ready / 未就绪"}; ${e.ruleCount} rules / 规则; ${e.mappingCount} mappings / 映射. Memory snapshot; /reload to refresh / 内存快照,重载后更新。`,e.ready?"info":"error")}}),o.on("session_shutdown",()=>e.close())}export{Ce as default};
6
+ `);if(t.length>Ee)throw new Error(O);let s=await X(i),a;try{a=L(i,!0);let f=c.fstatSync(a).size;if(f+t.length>Se)throw new Error;if(f){let d=Buffer.alloc(1);if(c.readSync(a,d,0,1,f-1),d[0]!==10)throw new Error}try{let d=0;for(;d<t.length;){let p=c.writeSync(a,t,d,t.length-d,f+d);if(!p)throw new Error;d+=p}c.fsyncSync(a)}catch{throw c.ftruncateSync(a,f),c.fsyncSync(a),new Error}}catch{throw new Error(O)}finally{a!==void 0&&c.closeSync(a),c.rmdirSync(s)}}function Q(i,e=20){if(!Number.isInteger(e)||e<1||e>100)throw new Error(O);let n;try{try{n=L(i,!1)}catch(g){if(g.code==="ENOENT")return{records:[],truncated:!1};throw g}let r=c.fstatSync(n).size,o=Math.max(0,r-1024*1024),t=Buffer.alloc(r-o),s=c.readSync(n,t,0,t.length,o),a=t.subarray(0,s).toString("utf8");if(o){let g=a.indexOf(`
7
+ `);a=g<0?"":a.slice(g+1)}let f=a.split(`
8
+ `),d=f.pop()!=="",p=[],l=!1;for(let g of f.slice(-e))try{let m=JSON.parse(g);if(m.version!==1||typeof m.time!="string"||typeof m.provider!="string"||typeof m.original!="string"||typeof m.replacement!="string"||typeof m.requestId!="string"||typeof m.timezoneOffset!="number")throw new Error;p.push(m)}catch{l=!0}return{records:p,truncated:o>0||f.length>e||d||l}}catch{throw new Error(O)}finally{n!==void 0&&c.closeSync(n)}}var S="SPI Protecter: request cleared; check config, audit log, scan limits or attachments / 请求已清空,请检查配置、审计日志、扫描限制或附件。",x=class{constructor(e,n=2e3,r=H){this.configDir=e;this.timeoutMs=n;this.machineId=r}configDir;timeoutMs;machineId;tokens=new Map;queue=Promise.resolve();workers=new Set;closed=!1;snapshot;async initialize(){if(this.closed)throw new Error(S);this.snapshot=void 0;let e=await q(this.configDir,this.machineId());if(this.closed)throw new Error(S);this.snapshot=e}get configPath(){return this.snapshot?.path??""}get auditPath(){return this.configPath?this.configPath.replace(/\.json$/,".jsonl"):""}auditRecords(e=20){if(!this.ready)throw new Error(S);return Q(this.auditPath,e)}clearAuditRecords(){let e=this.queue.then(()=>{if(!this.ready)throw new Error(S);return Y(this.auditPath)});return this.queue=e.catch(()=>{}),e}get ruleCount(){return this.snapshot?.config.sensitiveWords.length??0}get ready(){return!!this.snapshot&&!this.closed}get mappingCount(){return this.tokens.size}redact(e,n="unknown"){let r=this.queue.then(()=>this.scan(e,n));return this.queue=r.catch(()=>{}),r}async scan(e,n){if(!this.ready)throw new Error(S);let{config:r,sourceRaws:o}=this.snapshot,t=JSON.stringify(e);if(!t||Buffer.byteLength(t)>8*1024*1024)throw new Error(S);let s;try{s=JSON.parse(t)}catch{throw new Error(S)}let a=await new Promise((f,d)=>{let p=!1,l=new ke(ve(new URL("./scan-worker.mjs",import.meta.url)),{workerData:{payload:s,rules:r.sensitiveWords,entries:[...this.tokens],configRaws:o},execArgv:[],resourceLimits:{maxOldGenerationSizeMb:128}});this.workers.add(l);let g=y=>{if(!p){if(p=!0,clearTimeout(m),this.workers.delete(l),l.terminate(),!y||this.closed){d(new Error(S));return}f(y)}},m=setTimeout(()=>g(),this.timeoutMs);l.once("message",y=>g(y.failed?void 0:y)),l.once("error",()=>g()),l.once("exit",()=>g())});if(this.closed)throw new Error(S);if(await K(this.auditPath,n,a.hits),this.closed)throw new Error(S);for(let[f,d]of a.additions)this.tokens.set(f,d);return a.payload}restoreText(e){let n=t=>t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),r=[...this.tokens.keys()].filter(t=>!/^__PIP_[a-f0-9]{48}__$/.test(t)),o=new RegExp(["__PIP_[a-f0-9]{48}__",...r.sort((t,s)=>s.length-t.length).map(n)].join("|"),"g");return e.replace(o,t=>this.tokens.get(t)??t)}restore(e,n=0){if(n>80)throw new Error("protecter: 响应层级过深。");if(typeof e=="string")return this.restoreText(e);if(Array.isArray(e))return e.map(r=>this.restore(r,n+1));if(e&&typeof e=="object"){let r=Object.create(null);for(let[o,t]of Object.entries(e)){let s=this.restoreText(o);if(Object.hasOwn(r,s))throw new Error("protecter: 响应键冲突。");r[s]=this.restore(t,n+1)}return r}return e}close(){this.closed=!0,this.snapshot=void 0,this.tokens.clear();for(let e of this.workers)e.terminate();this.workers.clear()}};import{realpathSync as be,statSync as V,readdirSync as Ne}from"node:fs";import{homedir as Pe}from"node:os";import{basename as M,dirname as P,isAbsolute as Re,relative as ee,resolve as _}from"node:path";import{fileURLToPath as xe}from"node:url";function _e(i,e){let n=i.replace(/^@/,"");return n.startsWith("file://")&&(n=xe(n)),(n==="~"||n.startsWith("~/"))&&(n=Pe()+n.slice(1)),_(e,n)}function D(i){try{return be(i)}catch{let e=P(i);return e===i?i:_(D(e),ee(e,i))}}function Ce(i,e){let n=ee(i,e);return n===""||!n.startsWith("..")&&!Re(n)}function Z(i,e){try{let n=V(i),r=V(e);return n.dev===r.dev&&n.ino===r.ino}catch{return!1}}function re(i,e,n,r){if(!r)return!0;let o=D(_(r));if(["bash","powershell"].includes(i)){let a=typeof e.command=="string"?e.command:"";if(/protecter(?:\.[a-f0-9]{32})?\.jsonl?|\.protecter-migration/i.test(a)||a.includes(r))return!0}let t=e.path??e.file_path??e.filePath,s=["grep","find","ls"].includes(i);if(typeof t!="string"&&!s)return!1;try{let a=_e(typeof t=="string"?t:".",n),f=D(a),d=/^protecter\.[a-f0-9]{32}\.jsonl(?:\.lock)?$/,p=Ne(P(o)).filter(l=>d.test(l)).some(l=>Z(a,_(P(o),l)));return P(f)===P(o)&&(A.test(M(f))||d.test(M(f))||M(f).startsWith(".protecter-migration"))||f===o||Z(a,r)||p||s&&Ce(f,o)}catch{return!0}}function Te(i){let e=new x(Ie()),n=/^0\.85\./.test(te)&&Number(te.split(".")[2])>=1,r=!1;i.on("session_start",async(o,t)=>{try{if(!n)throw new Error;await e.initialize(),r=!0;let s=e.ruleCount;t.ui.setStatus("protecter",`SPI Protecter · ${s} rules`),s===0&&t.ui.notify("SPI Protecter: empty rules; edit local machine config and /reload / 规则为空,请编辑本地机器配置后重载。","warning")}catch{r=!1,t.ui.notify(S,"error")}}),i.on("before_provider_request",async(o,t)=>{try{if(!n)throw new Error;let s=await e.redact(o.payload,t.model?.provider??"unknown");return r=!0,s}catch{r=!1;try{await t.abort()}catch{}try{t.ui.notify(S,"error")}catch{}return{}}}),i.on("message_end",o=>{if(o.message.role!=="assistant")return;let t=o.message;return{message:{...t,content:t.content.map(s=>s.type==="text"?{...s,text:e.restoreText(s.text)}:s.type==="thinking"?{...s,thinking:e.restoreText(s.thinking)}:s.type==="toolCall"?{...s,arguments:e.restore(s.arguments)}:s),...t.errorMessage?{errorMessage:e.restoreText(t.errorMessage)}:{}}}}),i.registerMarkdownTransformer((o,t)=>t.messageType==="user"?o:e.restoreText(o)),i.on("tool_call",(o,t)=>{if(!r)return{block:!0,reason:"SPI Protecter 尚未就绪。"};let s=e.restore(o.input);for(let a of Object.keys(o.input))delete o.input[a];if(Object.assign(o.input,s),re(o.toolName,o.input,t.cwd,e.configPath))return{block:!0,reason:"SPI Protecter:禁止工具访问保护配置,请由用户在本地编辑。"}}),i.on("session_before_compact",(o,t)=>(t.ui.notify("SPI Protecter:暂不支持远程上下文压缩,请使用 /new 开始新会话。","warning"),{cancel:!0})),i.on("session_before_tree",o=>o.preparation.userWantsSummary?{cancel:!0}:void 0),i.registerCommand("protecter",{description:"View local protection records / 查看本地保护记录",handler:async(o,t)=>{let s=o.trim().split(/\s+/).filter(Boolean),a=s[0]??"logs";if(!["status","reload","logs"].includes(a)||a!=="logs"&&s.length>1||s.length>2||s[1]!==void 0&&s[1]!=="clear"&&!/^(?:[1-9]\d?|100)$/.test(s[1])){t.ui.notify("Usage / 用法: /protecter [logs [1-100|clear]|status|reload]. No secrets in arguments / 不要在参数中输入秘密。","info");return}if(a==="logs"){if(t.mode!=="tui"){t.ui.notify("Local TUI required / 请在本地 TUI 查看记录。","warning");return}if(s[1]==="clear"){try{if(!e.ready)throw new Error;if(!await t.ui.confirm("Clear current audit log? / 清空当前审计日志?","Irreversible. Only this machine's log; config and mappings stay. Later requests may add records. / 不可撤销,仅清空本机日志,保留配置和映射,后续请求可能新增记录。"))return;await t.waitForIdle();let f=await e.clearAuditRecords();r=e.ready,t.ui.notify(`Audit cleared: ${f} bytes / 已清空日志:${f} 字节。`,"info")}catch{t.ui.notify("Unable to clear audit log; check permissions or lock / 无法清空日志,请检查权限或锁。","error")}return}try{let{records:f,truncated:d}=e.auditRecords(Number(s[1]??20));if(!f.length){t.ui.notify("No readable records / 暂无可读记录。","info");return}let p=E=>JSON.stringify(E).replace(/[\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g,b=>`\\u${b.charCodeAt(0).toString(16).padStart(4,"0")}`),l=f.map((E,b)=>`${b+1}. ${p(E.time)} | ${p(E.provider).slice(0,120)}`),g=await t.ui.select(`Protection records / 保护记录${d?" (limited tail / 有限尾部)":""}`,l),m=g===void 0?-1:l.indexOf(g);if(m<0||!await t.ui.confirm("Sensitive plaintext / 敏感明文","Reveal locally? Never share this view / 确认本地显示?请勿分享此界面。"))return;let y=p(f[m]);await t.ui.editor("Local preview; edits discarded / 本地预览,编辑不保存",y.length>2e4?y.slice(0,2e4)+`
9
+ [Preview truncated / 预览截断]`:y)}catch{t.ui.notify("Cannot read audit records / 无法读取审计记录。","error")}return}if(a==="reload"){await t.waitForIdle(),await t.reload();return}t.ui.notify(`SPI Protecter: ${e.ready?"ready / 就绪":"not ready / 未就绪"}; ${e.ruleCount} rules / 规则; ${e.mappingCount} mappings / 映射. Memory snapshot; /reload to refresh / 内存快照,重载后更新。`,e.ready?"info":"error")}}),i.on("session_shutdown",()=>e.close())}export{Te as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-information-protecter",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Sensitive Personal Information (SPI) Protecter — local reversible privacy redaction for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",