opencode-prompt-recorder 1.8.0 → 1.8.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/README.md +11 -4
- package/dist/index.js +8 -8
- package/dist/package.json +3 -6
- package/package.json +3 -6
package/README.md
CHANGED
|
@@ -31,10 +31,11 @@ bun add -g opencode-prompt-recorder
|
|
|
31
31
|
|
|
32
32
|
## 工作原理
|
|
33
33
|
|
|
34
|
-
1. 插件监听 OpenCode
|
|
34
|
+
1. 插件监听 OpenCode 事件(`message.updated`、`message.part.updated`、`session.created`、`session.updated`)
|
|
35
35
|
2. 每次用户消息时,提取提示词文本和会话 ID
|
|
36
36
|
3. 同一 session 的消息合并到同一个文件,按日期组织存储
|
|
37
|
-
4.
|
|
37
|
+
4. task 工具创建的子 agent 会话自动存入 `task/` 子目录,与主会话隔离
|
|
38
|
+
5. 文件首行记录 SessionID,每条消息带有时间戳
|
|
38
39
|
|
|
39
40
|
## 文件结构
|
|
40
41
|
|
|
@@ -43,6 +44,11 @@ bun add -g opencode-prompt-recorder
|
|
|
43
44
|
```
|
|
44
45
|
.agent/
|
|
45
46
|
└── prompts/
|
|
47
|
+
├── task/
|
|
48
|
+
│ └── 2026/
|
|
49
|
+
│ └── 06/
|
|
50
|
+
│ └── 05/
|
|
51
|
+
│ └── 2606051003-子任务分析.txt
|
|
46
52
|
└── 2026/
|
|
47
53
|
└── 06/
|
|
48
54
|
└── 05/
|
|
@@ -66,11 +72,11 @@ bun add -g opencode-prompt-recorder
|
|
|
66
72
|
```markdown
|
|
67
73
|
============ SessionID: ses_xxxxx ============
|
|
68
74
|
|
|
69
|
-
============ 2026-06-05 10:05 ============
|
|
75
|
+
============ 2026-06-05 10:05:30 ============
|
|
70
76
|
|
|
71
77
|
什么是 AI?
|
|
72
78
|
|
|
73
|
-
============ 2026-06-05 10:50 ============
|
|
79
|
+
============ 2026-06-05 10:50:15 ============
|
|
74
80
|
|
|
75
81
|
如何编写 hello world 程序?
|
|
76
82
|
```
|
|
@@ -90,6 +96,7 @@ bun add -g opencode-prompt-recorder
|
|
|
90
96
|
- 同一 session 的后续消息追加到同一文件,文件名不变
|
|
91
97
|
- 文件名中的特殊字符会被自动清理(移除 `<>:"/\|?*` 及控制字符)
|
|
92
98
|
- 文件名截断至最多 40 个字符
|
|
99
|
+
- 若目标文件名已存在(极低概率),自动附加 `-xxxx` 随机后缀以避免覆盖
|
|
93
100
|
|
|
94
101
|
## 使用场景
|
|
95
102
|
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import{mkdir as
|
|
2
|
-
`;try{let
|
|
3
|
-
`)[0].trim().replace(/<[^>]*>/g,"").replace(/\s+/g," ").trim().replace(
|
|
1
|
+
import{mkdir as j,appendFile as ee,writeFile as q,readFile as te,rename as Q}from"fs/promises";import{join as l,dirname as ne,basename as Ee}from"path";import{fileURLToPath as Ae}from"url";import{readFile as ye,rm as we}from"fs/promises";import{basename as $,dirname as w,join as U}from"path";import{fileURLToPath as he}from"url";var ke="opencode-prompt-recorder",K=!1;function Y(s,e){if(!e||K)return;K=!0;let n=new AbortController,o=setTimeout(()=>n.abort(),1e4);$e(n.signal).then(i=>{i.updated&&setTimeout(()=>{s.client.tui.showToast({body:{title:"Prompt Recorder \u66F4\u65B0",message:`${i.name} \u5DF2\u4ECE ${i.current} \u66F4\u65B0\u5230 ${i.latest}\uFF0C\u91CD\u542F OpenCode \u5B8C\u6210\u66F4\u65B0`,variant:"info"}})},5e3)}).catch(i=>{console.error("[prompt-recorder] auto-update check failed:",i)}).finally(()=>clearTimeout(o))}async function $e(s){let e=await be(ke);if(!e)return{updated:!1};let n=await F(U(e,"package.json"));if(!n?.name||!n.version)return{updated:!1};let o=await Pe(n.name,s);if(!o||!ve(o,n.version))return{updated:!1};let i=await De(e,n.name);if(!i)return{updated:!1};try{await we(i,{recursive:!0,force:!0})}catch{return{updated:!1,error:"remove_failed",name:n.name,current:n.version,latest:o}}return{updated:!0,name:n.name,current:n.version,latest:o}}async function be(s){let e=w(he(import.meta.url));for(;;){if((await F(U(e,"package.json")))?.name===s)return $(e)==="dist"?w(e):e;let o=w(e);if(o===e)return;e=o}}async function De(s,e){let n=w(s),o=$(n).startsWith("@")?w(n):n;if($(o)!=="node_modules")return;let i=w(o),c=await F(U(i,"package.json")),u=Me(i,e)??c?.dependencies?.[e];if(!(!u||!Se(u)))return i}function Me(s,e){if(e.startsWith("@")){let[i,c]=e.split("/");if(!i||!c||$(w(s))!==i)return;let u=`${c}@`,g=$(s);return g.startsWith(u)?g.slice(u.length):void 0}let n=`${e}@`,o=$(s);return o.startsWith(n)?o.slice(n.length):void 0}function Se(s){let e=s.trim();return e?!!(e==="latest"||e==="*"||/^[~^]/.test(e)||/^(?:>=|>|<=|<)/.test(e)||/\s+(?:\|\||-|[<>=])\s+/.test(e)):!1}async function F(s){try{let e=JSON.parse(await ye(s,"utf-8"));return e&&typeof e=="object"?e:void 0}catch{return}}async function Pe(s,e){try{let n=await fetch(`https://registry.npmjs.org/${encodeURIComponent(s)}/latest`,{signal:e});if(!n.ok)return;let o=await n.json();if(!o||typeof o!="object")return;let i=o.version;return typeof i=="string"?i:void 0}catch{return}}function ve(s,e){let n=Z(s),o=Z(e);if(!n||!o)return!1;for(let i=0;i<3;i++)if(n.parts[i]!==o.parts[i])return n.parts[i]>o.parts[i];if(!n.pre.length&&o.pre.length)return!0;if(n.pre.length&&!o.pre.length)return!1;for(let i=0;i<Math.max(n.pre.length,o.pre.length);i++){let c=n.pre[i],u=o.pre[i];if(c===void 0)return!1;if(u===void 0)return!0;if(c===u)continue;let g=/^\d+$/.test(c)?Number(c):void 0,y=/^\d+$/.test(u)?Number(u):void 0;return g!==void 0&&y!==void 0?g>y:g!==void 0?!1:y!==void 0?!0:c>u}return!1}function Z(s){let e=s.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.+)?$/);if(e)return{parts:[Number(e[1]),Number(e[2]),Number(e[3])],pre:e[4]?.split(".")??[]}}var _e=ne(Ae(import.meta.url));async function L(s,e){if(process.env.PROMPT_RECORDER_DEBUG!=="1"&&process.env.PROMPT_RECORDER_DEBUG!=="true")return;let o=`[${new Date().toISOString()}] ${e}
|
|
2
|
+
`;try{let i=l(s,".agent","prompts-log");await j(i,{recursive:!0}),await ee(l(i,"log.txt"),o)}catch(i){console.error("debugLog failed:",i)}}var I="";async function Ie(){if(I)return I;try{return I=JSON.parse(await te(l(_e,"package.json"),"utf-8")).version||"unknown",I}catch{return"unknown"}}var Re=/[<>:"/\\|?*\x00-\x1f\u200B-\u200F\u2028-\u202E\uFEFF]/g;function N(s){return s.split(`
|
|
3
|
+
`)[0].trim().replace(/<[^>]*>/g,"").replace(/\s+/g," ").trim().replace(Re,"").substring(0,40).trim()||"untitled"}function Te(s){let e=s.trimStart();return e.startsWith("<system-reminder>")||e.startsWith("<system>")}function xe(s){let e=s.getFullYear().toString(),n=String(s.getMonth()+1).padStart(2,"0"),o=String(s.getDate()).padStart(2,"0"),i=String(s.getHours()).padStart(2,"0"),c=String(s.getMinutes()).padStart(2,"0"),u=String(s.getSeconds()).padStart(2,"0");return{yyyy:e,MM:n,dd:o,HH:i,mm:c,ss:u}}var Ce=async s=>{Y(s,!0);let{directory:e}=s;ue();let n=new Map,o=new Map,i=new Map,c=new Map,u=1440*60*1e3,g=200,y=2e3,M=1440*60*1e3,re=3600*1e3,m=new Map,h=new Map,R=".txt",T=new Map;function ie(){if(m.size<g)return;let a=Date.now();for(let[t,r]of m)a-r.time>u&&m.delete(t)}function se(){let a=Date.now();if(i.size>y)for(let[t,r]of i)a-r>M&&i.delete(t);if(o.size>y)for(let[t,r]of o)a-r.time>M&&o.delete(t);if(c.size>y)for(let[t,r]of c)a-r>M&&c.delete(t);if(n.size>y)for(let[t,r]of n)a-r.time>M&&n.delete(t);if(h.size>0)for(let[t,r]of h)a-r.time>re&&h.delete(t)}async function S(a,t){let r=ne(a.filepath),d=Ee(a.filepath).match(/^(\d{10})-/);if(!d)return;let f=l(r,`${d[1]}-${N(t)}${R}`);if(f!==a.filepath)try{await Q(a.filepath,f),a.filepath=f}catch{let P=crypto.randomUUID().slice(0,8),v=l(r,`${d[1]}-${N(t)}-${P}${R}`);try{await Q(a.filepath,v),a.filepath=v}catch(k){console.error(`[prompt-recorder] rename failed: ${a.filepath}`,k)}}}async function oe(a){let t=a.properties.info,r=t?.id,p=t?.role||t?.message?.role;r&&p==="user"&&o.set(r,{role:p,time:Date.now()})}async function ae(a){let t=a.properties.part;if(t?.type==="tool"&&t?.tool==="task"){let b=t.state?.metadata??t.metadata;if(b){let D=b.sessionId??b.sessionID;D&&(c.set(D,Date.now()),await L(e,`[prompt-recorder] tracked task session: ${D}`))}}if(t?.type!=="text"||!t?.text||t.synthetic||t.ignored)return;let r=t.sessionID,p=t.messageID,d=t.text,f=o.get(p)?.role;if(f||(f=t.message?.role),f||(f=a.properties.info?.role),f||(f=a.properties.info?.message?.role),f!=="user"||!d||!r)return;if(Te(d)){await L(e,`[prompt-recorder] filtered system-injected: sessionID=${r}`);return}let P=p?`${p}:${d}`:`${r}:${d}`;if(i.has(P))return;i.set(P,Date.now()),se(),await L(e,`[prompt-recorder] event=${a.type}, role=${f}, sessionID=${r}, textLength=${d.length}, textPreview=${d.substring(0,50)}`);let v=new Date,{yyyy:k,MM:E,dd:A,HH:O,mm:H,ss:pe}=xe(v),z=l(e,".agent","prompts"),J=c.has(r)?l(z,"task",k,E,A):l(z,k,E,A);await j(J,{recursive:!0});let fe=k.slice(-2),G=`============ ${k}-${E}-${A} ${O}:${H}:${pe} ============`,x=m.get(r);if(x)x.time=Date.now(),await ee(x.filepath,`
|
|
4
4
|
|
|
5
|
-
${
|
|
5
|
+
${G}
|
|
6
6
|
|
|
7
|
-
${
|
|
7
|
+
${d}`);else{let b=N(n.get(r)?.title??d),D=`${fe}${E}${A}${O}${H}-${b}${R}`,W=l(J,D),le=`============ SessionID: ${r} ============`;await q(W,`${le}
|
|
8
8
|
|
|
9
|
-
${
|
|
9
|
+
${G}
|
|
10
10
|
|
|
11
|
-
${
|
|
11
|
+
${d}`),m.set(r,{filepath:W,time:Date.now()}),ie();let V=h.get(r);if(V){h.delete(r);let C=m.get(r);C&&await S(C,V.title)}let X=T.get(r);X&&clearTimeout(X);let me=setTimeout(async()=>{T.delete(r);try{let _=(await s.client.session.get({path:{id:r}}))?.data?.title;if(!_)return;let ge=n.get(r)?.title;if(_===ge)return;n.set(r,{title:_,time:Date.now()});let B=m.get(r);B&&await S(B,_)}catch{}},5e3);T.set(r,me)}}async function ce(a){let t=a.properties.info;if(t?.id&&t?.title){n.set(t.id,{title:t.title,time:Date.now()});let r=m.get(t.id);r&&await S(r,t.title)}}async function ue(){try{let a=await Ie(),t=l(e,".agent"),r=l(t,"opencode-prompt-recorder-readme.txt"),p=`# OpenCode Prompt Recorder
|
|
12
12
|
|
|
13
13
|
\u81EA\u52A8\u8BB0\u5F55\u7528\u6237\u63D0\u793A\u8BCD\u5230 .agent/prompts \u76EE\u5F55\u7684\u63D2\u4EF6\u3002
|
|
14
14
|
|
|
15
15
|
\u7248\u672C\uFF1A${a}
|
|
16
16
|
\u4F5C\u8005\uFF1Aanarckk
|
|
17
|
-
\u9879\u76EE\u5730\u5740\uFF1Ahttps://github.com/anarckk/opencode-prompt-recorder`;try{if(await
|
|
17
|
+
\u9879\u76EE\u5730\u5740\uFF1Ahttps://github.com/anarckk/opencode-prompt-recorder`;try{if(await te(r,"utf-8")===p)return}catch{}await j(t,{recursive:!0}),await q(r,p)}catch{}}async function de(a){let t=a.properties.info;if(t?.id&&t?.title){let r=n.get(t.id)?.title;if(n.set(t.id,{title:t.title,time:Date.now()}),r!==t.title){let p=m.get(t.id);p?await S(p,t.title):h.set(t.id,{title:t.title,time:Date.now()})}}}return{event:async({event:a})=>{switch(a.type){case"message.updated":await oe(a);break;case"message.part.updated":await ae(a);break;case"session.created":await ce(a);break;case"session.updated":await de(a);break}}}},Ge=Ce;export{Ge as default};
|
package/dist/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-prompt-recorder",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "OpenCode plugin for recording user prompts. Automatically saves user messages to a local file system with organized directory structure.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"import": "./dist/index.js"
|
|
11
|
-
"require": "./dist/index.js"
|
|
10
|
+
"import": "./dist/index.js"
|
|
12
11
|
}
|
|
13
12
|
},
|
|
14
13
|
"files": [
|
|
@@ -18,8 +17,7 @@
|
|
|
18
17
|
"build": "node -e \"fs.rmSync('dist',{force:true,recursive:true})\" && npx esbuild index.ts --bundle --platform=node --outdir=dist --format=esm --external:@opencode-ai/plugin --minify && node -e \"fs.copyFileSync('package.json','dist/package.json')\"",
|
|
19
18
|
"build:uncompressed": "node -e \"fs.rmSync('dist',{force:true,recursive:true})\" && npx esbuild index.ts --bundle --platform=node --outdir=dist --format=esm --external:@opencode-ai/plugin && node -e \"fs.copyFileSync('package.json','dist/package.json')\"",
|
|
20
19
|
"prepublishOnly": "npm run build",
|
|
21
|
-
"publish": "node scripts/publish-npmjs.js"
|
|
22
|
-
"test": "npx tsx test/index.ts"
|
|
20
|
+
"publish": "node scripts/publish-npmjs.js"
|
|
23
21
|
},
|
|
24
22
|
"keywords": [
|
|
25
23
|
"opencode",
|
|
@@ -46,7 +44,6 @@
|
|
|
46
44
|
},
|
|
47
45
|
"devDependencies": {
|
|
48
46
|
"@opencode-ai/plugin": "^0.15.0",
|
|
49
|
-
"@types/bun": "^1.3.1",
|
|
50
47
|
"esbuild": "^0.25.0",
|
|
51
48
|
"typescript": "^5.9.3"
|
|
52
49
|
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-prompt-recorder",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "OpenCode plugin for recording user prompts. Automatically saves user messages to a local file system with organized directory structure.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"import": "./dist/index.js"
|
|
11
|
-
"require": "./dist/index.js"
|
|
10
|
+
"import": "./dist/index.js"
|
|
12
11
|
}
|
|
13
12
|
},
|
|
14
13
|
"files": [
|
|
@@ -18,8 +17,7 @@
|
|
|
18
17
|
"build": "node -e \"fs.rmSync('dist',{force:true,recursive:true})\" && npx esbuild index.ts --bundle --platform=node --outdir=dist --format=esm --external:@opencode-ai/plugin --minify && node -e \"fs.copyFileSync('package.json','dist/package.json')\"",
|
|
19
18
|
"build:uncompressed": "node -e \"fs.rmSync('dist',{force:true,recursive:true})\" && npx esbuild index.ts --bundle --platform=node --outdir=dist --format=esm --external:@opencode-ai/plugin && node -e \"fs.copyFileSync('package.json','dist/package.json')\"",
|
|
20
19
|
"prepublishOnly": "npm run build",
|
|
21
|
-
"publish": "node scripts/publish-npmjs.js"
|
|
22
|
-
"test": "npx tsx test/index.ts"
|
|
20
|
+
"publish": "node scripts/publish-npmjs.js"
|
|
23
21
|
},
|
|
24
22
|
"keywords": [
|
|
25
23
|
"opencode",
|
|
@@ -46,7 +44,6 @@
|
|
|
46
44
|
},
|
|
47
45
|
"devDependencies": {
|
|
48
46
|
"@opencode-ai/plugin": "^0.15.0",
|
|
49
|
-
"@types/bun": "^1.3.1",
|
|
50
47
|
"esbuild": "^0.25.0",
|
|
51
48
|
"typescript": "^5.9.3"
|
|
52
49
|
}
|